Compare commits

...

8 Commits

Author SHA1 Message Date
Tony Fages 1887022681 Add view workout details
1 week ago
Tony Fages 7dd7b2a482 zlknef
1 week ago
Tony Fages 019b67cce0 Add view workout details
1 week ago
Anthony RICHARD c8696f223d Add exercice stub
2 weeks ago
Anthony RICHARD 2c8db06f32 Add error view redirection
2 weeks ago
Anthony RICHARD 90b1e450e1 Add JWT OAuth
3 weeks ago
Anthony RICHARD a3e567f291 Add name question on quiz
4 weeks ago
Anthony RICHARD 4cba2025cf Add abstract service
4 weeks ago

@ -1,13 +1,13 @@
import axios from 'axios';
import axios from "axios";
const apiClient = axios.create({
baseURL: 'https://codefirst.iut.uca.fr/containers/Optifit-optifit-ef-api/api/v1/',
timeout: 10000,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
// ajoute ici les headers supplémentaires, ex. Auth
},
baseURL:
"https://codefirst.iut.uca.fr/containers/Optifit-optifit-ef-api/api/v1/",
timeout: 10000,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
export default apiClient;

@ -1,4 +1,7 @@
export const AUTH = {
LOGIN: "/login",
REFRESH_TOKEN: "/refresh-token",
};
export const EXERCICES = {
GETALL: '/exercices',

@ -1,12 +0,0 @@
import apiClient from "../client";
import {EXERCICES} from "../endpoints";
export const getExercices = async () => {
try {
const response = await apiClient.get(EXERCICES.GETALL);
return response.data.data;
} catch (error) {
console.error(error);
throw error;
}
};

@ -0,0 +1,77 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router } from "expo-router";
import { getItemAsync } from "expo-secure-store";
import apiClient from "../client";
import { AUTH } from "../endpoints";
export abstract class AbstractService {
protected IDP_URL =
"https://codefirst.iut.uca.fr/containers/Optifit-optifit-ef-api/api/v1";
protected URL =
"https://codefirst.iut.uca.fr/containers/Optifit-optifit-ef-api/api/v1";
protected CLIENT_ID = "mobile-app";
protected CLIENT_SECRET = "super-secret";
protected SCOPES =
"openid profile training.generate training.read offline_access";
protected ACCESS_TOKEN_PATH = "access-token";
protected REFRESH_TOKEN_PATH = "refresh-token";
protected async request(
path: string,
options: RequestInit = {}
): Promise<any> {
const token = await getItemAsync(this.ACCESS_TOKEN_PATH);
const res = await fetch(`${this.URL}${path}`, {
...options,
headers: {
...(options.headers || {}),
Authorization: `Bearer ${token}`,
},
});
switch (res.status) {
case 401:
await this.tryRefreshToken();
return this.request(path, options);
case 403:
router.replace("/(error)/NotAllowedProblem");
return;
case 500:
router.replace("/(error)/InternalErrorProblem");
return;
case 503:
router.replace("/(error)/BlockedProblem");
return;
default:
return await res.json();
}
}
private async tryRefreshToken(): Promise<boolean> {
try {
const refreshToken = await AsyncStorage.getItem(this.REFRESH_TOKEN_PATH);
if (!refreshToken) return false;
const response = await apiClient.post(AUTH.REFRESH_TOKEN, {
refreshToken,
});
const { accessToken, refreshToken: newRefreshToken } = response.data;
await AsyncStorage.setItem(this.ACCESS_TOKEN_PATH, accessToken);
await AsyncStorage.setItem(this.REFRESH_TOKEN_PATH, newRefreshToken);
apiClient.defaults.headers.common[
"Authorization"
] = `Bearer ${accessToken}`;
return true;
} catch (e) {
return false;
}
}
}

@ -0,0 +1,14 @@
import { EXERCICES } from "@/api/endpoints";
import { Workout } from "@/model/Workout";
import { AbstractService } from "../abstract.service";
import { IExerciceService } from "./exercice.service.interface";
export class ExerciceAPIService
extends AbstractService
implements IExerciceService
{
async getExercices(): Promise<Workout[]> {
const data = await this.request(EXERCICES.GETALL);
return data.data.map((item: any) => Workout.fromJson(item));
}
}

@ -0,0 +1,3 @@
export interface IExerciceService {
getExercices(): Promise<any>;
}

@ -0,0 +1,64 @@
import { Workout } from "@/model/Workout";
import { AbstractService } from "../abstract.service";
import { IExerciceService } from "./exercice.service.interface";
export class ExerciceStubService
extends AbstractService
implements IExerciceService
{
async getExercices() {
return [
new Workout({
id: "1",
name: "Pompes",
description:
"Exercice classique de renforcement des pectoraux et triceps.",
duration: 60,
image: "https://example.com/images/pompes.jpg",
video: "https://example.com/videos/pompes.mp4",
nbSeries: 3,
nbRepetitions: 15,
}),
new Workout({
id: "2",
name: "Squats",
description: "Travail les jambes, les fessiers et le tronc.",
duration: 90,
image: "https://example.com/images/squats.jpg",
video: "https://example.com/videos/squats.mp4",
nbSeries: 4,
nbRepetitions: 20,
}),
new Workout({
id: "3",
name: "Gainage",
description: "Renforcement du tronc, gainage statique.",
duration: 45,
image: "https://example.com/images/gainage.jpg",
video: "https://example.com/videos/gainage.mp4",
nbSeries: 3,
nbRepetitions: 1, // pour les exercices isométriques, 1 répétition de 45s par exemple
}),
new Workout({
id: "4",
name: "Fentes",
description: "Renforcement des jambes, bon pour léquilibre.",
duration: 80,
image: "https://example.com/images/fentes.jpg",
video: "https://example.com/videos/fentes.mp4",
nbSeries: 3,
nbRepetitions: 12,
}),
new Workout({
id: "5",
name: "Abdominaux",
description: "Travail des muscles abdominaux avec crunchs.",
duration: 60,
image: "https://example.com/images/abdos.jpg",
video: "https://example.com/videos/abdos.mp4",
nbSeries: 4,
nbRepetitions: 20,
}),
];
}
}

@ -0,0 +1,7 @@
import { IExerciceService } from "./exercice/exercice.service.interface";
import { ExerciceStubService } from "./exercice/exercice.service.stub";
import { IUserService } from "./user/user.service.interface";
import { UserStubService } from "./user/user.service.stub";
export const ExerciceServiceRef: IExerciceService = new ExerciceStubService();
export const UserServiceRef: IUserService = new UserStubService();

@ -0,0 +1,31 @@
import { User } from "@/model/User";
import { setItemAsync } from "expo-secure-store";
import { AbstractService as AbstractAPIService } from "../abstract.service";
import { IUserService } from "./user.service.interface";
export class UserAPIService extends AbstractAPIService implements IUserService {
async login(email: string, password: string): Promise<User> {
const body = new URLSearchParams({
grant_type: "password",
client_id: email,
client_secret: this.CLIENT_SECRET,
email,
password,
scope: this.SCOPES,
});
const res = await fetch(`${this.IDP_URL}/connect/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: body.toString(),
});
if (!res.ok) throw new Error(`Auth failed: ${res.status}`);
const json = await res.json();
await setItemAsync(this.ACCESS_TOKEN_PATH, json.access_token);
await setItemAsync(this.REFRESH_TOKEN_PATH, json.refresh_token);
return json;
}
}

@ -1,5 +1,5 @@
import { User } from "@/model/User";
export interface IUserService {
login(email: string, password: string): User | undefined;
login(email: string, password: string): Promise<User>;
}

@ -0,0 +1,167 @@
import { User } from "@/model/User";
import { IUserService } from "./user.service.interface";
export class UserStubService implements IUserService {
private readonly users: User[] = [
new User({
name: "Alice",
age: 28,
height: 165,
weight: 58,
sexe: false,
logo: "alice.png",
nbSessionPerWeek: 3,
goal: "Perdre du poids",
healthProblems: [],
sport: "YOGA",
sleepLevel: "GOOD",
sportLevel: "BEGINNER",
email: "test@1.com",
password: "password1",
}),
new User({
email: "test@2.com",
password: "password2",
}),
new User({
name: "Charlie",
age: 22,
height: 172,
weight: 70,
sexe: true,
logo: "charlie.png",
nbSessionPerWeek: 2,
goal: "Se remettre en forme",
healthProblems: [],
sport: "BIKING",
sleepLevel: "GOOD",
sportLevel: "BEGINNER",
email: "test@3.com",
password: "password3",
}),
new User({
name: "Diana",
age: 31,
height: 160,
weight: 55,
sexe: false,
logo: "diana.png",
nbSessionPerWeek: 5,
goal: "Préparer un marathon",
healthProblems: [],
sport: "RUNNING",
sleepLevel: "GOOD",
sportLevel: "VERY_SPORTY",
email: "test@4.com",
password: "password4",
}),
new User({
name: "Ethan",
age: 40,
height: 180,
weight: 88,
sexe: true,
logo: "ethan.png",
nbSessionPerWeek: 1,
goal: "Maintenir sa forme",
healthProblems: ["MIGRAINE"],
sport: "WALKING",
sleepLevel: "BAD",
sportLevel: "SPORTY",
email: "test@5.com",
password: "password5",
}),
new User({
name: "Fiona",
age: 26,
height: 167,
weight: 62,
sexe: false,
logo: "fiona.png",
nbSessionPerWeek: 3,
goal: "Renforcer le dos",
healthProblems: ["MIGRAINE"],
sport: "CARDIO",
sleepLevel: "BAD",
sportLevel: "BEGINNER",
email: "test@6.com",
password: "password6",
}),
new User({
name: "George",
age: 30,
height: 185,
weight: 90,
sexe: true,
logo: "george.png",
nbSessionPerWeek: 4,
goal: "Perdre du gras",
healthProblems: [],
sport: "BIKING",
sleepLevel: "TERRIBLE",
sportLevel: "SPORTY",
email: "test@7.com",
password: "password7",
}),
new User({
name: "Hanna",
age: 24,
height: 158,
weight: 54,
sexe: false,
logo: "hanna.png",
nbSessionPerWeek: 2,
goal: "Se tonifier",
healthProblems: [],
sport: "RANDO",
sleepLevel: "GOOD",
sportLevel: "BEGINNER",
email: "test@8.com",
password: "password8",
}),
new User({
name: "Ivan",
age: 50,
height: 175,
weight: 95,
sexe: true,
logo: "ivan.png",
nbSessionPerWeek: 1,
goal: "Rééducation",
healthProblems: ["ARTHROSE"],
sport: "WALKING",
sleepLevel: "BAD",
sportLevel: "BEGINNER",
email: "test@9.com",
password: "password9",
}),
new User({
name: "Julia",
age: 29,
height: 170,
weight: 60,
sexe: false,
logo: "julia.png",
nbSessionPerWeek: 3,
goal: "Rester active",
healthProblems: [],
sport: "ELSE",
sleepLevel: "GOOD",
sportLevel: "SPORTY",
email: "test@10.com",
password: "password10",
}),
];
async login(email: string, password: string): Promise<User> {
const user = this.users.find(
(x) => x.email === email && x.password === password
);
if (!user) {
throw new Error("User not found.");
}
return user;
}
}

@ -1,13 +1,13 @@
import BackButton from "@/components/BackButton";
import { EMPTY_FIELD, INVALID_EMAIL } from "@/components/Errors";
import FormError from "@/components/form/FormError";
import TextInput from "@/components/form/FormInput";
import CodeSent from "@/components/modals/CodeSent";
import Button from "@/components/ui/Button";
import Screen from "@/components/ui/Screen";
import Text from "@/components/ui/Text";
import TextInput from "@/components/form/FormInput";
import BackButton from "@/components/BackButton";
import { View } from "react-native";
import React from "react";
import CodeSent from "@/components/modals/CodeSent";
import FormError from "@/components/form/FormError";
import { EMPTY_FIELD, INVALID_EMAIL } from "@/components/Errors";
import { View } from "react-native";
import { isEmail } from "validator";
export default function ResetPasswordWithEmail() {

@ -0,0 +1,16 @@
import Error from "@/components/Error";
//@ts-ignore
import blockedPict from "@/assets/images/Blocked.png";
import { FEATURE_LOCKED } from "@/components/Errors";
import React from "react";
export default function Blocked() {
return (
<Error
picture={blockedPict}
problem="Fonctionnalité bloquée"
description={FEATURE_LOCKED}
information="Devenez PREMIUM pour débloquer"
/>
);
}

@ -0,0 +1,16 @@
import Error from "@/components/Error";
//@ts-ignore
import internalErrorPict from "@/assets/images/InternalError.png";
import { INTERNAL_ERROR } from "@/components/Errors";
import React from "react";
export default function InternalError() {
return (
<Error
picture={internalErrorPict}
problem="Problème interne"
description={INTERNAL_ERROR}
information="Contactez le support"
/>
);
}

@ -0,0 +1,16 @@
import Error from "@/components/Error";
//@ts-ignore
import maintenancePict from "@/assets/images/Maintenance.png";
import { MAINTENANCE } from "@/components/Errors";
import React from "react";
export default function Maintenance() {
return (
<Error
picture={maintenancePict}
problem="Maintenance"
description={MAINTENANCE}
information="Revenez plus tard"
/>
);
}

@ -0,0 +1,16 @@
import Error from "@/components/Error";
//@ts-ignore
import noInternetPict from "@/assets/images/NoInternet.png";
import { NO_INTERNET } from "@/components/Errors";
import React from "react";
export default function NoInternet() {
return (
<Error
picture={noInternetPict}
problem="Pas d'internet"
description={NO_INTERNET}
information="Réessayez plus tard"
/>
);
}

@ -0,0 +1,16 @@
import Error from "@/components/Error";
//@ts-ignore
import notAllowedPict from "@/assets/images/NotAllowed.png";
import { NOT_AUTHORIZED } from "@/components/Errors";
import React from "react";
export default function NotAllowedProblem() {
return (
<Error
picture={notAllowedPict}
problem="Pas autorisé"
description={NOT_AUTHORIZED}
information="Connectez vous avec plus de privilèges"
/>
);
}

@ -0,0 +1,16 @@
import Error from "@/components/Error";
//@ts-ignore
import notFoundPict from "@/assets/images/NotFound.png";
import { NOT_FOUND } from "@/components/Errors";
import React from "react";
export default function NotFound() {
return (
<Error
picture={notFoundPict}
problem="Introuvable"
description={NOT_FOUND}
information="Status Code : 404"
/>
);
}

@ -17,6 +17,7 @@ import GoalQuestion, { GoalQuestionRef } from "@/components/quiz/GoalQuestion";
import HeightQuestion, {
HeightQuestionRef,
} from "@/components/quiz/HeightQuestion";
import NameQuestion, { NameQuestionRef } from "@/components/quiz/NameQuestion";
import SleepQuestion, {
SleepQuestionRef,
} from "@/components/quiz/SleepQuestion";
@ -30,13 +31,12 @@ import Button from "@/components/ui/Button";
import Screen from "@/components/ui/Screen";
import Text from "@/components/ui/Text";
import { useSession } from "@/ctx";
import { useRouter } from "expo-router";
import { router } from "expo-router";
import React, { useRef, useState } from "react";
import { View } from "react-native";
export default function Quiz() {
const { signIn, session } = useSession();
const router = useRouter();
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const goalRef = useRef<GoalQuestionRef>(null);
@ -47,8 +47,9 @@ export default function Quiz() {
const beginnerRef = useRef<BeginnerQuestionRef>(null);
const activityRef = useRef<ActivityQuestionRef>(null);
const frequencyRef = useRef<FrequencyQuestionRef>(null);
const sportQuestionRef = useRef<SportQuestionRef>(null);
const sleepQuestionRef = useRef<SleepQuestionRef>(null);
const sportRef = useRef<SportQuestionRef>(null);
const sleepRef = useRef<SleepQuestionRef>(null);
const NameRef = useRef<NameQuestionRef>(null);
interface Question<T = any> {
component: React.ForwardRefExoticComponent<T & React.RefAttributes<any>>;
@ -56,6 +57,7 @@ export default function Quiz() {
}
const questions: Question[] = [
{ component: NameQuestion, props: { ref: NameRef } },
{ component: GoalQuestion, props: { ref: goalRef } },
{ component: GenderQuestion, props: { ref: genderRef } },
{
@ -71,15 +73,17 @@ export default function Quiz() {
component: FrequencyQuestion,
props: { ref: frequencyRef, isMale: genderRef.current?.getAnswer() },
},
{ component: SportQuestion, props: { ref: sportQuestionRef } },
{ component: SleepQuestion, props: { ref: sleepQuestionRef } },
{ component: SportQuestion, props: { ref: sportRef } },
{ component: SleepQuestion, props: { ref: sleepRef } },
];
const goNext = () => {
if (currentQuestionIndex < questions.length - 1) {
setCurrentQuestionIndex(currentQuestionIndex + 1);
} else {
Collect();
if (NameRef.current?.isOk()) {
if (currentQuestionIndex < questions.length - 1) {
setCurrentQuestionIndex(currentQuestionIndex + 1);
} else {
Collect();
}
}
};
@ -87,15 +91,16 @@ export default function Quiz() {
if (session) {
session.healthProblems = [];
session.goals = goalRef.current?.getAnswer();
session.name = NameRef.current?.getAnswer();
session.goal = goalRef.current?.getAnswer();
session.sexe = genderRef.current?.getAnswer();
session.weight = weightRef.current?.getAnswer();
session.height = heightRef.current?.getAnswer();
session.age = ageRef.current?.getAnswer();
session.sportLevel = activityRef.current?.getAnswer();
session.nbSessionPerWeek = frequencyRef.current?.getAnswer();
session.sports = sportQuestionRef.current?.getAnswer();
session.sleepLevel = sleepQuestionRef.current?.getAnswer();
session.sport = sportRef.current?.getAnswer();
session.sleepLevel = sleepRef.current?.getAnswer();
signIn(session);
}

@ -1,16 +1,33 @@
import {SafeAreaView, View, Text} from "react-native";
import React from "react";
import InternalError from "@/components/error/InternalErrorProblem";
import {
ImageBackground,
SafeAreaView,
Text,
TouchableOpacity,
View,
} from "react-native";
export default function AddScreen() {
return (
<SafeAreaView>
<View>
<InternalError/>
</View>
</SafeAreaView>
);
}
return (
<ImageBackground
className="h-full w-full"
source={require("assets/images/TrainingPresentation.png")}
>
<SafeAreaView>
<View className="flex-row justify-between items-center mb-4">
<TouchableOpacity className="bg-blue-500 px-4 py-2 rounded"></TouchableOpacity>
<TouchableOpacity className="bg-green-500 px-4 py-2 rounded">
<Text className="text-white font-bold">Action</Text>
</TouchableOpacity>
</View>
//<Text className="m-7 font-extrabold">Welcome to Add Screen </Text>
// <Text>We will do it soon</Text>
<View className="flex-1 items-center justify-center">
<Text className="text-xl font-semibold">
Contenu de l'entraînement
</Text>
</View>
</SafeAreaView>
</ImageBackground>
);
}

@ -1,11 +1,13 @@
import React from "react";
import { Stack } from "expo-router";
import React from "react";
export default function RootoLayout() {
return (
<Stack screenOptions={{
headerShown: false,
}}>
<Stack
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="index" />
</Stack>
);

@ -1,97 +0,0 @@
// import { SafeAreaView, StyleSheet, View } from "react-native";
// import React from "react";
// import {
// Avatar,
// AvatarFallbackText,
// AvatarImage,
// } from "@/components/ui/avatar";
// import { AntDesign } from "@expo/vector-icons";
// import { Text } from "@/components/ui/text";
// import ExerciceOverview from "@/components/ExerciceOverview";
// export default function HomeScreen() {
// const date = new Date();
// const formattedDate = date.toLocaleDateString("fr-FR", {
// year: "numeric",
// month: "long",
// day: "numeric",
// });
// return (
// <SafeAreaView style={styles.container}>
// <View style={styles.headerStyle}>
// <View style={styles.dateContainer}>
// <AntDesign name="calendar" size={24} color="white" />
// <Text style={styles.dateText}>{formattedDate}</Text>
// </View>
// <View style={styles.avatarContainer}>
// <Avatar size="xl">
// <AvatarFallbackText>Jane Doe</AvatarFallbackText>
// <AvatarImage
// source={{
// uri: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80",
// }}
// />
// </Avatar>
// <Text style={styles.titleNameUser}>Hello, Tata Monique</Text>
// </View>
// </View>
// <View>
// <ExerciceOverview />
// </View>
// </SafeAreaView>
// );
// }
// const styles = StyleSheet.create({
// container: {
// flex: 1,
// backgroundColor: "#f9f9f9", // Fond de l'écran avec une couleur claire
// },
// headerStyle: {
// height: 200,
// backgroundColor: "#333333", // Gris foncé pour l'arrière-plan du header
// borderBottomEndRadius: 25,
// borderBottomStartRadius: 25,
// padding: 20,
// alignItems: "flex-start",
// justifyContent: "flex-start",
// },
// dateContainer: {
// flexDirection: "row",
// alignItems: "flex-start",
// marginBottom: 20,
// },
// dateText: {
// fontSize: 18,
// fontWeight: "bold",
// marginLeft: 10,
// color: "#ffffff",
// },
// avatarContainer: {
// marginBottom: 15,
// flexDirection: "row",
// alignItems: "center",
// },
// titleNameUser: {
// fontSize: 24,
// color: "#ffffff",
// fontWeight: "bold",
// marginLeft: 10,
// },
// contentContainer: {
// marginTop: 20,
// padding: 15,
// alignItems: "flex-start",
// },
// contentText: {
// fontSize: 16,
// color: "#333333", // Texte en gris foncé pour une bonne lisibilité
// textAlign: "center",
// },
// fitness: {},
// });

@ -1,56 +0,0 @@
import { getExercices } from "@/api/services/ExercicesServices";
import WorkoutPresentationComponent from "@/components/WorkoutPresentationComponent";
import { useRouter } from "expo-router";
import React, { useEffect, useState } from "react";
import { ActivityIndicator, Text, View } from "react-native";
export default function WorkoutScreen() {
const router = useRouter();
const [exercices, setExercices] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const loadExercices = async () => {
try {
const data = await getExercices();
setExercices(data);
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
loadExercices();
}, []);
if (loading) {
return (
<View className="flex-1 justify-center items-center bg-black">
<ActivityIndicator size="large" color="#fff" />
</View>
);
}
if (error) {
return (
<View className="flex-1 justify-center items-center bg-black">
<Text className="text-red-500 text-xl">{error}</Text>
</View>
);
}
const workout = exercices[1]; // Choisis lexercice que tu souhaites afficher
return (
<View className="h-full rounded-2xl">
<WorkoutPresentationComponent
workout={workout}
dataExercise={exercices}
router={router}
/>
</View>
);
}

@ -1,19 +0,0 @@
import React from "react";
import {Stack} from "expo-router";
export default function RootoLayout() {
return (
<Stack screenOptions={{
headerShown: false,
}}
initialRouteName={"ExercicesScreen"}
>
<Stack.Screen name="ExercicesScreen" />
<Stack.Screen name="WorkoutScreen"/>
</Stack>
);
}

@ -1,14 +1,13 @@
import {SafeAreaView, Text, View} from "react-native";
import Blocked from "@/app/(error)/BlockedProblem";
import React from "react";
import Blocked from "@/components/error/BlockedProblem";
import { SafeAreaView, View } from "react-native";
export default function HelpsScreen() {
return (
<SafeAreaView>
<View>
<Blocked/>
</View>
</SafeAreaView>
);
return (
<SafeAreaView>
<View>
<Blocked />
</View>
</SafeAreaView>
);
}

@ -1,15 +1,14 @@
import { Stack } from "expo-router";
import React from "react";
import {Stack} from "expo-router";
import HelpsScreen from "@/app/(tabs)/(help)/HelpsScreen";
export default function RootoLayout() {
return (
<Stack screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="HelpsScreen" />
</Stack>
);
return (
<Stack
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="HelpsScreen" />
</Stack>
);
}

@ -1,4 +1,4 @@
import { getExercices } from "@/api/services/ExercicesServices";
import { ExerciceServiceRef } from "@/api/services/service.ref";
import ActivitiesComponent from "@/components/ActivitiesComponent";
import CalendarComponent from "@/components/CalendarComponent";
import WelcomeComponent from "@/components/WelcomeComponent";
@ -7,8 +7,10 @@ import Screen from "@/components/ui/Screen";
import { Workout } from "@/model/Workout";
import React, { useEffect, useState } from "react";
import { ScrollView, Text, View } from "react-native";
export default function HomeScreen() {
const [exercices, setExercices] = useState<Workout[]>([]);
const exerciceService = ExerciceServiceRef;
const getRandomNumber = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
@ -16,7 +18,7 @@ export default function HomeScreen() {
useEffect(() => {
const fetchData = async () => {
try {
const data = await getExercices();
const data = await exerciceService.getExercices();
setExercices(data);
} catch (err: any) {}
};
@ -47,7 +49,7 @@ export default function HomeScreen() {
<Text className="text-orange-500 font-semibold">See All</Text>
</View>
<WorkoutCardComponent
exercise={exercices[getRandomNumber(0, 3)]}
exercise={exercices[getRandomNumber(0, exercices.length - 1)]}
background="bg-black"
/>
</View>

@ -3,9 +3,11 @@ import React from "react";
export default function RootoLayout() {
return (
<Stack screenOptions={{
headerShown: false,
}}>
<Stack
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="HomeScreen" />
</Stack>
);

@ -1,10 +1,6 @@
import {SafeAreaView, Text, View} from "react-native";
import React from "react";
import HomeScreen from "@/app/(tabs)/(home)/HomeScreen";
import React from "react";
export default function App() {
return (
<HomeScreen/>
);
return <HomeScreen />;
}

@ -1,8 +1,8 @@
import { SafeAreaView, Text, View } from "react-native";
import React from "react";
import Button from "@/components/ui/Button";
import { useSession } from "@/ctx";
import { router } from "expo-router";
import React from "react";
import { SafeAreaView, Text, View } from "react-native";
export default function ProfileScreen() {
const { signOut } = useSession();

@ -3,10 +3,12 @@ import React from "react";
export default function RootoLayout() {
return (
<Stack screenOptions={{
headerShown: false,
}}>
<Stack.Screen name="ProfileScreen" />
</Stack>
<Stack
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="ProfileScreen" />
</Stack>
);
}

@ -1,9 +1,9 @@
import { getExercices } from "@/api/services/ExercicesServices";
import { ExerciceServiceRef } from "@/api/services/service.ref";
import WorkoutCardComponent from "@/components/WorkoutCardComponent";
import { Workout } from "@/model/Workout";
import { useRouter } from "expo-router";
import {useNavigation, useRouter} from "expo-router";
import React, { useEffect, useState } from "react";
import { FlatList, Text, TouchableOpacity, View } from "react-native";
import {FlatList, SafeAreaView, Text, TouchableOpacity, View} from "react-native";
export default function ExercicesScreen() {
const [exercices, setExercices] = useState<Workout[]>([]);
@ -11,10 +11,12 @@ export default function ExercicesScreen() {
const [error, setError] = useState(null);
const router = useRouter();
const exerciceService = ExerciceServiceRef;
useEffect(() => {
const fetchData = async () => {
try {
const data = await getExercices();
const data = await exerciceService.getExercices();
setExercices(data);
} catch (err: any) {
setError(err.message);
@ -35,24 +37,18 @@ export default function ExercicesScreen() {
}
return (
<View className="p-4">
<SafeAreaView>
<FlatList
data={exercices}
keyExtractor={(item) => item.id}
keyExtractor={(item) => item.id ?? ""}
renderItem={({ item }) => (
<TouchableOpacity
className="p-4 mb-2 bg-gray-800 rounded-lg"
onPress={() =>
router.push({
pathname: "/WorkoutScreen",
params: { workout: JSON.stringify(item) },
})
}
className="flex-1 m-2"
>
<WorkoutCardComponent exercise={item} background={"black"} />
</TouchableOpacity>
)}
/>
</View>
</SafeAreaView>
);
}

@ -0,0 +1,156 @@
import React, { useState } from "react";
import { View, Text, ImageBackground, TouchableOpacity, ScrollView, Dimensions, Image } from "react-native";
import Modal from "react-native-modal";
import { Ionicons } from "@expo/vector-icons";
import {useWorkoutStore} from "@/store/workoutStore";
import {Workout} from "@/model/Workout";
import {useRouter} from "expo-router";
const screenHeight = Dimensions.get("window").height;
export default function WorkoutScreen() {
const router = useRouter();
const workout = useWorkoutStore((s) => s.selectedWorkout);
const [isModalVisible, setModalVisible] = useState(false);
if (!workout) return <Text>Pas de workout</Text>;
const exercises: any[] = [
{
id: "1",
title: "Back Warmup",
duration: "05:30",
},
{
id: "2",
title: "Bent Over Row",
duration: "10:00",
},
{
id: "3",
title: "Renegade Row",
duration: "08:45",
},
];
return (
<View className="flex-1 bg-black">
<ImageBackground
source={require("assets/images/TrainingPresentation.png")}
className="flex-1 justify-between px-6 pb-10 pt-14"
imageStyle={{ resizeMode: "cover" }}
>
<View className="flex-row justify-between">
<TouchableOpacity className="bg-black/50 p-2 rounded-full">
<Ionicons name="chevron-back" size={20} color="white" />
</TouchableOpacity>
<TouchableOpacity className="bg-black/50 p-2 rounded-full">
<Ionicons name="settings-outline" size={20} color="white" />
</TouchableOpacity>
</View>
<View className="flex-1 justify-end">
<View className="items-center">
<View className="bg-black/50 px-6 py-2 rounded-full mb-4">
<Text className="text-white text-base">{exercises.length} Total</Text>
</View>
<Text className="text-white text-4xl font-bold">{workout.name}</Text>
</View>
<View className="flex-row justify-between mb-16 mt-16">
<View className="items-center flex-1">
<Ionicons name="time-outline" size={36} color="white" />
<Text className="text-white font-bold text-xl mt-2">{workout.duration} min</Text>
<Text className="text-gray-300 text-lg">Time</Text>
</View>
<View className="h-20 w-1 bg-white mx-2" />
<View className="items-center flex-1">
<Ionicons name="flame-outline" size={36} color="white" />
<Text className="text-white font-bold text-xl mt-2">240kcal</Text>
<Text className="text-gray-300 text-lg">Calories</Text>
</View>
<View className="h-20 w-1 bg-white mx-2" />
<View className="items-center flex-1">
<Ionicons name="barbell-outline" size={36} color="white" />
<Text className="text-white font-bold text-xl mt-2">{workout.nbRepetitions}</Text>
<Text className="text-gray-300 text-lg">Sets</Text>
</View>
</View>
</View>
<View className="flex-row">
<TouchableOpacity
className="flex-1 bg-black/50 p-4 rounded-xl flex-row justify-center items-center"
onPress={() => setModalVisible(true)}
>
<Text className="text-white mr-2">Details</Text>
<Ionicons name="document-text-outline" size={18} color="white" />
</TouchableOpacity>
<TouchableOpacity className="flex-1 bg-orange-500 p-4 rounded-xl flex-row justify-center items-center">
<Text className="text-white font-bold mr-2">Start</Text>
<Ionicons name="timer-outline" size={18} color="white" />
</TouchableOpacity>
</View>
</ImageBackground>
<Modal
isVisible={isModalVisible}
onBackdropPress={() => setModalVisible(false)}
swipeDirection="down"
onSwipeComplete={() => setModalVisible(false)}
style={{
justifyContent: "flex-end",
margin: 0,
}}
>
<View
style={{
height: screenHeight * 0.65,
backgroundColor: "white",
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
padding: 20,
}}
>
<ScrollView>
<Text className="text-center text-gray-700 mb-4">
Liste des exercices
</Text>
{exercises.map((ex, index) => (
<View
key={ex.id}
className="flex-row bg-gray-100 rounded-xl items-center p-3 mb-4"
>
<Image source={require("assets/images/TrainingPresentation.png")} className="w-16 h-16 rounded-md mr-3" />
<View className="flex-1">
<Text className="font-semibold text-black">
{index + 1}. {ex.title}
</Text>
<View className="flex-row items-center mt-1">
<Ionicons name="time-outline" size={14} color="gray" />
<Text className="text-gray-500 text-xs ml-1">{ex.duration}</Text>
</View>
</View>
<TouchableOpacity
onPress={() => {
// router.push("/(tabs)/(exercice)/ExercicesScreen")
}}>
<Ionicons name="play-circle" size={30} color="#ff7a00" />
</TouchableOpacity>
</View>
))}
</ScrollView>
</View>
</Modal>
</View>
);
}

@ -0,0 +1,32 @@
import {router, useLocalSearchParams} from 'expo-router';
import { View, Text, Image } from 'react-native';
import { useEffect, useState } from 'react';
import {Workout} from "@/model/Workout";
import WorkoutPresentationComponent from "@/components/WorkoutPresentationComponent";
export default function WorkoutScreen() {
const params = useLocalSearchParams();
const [workout, setWorkout] = useState<Workout | null>(null);
useEffect(() => {
if (typeof params.workout === 'string') {
try {
const parsed = JSON.parse(params.workout);
setWorkout(parsed);
} catch (error) {
}
}
}, []);
if (!workout) {
return <Text style={{ padding: 20, margin: 30 }}>Aucune donnée reçue.</Text>;
}
return (
<View>
<WorkoutPresentationComponent
workout={workout}
/>r
</View>
);
}

@ -0,0 +1,16 @@
import React from "react";
import {Stack} from "expo-router";
export default function RootoLayout() {
return (
<Stack
screenOptions={{
headerShown: false,
}}
initialRouteName="ExercicesScreen"
>
<Stack.Screen name="ExercicesScreen" />
<Stack.Screen name="WorkoutScreen" />
</Stack>
);
}

@ -1,13 +1,12 @@
import { useSession } from "@/ctx";
import { AntDesign, Ionicons, MaterialIcons } from "@expo/vector-icons";
import { Redirect, Tabs, useRouter } from "expo-router";
import { Redirect, Tabs } from "expo-router";
import React from "react";
import Loading from "../loading";
export default function TabBarLayout() {
const { session, isLoading } = useSession();
const router = useRouter();
const sizeIcon = 24;
if (isLoading) {
return <Loading />;
@ -16,13 +15,13 @@ export default function TabBarLayout() {
if (!session) {
return <Redirect href={"/log-in"} />;
}
console.log(session);
if (!session.isQuizDone()) {
return <Redirect href={"/quiz"} />;
}
return (
<Tabs
<Tabs
screenOptions={{
headerShown: false,
}}
@ -38,7 +37,7 @@ export default function TabBarLayout() {
/>
<Tabs.Screen
name="(exercice)"
name="(workout)"
options={{
tabBarShowLabel: false,
tabBarIcon: () => (

@ -1,37 +0,0 @@
import Text from "@/components/ui/Text";
import React, {forwardRef} from "react";
import {Image, View, ViewProps} from "react-native";
import BackButton from "@/components/BackButton";
import {Entypo} from "@expo/vector-icons";
export interface ProblemProps extends ViewProps{
picture: string;
problem: string;
description: string;
information: string;
isVisible?: boolean;
}
export default forwardRef<any, ProblemProps> (({className, ...Props}, ref) => {
return (
<View className={"gap-4 justify-between h-3/4" + " " + className} {...Props} ref={ref}>
<View className="flex-row justify-between items-center p-4">
<BackButton/>
</View>
<View className="flex-row justify-center">
<Image className="aspect-square w-3/5 h-3/5" source={Props.picture}/>
</View>
<Text position="center" weight="bold" size="3xl"> {Props.problem} </Text>
<Text size="lg" position="center" className="text-gray-400"> {Props.description} </Text>
<View className="flex-row justify-center">
<View className="flex-row items-center border-2 rounded-2xl bg-red-300 border-red-600 p-4">
<Entypo name="warning" size={30} color="red"/>
<Text size="lg" position="center"> {Props.information} </Text>
</View>
</View>
</View>
);
});

@ -1,19 +1,19 @@
import {Link, router, Stack, usePathname, useRouter} from 'expo-router';
import {Button, SafeAreaView, StyleSheet, Text, View} from 'react-native';
import React from "react";
import { router, Stack, usePathname } from "expo-router";
import { Button, Text, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
export default function NotFoundScreen() {
const pathname = usePathname();
const router = useRouter();
return (
<SafeAreaView>
<Stack.Screen options={{ title: 'Oops!' }} />
<Stack.Screen options={{ title: "Oops!" }} />
<View>
<Text>This screen {pathname} doesn't exist: {pathname}</Text>
<Button title="Retour Home" onPress={() => router.replace("/")}/>
<Text>Go to home screen!</Text>
<Text>
This screen {pathname} doesn't exist: {pathname}
</Text>
<Button title="Retour Home" onPress={() => router.replace("/")} />
<Text>Go to home screen!</Text>
</View>
</SafeAreaView>
);

@ -6,7 +6,6 @@ import { Slot } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
@ -21,13 +20,11 @@ export default function RootLayout() {
}
}, [loaded]);
if (!loaded) {
return null;
}
if (!loaded) return null;
return (
<SessionProvider>
<Slot />
</SessionProvider>
<SessionProvider>
<Slot />
</SessionProvider>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,113 +1,114 @@
import {Text, TouchableOpacity, View} from "react-native";
import {CurveType, LineChart} from "react-native-gifted-charts";
import React, {useRef, useState} from "react";
import React, { useRef, useState } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import { CurveType, LineChart } from "react-native-gifted-charts";
export default function ActivitiesComponent() {
const ref = useRef(null)
const [dateSelected, setDateSelected] = useState('1d');
const [lineData, setLineData] = useState([
{value: 4},
{value: 14},
{value: 8},
{value: 38},
{value: 36},
{value: 28},
]);
const ref = useRef(null);
const [dateSelected, setDateSelected] = useState("1d");
const [lineData, setLineData] = useState([
{ value: 4 },
{ value: 14 },
{ value: 8 },
{ value: 38 },
{ value: 36 },
{ value: 28 },
]);
const months = ['1d','1w','1m','1y','All']
const changeMonthSelected = (ind: number) => {
const selectedMonth = months[ind];
setDateSelected(selectedMonth);
const months = ["1d", "1w", "1m", "1y", "All"];
const changeMonthSelected = (ind: number) => {
const selectedMonth = months[ind];
setDateSelected(selectedMonth);
// Update lineData based on the selected month
let newData: React.SetStateAction<{ value: number; }[]>;
switch (selectedMonth) {
case '1d':
newData = [
{value: 4},
{value: 14},
{value: 8},
{value: 38},
{value: 36},
{value: 28},
];
break;
case '1w':
newData = [
{value: 8},
{value: 14},
{value: 8},
{value: 38},
{value: 14},
{value: 28},
{value: 4},
];
break;
case '1m':
newData = [
{value: 10},
{value: 20},
{value: 30},
{value: 40},
{value: 50},
{value: 60},
];
break;
case '1y':
newData = [
{value: 15},
{value: 25},
{value: 35},
{value: 45},
{value: 55},
{value: 65},
];
break;
case 'All':
newData = [
{value: 5},
{value: 15},
{value: 25},
{value: 35},
{value: 45},
{value: 55},
];
break;
default:
newData = [];
}
setLineData(newData);
};
// Update lineData based on the selected month
let newData: React.SetStateAction<{ value: number }[]>;
switch (selectedMonth) {
case "1d":
newData = [
{ value: 4 },
{ value: 14 },
{ value: 8 },
{ value: 38 },
{ value: 36 },
{ value: 28 },
];
break;
case "1w":
newData = [
{ value: 8 },
{ value: 14 },
{ value: 8 },
{ value: 38 },
{ value: 14 },
{ value: 28 },
{ value: 4 },
];
break;
case "1m":
newData = [
{ value: 10 },
{ value: 20 },
{ value: 30 },
{ value: 40 },
{ value: 50 },
{ value: 60 },
];
break;
case "1y":
newData = [
{ value: 15 },
{ value: 25 },
{ value: 35 },
{ value: 45 },
{ value: 55 },
{ value: 65 },
];
break;
case "All":
newData = [
{ value: 5 },
{ value: 15 },
{ value: 25 },
{ value: 35 },
{ value: 45 },
{ value: 55 },
];
break;
default:
newData = [];
}
setLineData(newData);
};
return (
<View className="bg-gray-200 rounded-2xl p-1 h-full">
<View className=" m-2 flex-row justify-center rounded-2xl bg-white">
{months.map((item, index) => {
return (
<TouchableOpacity
key={index}
className={`w-16 h-10 flex items-center justify-center rounded-xl ${
dateSelected === item
? "bg-orange-500 border-2 border-orange-300"
: "bg-transparent "
}`}
onPress={() => changeMonthSelected(index)}>
<Text className="font-extrabold">{months[index]}</Text>
</TouchableOpacity>
);
})}
</View>
<LineChart
scrollRef={ref}
data={lineData}
areaChart
curved
initialSpacing={0}
rotateLabel
isAnimated={true}
startFillColor="orange"
curveType={CurveType.QUADRATIC}
/>
</View>
);
return (
<View className="bg-gray-200 rounded-2xl p-1 h-full">
<View className=" m-2 flex-row justify-center rounded-2xl bg-white">
{months.map((item, index) => {
return (
<TouchableOpacity
key={index}
className={`w-16 h-10 flex items-center justify-center rounded-xl ${
dateSelected === item
? "bg-orange-500 border-2 border-orange-300"
: "bg-transparent "
}`}
onPress={() => changeMonthSelected(index)}
>
<Text className="font-extrabold">{months[index]}</Text>
</TouchableOpacity>
);
})}
</View>
<LineChart
scrollRef={ref}
data={lineData}
areaChart
curved
initialSpacing={0}
rotateLabel
isAnimated={true}
startFillColor="orange"
curveType={CurveType.QUADRATIC}
/>
</View>
);
}

@ -1,51 +1,51 @@
import {FlatList, TouchableOpacity,Text, View} from "react-native";
import {useState} from "react";
import dayjs from "dayjs";
import { useState } from "react";
import { FlatList, Text, TouchableOpacity, View } from "react-native";
export default function CalendarComponent() {
const [selectedDay] = useState(dayjs().date());
const [selectedDay] = useState(dayjs().date());
const days = Array.from({ length: 7 }, (_, index) => {
const day = dayjs().add(index, "day");
return {
id: day.date(),
label: day.format("ddd"),
};
});
const days = Array.from({ length: 7 }, (_, index) => {
const day = dayjs().add(index, "day");
return {
id: day.date(),
label: day.format("ddd"),
};
});
return (
<View className="bg-transparent">
<FlatList
horizontal
data={days}
keyExtractor={(item) => item.id.toString()}
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ gap: 12 }} // Espacement entre les items
renderItem={({ item }) => (
<TouchableOpacity
className={`w-16 h-20 flex items-center justify-center rounded-xl ${
selectedDay === item.id
? "bg-orange-500 border-2 border-orange-300"
: "bg-black"
}`}
>
<Text
className={`text-sm ${
selectedDay === item.id ? "text-white" : "text-gray-400"
}`}
>
{item.label}
</Text>
<Text
className={`text-lg font-bold ${
selectedDay === item.id ? "text-white" : "text-gray-200"
}`}
>
{item.id}
</Text>
</TouchableOpacity>
)}
/>
</View>
);
return (
<View className="bg-transparent">
<FlatList
horizontal
data={days}
keyExtractor={(item) => item.id.toString()}
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ gap: 12 }} // Espacement entre les items
renderItem={({ item }) => (
<TouchableOpacity
className={`w-16 h-20 flex items-center justify-center rounded-xl ${
selectedDay === item.id
? "bg-orange-500 border-2 border-orange-300"
: "bg-black"
}`}
>
<Text
className={`text-sm ${
selectedDay === item.id ? "text-white" : "text-gray-400"
}`}
>
{item.label}
</Text>
<Text
className={`text-lg font-bold ${
selectedDay === item.id ? "text-white" : "text-gray-200"
}`}
>
{item.id}
</Text>
</TouchableOpacity>
)}
/>
</View>
);
}

@ -1,6 +1,3 @@
import React, { forwardRef } from "react";
import { View, TouchableOpacity, ViewProps } from "react-native";
import Text from "./ui/Text";
import {
AntDesign,
Entypo,
@ -8,6 +5,8 @@ import {
Ionicons,
MaterialCommunityIcons,
} from "@expo/vector-icons";
import React, { forwardRef } from "react";
import { TouchableOpacity, View, ViewProps } from "react-native";
import {
AntDesignIconNames,
CommunityIconNames,
@ -16,6 +15,7 @@ import {
FontAwesomeIconNames,
IonIconNames,
} from "./Icons";
import Text from "./ui/Text";
export type CheckBoxDirection = "row" | "col";

@ -0,0 +1,50 @@
import BackButton from "@/components/BackButton";
import Text from "@/components/ui/Text";
import React, { forwardRef } from "react";
import { Image, View, ViewProps } from "react-native";
import { Entypo } from "@expo/vector-icons";
export interface ProblemProps extends ViewProps {
picture: string;
problem: string;
description: string;
information: string;
isVisible?: boolean;
}
export default forwardRef<any, ProblemProps>(({ className, ...Props }, ref) => {
return (
<View
className={"gap-4 justify-between h-3/4" + " " + className}
{...Props}
ref={ref}
>
<View className="flex-row justify-between items-center p-4">
<BackButton />
</View>
<View className="flex-row justify-center">
<Image className="aspect-square w-3/5 h-3/5" source={Props.picture} />
</View>
<Text position="center" weight="bold" size="3xl">
{" "}
{Props.problem}{" "}
</Text>
<Text size="lg" position="center" className="text-gray-400">
{" "}
{Props.description}{" "}
</Text>
<View className="flex-row justify-center">
<View className="flex-row items-center border-2 rounded-2xl bg-red-300 border-red-600 p-4">
<Entypo name="warning" size={30} color="red" />
<Text size="lg" position="center">
{" "}
{Props.information}{" "}
</Text>
</View>
</View>
</View>
);
});

@ -5,6 +5,8 @@ export const NOT_MATCHING_PASSWORD = "Les mots de passe sont différents";
export const NOT_FOUND = "Ressource introuvable :<";
export const NO_INTERNET = "Pas de connexion à internet";
export const INTERNAL_ERROR = "Erreur interne, veuillez nous pardonner";
export const MAINTENANCE = "Le serveur est en maintenance, veuillez réessayer plus tard";
export const NOT_AUTHORIZED = "Vous n'êtes pas autorisé à accéder à cette ressource";
export const MAINTENANCE =
"Le serveur est en maintenance, veuillez réessayer plus tard";
export const NOT_AUTHORIZED =
"Vous n'êtes pas autorisé à accéder à cette ressource";
export const FEATURE_LOCKED = "Cette fonctionnalité est verrouillée";

@ -1,28 +1,29 @@
import {Image, Text, TouchableOpacity, View} from "react-native";
import {Ionicons} from "@expo/vector-icons";
import { useSession } from "@/ctx";
import React from "react";
import {useSession} from "@/ctx";
import { Image, Text, View } from "react-native";
export default function HeaderProfileComponent() {
const {session} = useSession();
const { session } = useSession();
return (
<View className="rounded-2xl overflow-hidden shadow-lg h-auto p-4">
<View className="flex-row items-center justify-between">
<View className="flex-row items-center w-full">
<Image
className="h-16 w-16 rounded-2xl"
source={require("assets/images/sigma-profile.jpeg")}
/>
return (
<View className="rounded-2xl overflow-hidden shadow-lg h-auto p-4">
<View className="flex-row items-center justify-between">
<View className="flex-row items-center w-full">
<Image className="h-16 w-16 rounded-2xl"
source={require("assets/images/sigma-profile.jpeg")}
/>
<View>
<Text className="text-gray-500 font-semibold ml-4">Prêt pour t'entrainer ?</Text>
<Text className="text-black text-4xl ml-4 mt-0.5">{session}</Text>
</View>
</View>
</View>
<View>
<Text className="text-gray-500 font-semibold ml-4">
Prêt pour t'entrainer ?
</Text>
<Text className="text-black text-4xl ml-4 mt-0.5">
{session?.name}
</Text>
</View>
</View>
);
</View>
</View>
);
}

@ -49,9 +49,7 @@ export default function LinearProgressBar({ duration = 10 }) {
<TouchableOpacity
onPress={startAnimation}
disabled={isRunning}
className={`px-4 py-2 rounded-full ${
isRunning ? "bg-orange-400" : "bg-orange-400"
}`}
className={"px-4 py-2 rounded-full bg-orange-400"}
>
<Text className="text-white font-bold">
{isRunning ? "En cours..." : "Play"}

@ -1,28 +1,20 @@
import Text from "@/components/ui/Text";
import { Workout } from "@/model/Workout";
import { useWorkoutStore } from "@/store/workoutStore";
import { AntDesign, MaterialCommunityIcons } from "@expo/vector-icons";
import { useRouter } from "expo-router";
import React from "react";
import Text from "@/components/ui/Text"
import {ImageBackground, TouchableOpacity, View} from "react-native";
import { ImageBackground, TouchableOpacity, View } from "react-native";
interface WorkoutCardComponentProps {
exercise?: Workout;
background?: String;
background?: string;
height?: number;
}
export default function WorkoutCardComponent({
exercise,
height,
background,
}: WorkoutCardComponentProps) {
const style = () => {
return `h-full rounded-2xl overflow-hidden ${background ?? "bg-black"}`;
};
const styleImage = () => {
return `w-full h-full `;
};
const router = useRouter();
return (
<View className="h-full rounded-2xl overflow-hidden bg-black">
@ -66,7 +58,10 @@ export default function WorkoutCardComponent({
<TouchableOpacity
className="absolute bottom-2 right-4 p-4 rounded-full"
onPress={() => {
router.push("/WorkoutScreen");
if (exercise) {
useWorkoutStore.getState().setWorkout(exercise);
}
router.push("/WorkoutDetailScreen");
}}
>
<AntDesign name="play" size={50} color="orange" />

@ -2,20 +2,17 @@ import LinearProgressBar from "@/components/LinearProgressBar";
import Screen from "@/components/ui/Screen";
import { Workout } from "@/model/Workout";
import { Ionicons } from "@expo/vector-icons";
import { Router, useRouter } from "expo-router";
import { router } from "expo-router";
import * as React from "react";
import { ImageBackground, Text, TouchableOpacity, View } from "react-native";
type WorkoutPresentationComponentProps = {
workout: Workout;
dataExercise: Workout[];
router: Router; // Typage précis recommandé selon ta navigation
};
export default function WorkoutPresentationComponent({
workout,
}: WorkoutPresentationComponentProps) {
const router = useRouter();
}: Readonly<WorkoutPresentationComponentProps>) {
return (
<ImageBackground
className="h-full w-full"
@ -23,11 +20,10 @@ export default function WorkoutPresentationComponent({
>
<Screen>
<View className="flex-col h-full justify-between">
{/* Bouton Retour */}
<View className="mt-5 ml-5">
<TouchableOpacity
onPress={() => {
router.replace("/ExercicesScreen");
router.back();
}}
>
<Ionicons
@ -38,10 +34,8 @@ export default function WorkoutPresentationComponent({
</TouchableOpacity>
</View>
{/* Permet de pousser le reste du contenu vers le bas */}
<View className="flex-grow" />
{/* Texte en bas */}
<View className="items-center mb-10">
<Text className="text-white bg-transparent border-2 border-white px-3 py-1 rounded-full text-2xl font-bold">
{workout.nbSeries} x {workout.nbRepetitions}
@ -52,7 +46,6 @@ export default function WorkoutPresentationComponent({
</Text>
</View>
{/* Barre de progression */}
<View className="mb-5">
<LinearProgressBar duration={workout.duration} />
</View>

@ -1,15 +0,0 @@
import React from "react";
import {FEATURE_LOCKED} from "@/components/Errors";
import Error from "@/app/(utility)/Error";
import blockedPict from "@/assets/images/Blocked.png";
export default function Blocked() {
return (
<Error
picture={blockedPict}
problem="Fonctionnalité bloquée"
description={FEATURE_LOCKED}
information="Devenez PREMIUM pour débloquer"
/>
);
}

@ -1,15 +0,0 @@
import React from "react";
import {INTERNAL_ERROR} from "@/components/Errors";
import Error from "@/app/(utility)/Error";
import internalErrorPict from "@/assets/images/InternalError.png";
export default function InternalError() {
return (
<Error
picture={internalErrorPict}
problem="Problème interne"
description={INTERNAL_ERROR}
information="Contactez le support"
/>
);
}

@ -1,15 +0,0 @@
import React from "react";
import {MAINTENANCE} from "@/components/Errors";
import Error from "@/app/(utility)/Error";
import maintenancePict from "@/assets/images/Maintenance.png";
export default function Maintenance() {
return (
<Error
picture={maintenancePict}
problem="Maintenance"
description={MAINTENANCE}
information="Revenez plus tard"
/>
);
}

@ -1,15 +0,0 @@
import React from "react";
import {NO_INTERNET} from "@/components/Errors";
import Error from "@/app/(utility)/Error";
import noInternetPict from "@/assets/images/NoInternet.png";
export default function NoInternet() {
return (
<Error
picture={noInternetPict}
problem="Pas d'internet"
description={NO_INTERNET}
information="Réessayez plus tard"
/>
);
}

@ -1,15 +0,0 @@
import React from "react";
import {NOT_AUTHORIZED} from "@/components/Errors";
import Error from "@/app/(utility)/Error";
import notAllowedPict from "@/assets/images/NotAllowed.png";
export default function NotAllowedProblem() {
return (
<Error
picture={notAllowedPict}
problem="Pas autorisé"
description={NOT_AUTHORIZED}
information="Connectez vous avec plus de privilèges"
/>
);
}

@ -1,15 +0,0 @@
import React from "react";
import {NOT_FOUND} from "@/components/Errors";
import Error from "@/app/(utility)/Error";
import notFoundPict from "@/assets/images/NotFound.png";
export default function NotFound() {
return (
<Error
picture={notFoundPict}
problem="Introuvable"
description={NOT_FOUND}
information="Status Code : 404"
/>
);
}

@ -1,5 +1,5 @@
import { UserServiceRef } from "@/api/services/service.ref";
import { useSession } from "@/ctx";
import { UserServiceStub } from "@/service/user/user.service.stub";
import { router } from "expo-router";
import React from "react";
import { View, ViewProps } from "react-native";
@ -10,8 +10,6 @@ import FormError from "./FormError";
import TextInput from "./FormInput";
import PasswordTextInput from "./SecretTextInput";
const userService = new UserServiceStub();
export default React.forwardRef<any, ViewProps>(
({ ...props }, ref): React.ReactElement => {
const [email, setEmail] = React.useState("");
@ -19,6 +17,7 @@ export default React.forwardRef<any, ViewProps>(
const [error, setError] = React.useState("");
const [isFormValid, setIsFormValid] = React.useState(true);
const { signIn } = useSession();
const userService = UserServiceRef;
const validateForm = () => {
setError("");
@ -35,13 +34,15 @@ export default React.forwardRef<any, ViewProps>(
if (isEmail(email)) {
if (password != "") {
validateForm();
const user = userService.login(email, password);
if (user) {
signIn(user);
router.replace("/HomeScreen");
} else {
const user = userService.login(email, password).catch((e) => {
invalidateForm("Email ou mot de passe incorrect");
}
});
user.then((u) => {
if (u) {
signIn(u);
router.replace("/HomeScreen");
}
});
} else {
invalidateForm(EMPTY_FIELD);
}

@ -32,7 +32,6 @@ export default React.forwardRef<any, ViewProps>(
if (confirmPassword != "") {
if (confirmPassword == password) {
validateForm();
console.log("tmp");
} else {
invalidateForm(NOT_MATCHING_PASSWORD);
}

@ -0,0 +1,33 @@
import { StringUtils } from "@/utils/string.utils";
import React, { forwardRef, useImperativeHandle, useState } from "react";
import { View } from "react-native";
import FormInput from "../form/FormInput";
import Question, { QuestionChildProps } from "./Question";
export interface NameQuestionRef {
getAnswer: () => string;
isOk: () => boolean;
}
export default forwardRef<any, QuestionChildProps>(({ ...props }, ref) => {
const [answer, setAnswer] = useState<string>();
useImperativeHandle(ref, () => ({
getAnswer: () => answer,
isOk: () => !StringUtils.IsNullOrEnptyWhiteSpace(answer ?? ""),
}));
return (
<Question question="Quel est votre nom ?" {...props} {...ref}>
<View className="gap-4">
<FormInput
beforeIcon="user"
placeholder="Ex: Michel"
label={"Prénom"}
value={answer}
onChangeText={setAnswer}
/>
</View>
</Question>
);
});

@ -1,9 +1,9 @@
import { AntDesign } from "@expo/vector-icons";
import React from "react";
import { TouchableOpacity, TouchableOpacityProps, View } from "react-native";
import CustomText from "./Text";
import { AntDesign } from "@expo/vector-icons";
import { AntDesignIconNames } from "../Icons";
import { Size } from "../Constants";
import { AntDesignIconNames } from "../Icons";
import CustomText from "./Text";
export type ButtonStyle = "default" | "outline" | "secondary";

@ -1,6 +1,6 @@
import React from "react";
import { View, ViewProps } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import React from "react";
export default React.forwardRef<any, ViewProps>(
({ children, ...props }, ref): React.ReactElement => {

@ -1,7 +1,7 @@
import React from "react";
import SegmentedControl, {
SegmentedControlProps,
} from "@react-native-segmented-control/segmented-control";
import React from "react";
export default React.forwardRef<any, SegmentedControlProps>(
(props, ref): React.ReactElement => {

@ -1,5 +1,5 @@
import React from "react";
import Slider, { SliderProps } from "@react-native-community/slider";
import React from "react";
export default React.forwardRef<any, SliderProps>(
({ ...props }, ref): React.ReactElement => {

@ -27,7 +27,7 @@ export function SessionProvider(props: Readonly<React.PropsWithChildren>) {
<AuthContext.Provider
value={{
signIn: (user: User) => {
setSessionStr(JSON.stringify(user));
setSessionStr(user.toJSON());
},
signOut: () => {
setSessionStr(null);

@ -0,0 +1,16 @@
import {TargetEnum} from "@/model/enums/Target.enum";
export interface Exercice {
id: string;
name: string;
description: string;
target: TargetEnum;
image: string;
video: string;
duration: number;
nbSeries: number;
restTime: number;
nbRepetitions: number;
weight: number;
passed: boolean;
}

@ -0,0 +1,10 @@
import {Exercice} from "@/model/Exercice";
import {CategoryEnum} from "@/model/enums/Category.enum";
export interface Session {
id: string;
name: string;
description: string;
category: CategoryEnum;
exercises: Exercice[];
}

@ -0,0 +1,8 @@
import {Session} from "@/model/Session";
export interface Training {
id: string;
name: string;
description: string;
sessions: Session[];
}

@ -6,37 +6,52 @@ import {
} from "./enums/Enums";
export class User {
private _name: string;
private _age: number | undefined;
private _height: number | undefined;
private _weight: number | undefined;
private _sexe: boolean | undefined; // true = Male, false = Female
private _logo: string | undefined;
private _nbSessionPerWeek: number | undefined;
private _goal: string | undefined;
private _healthProblems: EHealthProblem[] | undefined;
private _sport: ESport | undefined;
private _sleepLevel: ESleepLevel | undefined;
private _sportLevel: ESportLevel | undefined;
private _email: string;
private _password: string;
constructor(
name: string,
age: number | undefined,
height: number | undefined,
weight: number | undefined,
sexe: boolean | undefined,
logo: string | undefined,
nbSessionPerWeek: number | undefined,
goal: string | undefined,
healthProblems: EHealthProblem[] | undefined,
sport: ESport | undefined,
sleepLevel: ESleepLevel | undefined,
sportLevel: ESportLevel | undefined,
email: string,
password: string
) {
private _name?: string;
private _age?: number;
private _height?: number;
private _weight?: number;
private _sexe?: boolean; // true = Male, false = Female
private _logo?: string;
private _nbSessionPerWeek?: number;
private _goal?: string;
private _healthProblems?: EHealthProblem[];
private _sport?: ESport;
private _sleepLevel?: ESleepLevel;
private _sportLevel?: ESportLevel;
private _email?: string;
private _password?: string;
constructor({
name,
age,
height,
weight,
sexe,
logo,
nbSessionPerWeek,
goal,
healthProblems,
sport,
sleepLevel,
sportLevel,
email,
password,
}: {
name?: string;
age?: number;
height?: number;
weight?: number;
sexe?: boolean;
logo?: string;
nbSessionPerWeek?: number;
goal?: string;
healthProblems?: EHealthProblem[];
sport?: ESport;
sleepLevel?: ESleepLevel;
sportLevel?: ESportLevel;
email?: string;
password?: string;
} = {}) {
this._name = name;
this._age = age;
this._height = height;
@ -54,7 +69,7 @@ export class User {
}
// Getters
get name(): string {
get name(): string | undefined {
return this._name;
}
@ -82,7 +97,7 @@ export class User {
return this._nbSessionPerWeek;
}
get goals(): string | undefined {
get goal(): string | undefined {
return this._goal;
}
@ -90,7 +105,7 @@ export class User {
return this._healthProblems;
}
get sports(): ESport | undefined {
get sport(): ESport | undefined {
return this._sport;
}
@ -102,16 +117,16 @@ export class User {
return this._sportLevel;
}
get email(): string {
get email(): string | undefined {
return this._email;
}
get password(): string {
get password(): string | undefined {
return this._password;
}
// Setters
set name(value: string) {
set name(value: string | undefined) {
this._name = value;
}
@ -139,7 +154,7 @@ export class User {
this._nbSessionPerWeek = value;
}
set goals(value: string | undefined) {
set goal(value: string | undefined) {
this._goal = value;
}
@ -147,7 +162,7 @@ export class User {
this._healthProblems = value;
}
set sports(value: ESport | undefined) {
set sport(value: ESport | undefined) {
this._sport = value;
}
@ -184,21 +199,40 @@ export class User {
}
static fromJSON(json: any): User {
return new User(
json._name,
json._age,
json._height,
json._weight,
json._sexe,
json._logo,
json._nbSessionPerWeek,
json._goal,
json._healthProblems,
json._sport,
json._sleepLevel,
json._sportLevel,
json._email,
json._password
);
return new User({
name: json.name,
age: json.age,
height: json.height,
weight: json.weight,
sexe: json.sexe,
logo: json.logo,
nbSessionPerWeek: json.nbSessionPerWeek,
goal: json.goal,
healthProblems: json.healthProblems,
sport: json.sport,
sleepLevel: json.sleepLevel,
sportLevel: json.sportLevel,
email: json.email,
password: json.password,
});
}
toJSON(): any {
return JSON.stringify({
name: this.name,
age: this.age,
height: this.height,
weight: this.weight,
sexe: this.sexe,
logo: this.logo,
nbSessionPerWeek: this.nbSessionPerWeek,
goal: this.goal,
healthProblems: this.healthProblems,
sport: this.sport,
sleepLevel: this.sleepLevel,
sportLevel: this.sportLevel,
email: this.email,
password: this.password,
});
}
}

@ -1,10 +1,118 @@
export interface Workout {
id: string;
name: string;
description: string;
duration: number;
image: string;
video: string;
nbSeries: number;
nbRepetitions: number;
export class Workout {
private _id?: string;
private _name?: string;
private _description?: string;
private _duration?: number;
private _image?: string;
private _video?: string;
private _nbSeries?: number;
private _nbRepetitions?: number;
constructor({
id,
name,
description,
duration,
image,
video,
nbSeries,
nbRepetitions,
}: {
id?: string;
name?: string;
description?: string;
duration?: number;
image?: string;
video?: string;
nbSeries?: number;
nbRepetitions?: number;
} = {}) {
this._id = id;
this._name = name;
this._description = description;
this._duration = duration;
this._image = image;
this._video = video;
this._nbSeries = nbSeries;
this._nbRepetitions = nbRepetitions;
}
// Getters
get id(): string | undefined {
return this._id;
}
get name(): string | undefined {
return this._name;
}
get description(): string | undefined {
return this._description;
}
get duration(): number | undefined {
return this._duration;
}
get image(): string | undefined {
return this._image;
}
get video(): string | undefined {
return this._video;
}
get nbSeries(): number | undefined {
return this._nbSeries;
}
get nbRepetitions(): number | undefined {
return this._nbRepetitions;
}
// Setters
set id(value: string | undefined) {
this._id = value;
}
set name(value: string | undefined) {
this._name = value;
}
set description(value: string | undefined) {
this._description = value;
}
set duration(value: number | undefined) {
this._duration = value;
}
set image(value: string | undefined) {
this._image = value;
}
set video(value: string | undefined) {
this._video = value;
}
set nbSeries(value: number | undefined) {
this._nbSeries = value;
}
set nbRepetitions(value: number | undefined) {
this._nbRepetitions = value;
}
static fromJson(json: any): Workout {
return new Workout({
id: json.id,
name: json.name,
description: json.description,
duration: json.duration,
image: json.image,
video: json.video,
nbSeries: json.nbSeries,
nbRepetitions: json.nbRepetitions,
});
}
}

@ -0,0 +1,6 @@
export enum CategoryEnum {
NONE = 'none',
TRAINING = 'training',
STRETCHING = 'stretching',
WARMUP = 'warmup'
}

@ -0,0 +1,8 @@
export enum TargetEnum {
NONE = 'none',
ARM= 'arms',
BACK = 'back',
CARDIO = 'cardio',
CHEST = 'chest',
LEG = 'legs'
}

173
package-lock.json generated

@ -10,6 +10,8 @@
"dependencies": {
"@expo/html-elements": "^0.4.2",
"@expo/vector-icons": "^14.0.2",
"@gorhom/bottom-sheet": "^5.1.5",
"@react-native-async-storage/async-storage": "^2.1.2",
"@react-native-community/slider": "4.5.6",
"@react-native-segmented-control/segmented-control": "2.5.7",
"@react-navigation/bottom-tabs": "^7.2.0",
@ -26,8 +28,8 @@
"expo-haptics": "^14.0.1",
"expo-linear-gradient": "^14.0.2",
"expo-linking": "^7.0.5",
"expo-router": "~5.0.6",
"expo-secure-store": "^14.0.1",
"expo-router": "~5.0.7",
"expo-secure-store": "^14.2.3",
"expo-splash-screen": "~0.30.8",
"expo-status-bar": "^2.0.1",
"expo-symbols": "~0.4.4",
@ -38,10 +40,12 @@
"react-dom": "19.0.0",
"react-hook-form": "^7.54.2",
"react-native": "0.79.2",
"react-native-async-storage": "^0.0.1",
"react-native-element-dropdown": "^2.12.4",
"react-native-gesture-handler": "~2.24.0",
"react-native-gifted-charts": "^1.4.54",
"react-native-reanimated": "^3.16.1",
"react-native-modal": "^14.0.0-rc.1",
"react-native-reanimated": "^3.17.5",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "^4.4.0",
"react-native-svg": "15.11.2",
@ -49,13 +53,15 @@
"react-native-web": "^0.20.0",
"react-native-webview": "13.13.5",
"tailwindcss": "^3.4.17",
"validator": "^13.12.0"
"validator": "^13.12.0",
"zustand": "^5.0.4"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/jest": "^29.5.12",
"@types/react": "~19.0.10",
"@types/react-native": "^0.72.8",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^19.0.0",
"@types/validator": "^13.12.2",
"jest": "^29.2.1",
@ -2050,6 +2056,45 @@
"@babel/highlight": "^7.10.4"
}
},
"node_modules/@gorhom/bottom-sheet": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-5.1.5.tgz",
"integrity": "sha512-zn7IZMsEyVehB+hSl+VxHjk62PERyvQheBvhPzeZU+82ozDZ+qRxyrWMKy5YBFGka7DDfDgoPUUCCiaIWFyqmg==",
"license": "MIT",
"dependencies": {
"@gorhom/portal": "1.0.14",
"invariant": "^2.2.4"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-native": "*",
"react": "*",
"react-native": "*",
"react-native-gesture-handler": ">=2.16.1",
"react-native-reanimated": ">=3.16.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-native": {
"optional": true
}
}
},
"node_modules/@gorhom/portal": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/@gorhom/portal/-/portal-1.0.14.tgz",
"integrity": "sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A==",
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.1"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -2723,6 +2768,18 @@
}
}
},
"node_modules/@react-native-async-storage/async-storage": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz",
"integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==",
"license": "MIT",
"dependencies": {
"merge-options": "^3.0.4"
},
"peerDependencies": {
"react-native": "^0.0.0-0 || >=0.65 <1.0"
}
},
"node_modules/@react-native-community/slider": {
"version": "4.5.6",
"resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-4.5.6.tgz",
@ -3719,6 +3776,27 @@
"@types/react": "*"
}
},
"node_modules/@types/react-native-vector-icons": {
"version": "6.4.18",
"resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.18.tgz",
"integrity": "sha512-YGlNWb+k5laTBHd7+uZowB9DpIK3SXUneZqAiKQaj1jnJCZM0x71GDim5JCTMi4IFkhc9m8H/Gm28T5BjyivUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*",
"@types/react-native": "^0.70"
}
},
"node_modules/@types/react-native-vector-icons/node_modules/@types/react-native": {
"version": "0.70.19",
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.19.tgz",
"integrity": "sha512-c6WbyCgWTBgKKMESj/8b4w+zWcZSsCforson7UdXtXMecG3MxCinYi6ihhrHVPyUrVzORsvEzK8zg32z4pK6Sg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-native/node_modules/@react-native/virtualized-lists": {
"version": "0.72.8",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.72.8.tgz",
@ -6028,9 +6106,9 @@
}
},
"node_modules/expo-router": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/expo-router/-/expo-router-5.0.6.tgz",
"integrity": "sha512-/44G3liB7LMMDoUO+lN5TS8XvZrAhLtq7cVGoilO2QkoSBjFQfxFA9VYOVWVlu2R80tN6dM3cgsEuoA275FGQg==",
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/expo-router/-/expo-router-5.0.7.tgz",
"integrity": "sha512-NlEgRXCKtseDuIHBp87UfkvqsuVrc0MYG+zg33dopaN6wik4RkrWWxUYdNPHub0s/7qMye6zZBY4ZCrXwd/xpA==",
"license": "MIT",
"dependencies": {
"@expo/metro-runtime": "5.0.4",
@ -7045,6 +7123,15 @@
"node": ">=0.12.0"
}
},
"node_modules/is-plain-obj": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@ -8680,6 +8767,18 @@
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT"
},
"node_modules/merge-options": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
"integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
"license": "MIT",
"dependencies": {
"is-plain-obj": "^2.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@ -10480,6 +10579,24 @@
}
}
},
"node_modules/react-native-animatable": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.4.0.tgz",
"integrity": "sha512-DZwaDVWm2NBvBxf7I0wXKXLKb/TxDnkV53sWhCvei1pRyTX3MVFpkvdYBknNBqPrxYuAIlPxEp7gJOidIauUkw==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.8.1"
}
},
"node_modules/react-native-async-storage": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/react-native-async-storage/-/react-native-async-storage-0.0.1.tgz",
"integrity": "sha512-EwOIpNfwMiELMnS7HDzgyX2xO4TALK95O473Z6Kai5Il5j/03yTTErwMtvoYnA0OKUHd1R54ptmbbMukUH03Xg==",
"license": "MIT",
"peerDependencies": {
"react-native": ">=0.5.0"
}
},
"node_modules/react-native-css-interop": {
"version": "0.1.22",
"resolved": "https://registry.npmjs.org/react-native-css-interop/-/react-native-css-interop-0.1.22.tgz",
@ -10598,6 +10715,19 @@
"react-native": "*"
}
},
"node_modules/react-native-modal": {
"version": "14.0.0-rc.1",
"resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-14.0.0-rc.1.tgz",
"integrity": "sha512-v5pvGyx1FlmBzdHyPqBsYQyS2mIJhVmuXyNo5EarIzxicKhuoul6XasXMviGcXboEUT0dTYWs88/VendojPiVw==",
"license": "MIT",
"dependencies": {
"react-native-animatable": "1.4.0"
},
"peerDependencies": {
"react": "*",
"react-native": ">=0.70.0"
}
},
"node_modules/react-native-reanimated": {
"version": "3.17.5",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.17.5.tgz",
@ -13135,6 +13265,35 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zustand": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.4.tgz",
"integrity": "sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
}
}
}

@ -1,22 +1,10 @@
{
"name": "optifit",
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"test": "jest --watchAll",
"lint": "expo lint"
},
"jest": {
"preset": "jest-expo"
},
"dependencies": {
"@expo/html-elements": "^0.4.2",
"@expo/vector-icons": "^14.0.2",
"@gorhom/bottom-sheet": "^5.1.5",
"@react-native-async-storage/async-storage": "^2.1.2",
"@gorhom/bottom-sheet": "^5.1.5",
"@react-native-community/slider": "4.5.6",
"@react-native-segmented-control/segmented-control": "2.5.7",
"@react-navigation/bottom-tabs": "^7.2.0",
@ -33,8 +21,8 @@
"expo-haptics": "^14.0.1",
"expo-linear-gradient": "^14.0.2",
"expo-linking": "^7.0.5",
"expo-router": "~5.0.6",
"expo-secure-store": "^14.0.1",
"expo-router": "~5.0.7",
"expo-secure-store": "^14.2.3",
"expo-splash-screen": "~0.30.8",
"expo-status-bar": "^2.0.1",
"expo-symbols": "~0.4.4",
@ -45,10 +33,12 @@
"react-dom": "19.0.0",
"react-hook-form": "^7.54.2",
"react-native": "0.79.2",
"react-native-async-storage": "^0.0.1",
"react-native-element-dropdown": "^2.12.4",
"react-native-gesture-handler": "~2.24.0",
"react-native-gifted-charts": "^1.4.54",
"react-native-reanimated": "^3.16.1",
"react-native-modal": "^14.0.0-rc.1",
"react-native-reanimated": "^3.17.5",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "^4.4.0",
"react-native-svg": "15.11.2",
@ -56,13 +46,15 @@
"react-native-web": "^0.20.0",
"react-native-webview": "13.13.5",
"tailwindcss": "^3.4.17",
"validator": "^13.12.0"
"validator": "^13.12.0",
"zustand": "^5.0.4"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/jest": "^29.5.12",
"@types/react": "~19.0.10",
"@types/react-native": "^0.72.8",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^19.0.0",
"@types/validator": "^13.12.2",
"jest": "^29.2.1",
@ -71,5 +63,20 @@
"react-test-renderer": "19.0.0",
"typescript": "^5.3.3"
},
"private": true
"jest": {
"preset": "jest-expo"
},
"main": "expo-router/entry",
"name": "optifit",
"private": true,
"scripts": {
"android": "expo start --android",
"ios": "expo start --ios",
"lint": "expo lint",
"reset-project": "node ./scripts/reset-project.js",
"start": "expo start",
"test": "jest --watchAll",
"web": "expo start --web"
},
"version": "1.0.0"
}

@ -1,171 +0,0 @@
import { User } from "@/model/User";
import { IUserService } from "./user.service.interface";
export class UserServiceStub implements IUserService {
private readonly users: User[] = [
new User(
"Alice",
28,
165,
58,
false,
"alice.png",
3,
"Perdre du poids",
[],
"YOGA",
"GOOD",
"BEGINNER",
"test@1.com",
"password1"
),
new User(
"Bob",
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
"test@2.com",
"password2"
),
new User(
"Charlie",
22,
172,
70,
true,
"charlie.png",
2,
"Se remettre en forme",
[],
"BIKING",
"GOOD",
"BEGINNER",
"test@3.com",
"password3"
),
new User(
"Diana",
31,
160,
55,
false,
"diana.png",
5,
"Préparer un marathon",
[],
"RUNNING",
"GOOD",
"VERY_SPORTY",
"test@4.com",
"password4"
),
new User(
"Ethan",
40,
180,
88,
true,
"ethan.png",
1,
"Maintenir sa forme",
["MIGRAINE"],
"WALKING",
"BAD",
"SPORTY",
"test@5.com",
"password5"
),
new User(
"Fiona",
26,
167,
62,
false,
"fiona.png",
3,
"Renforcer le dos",
["MIGRAINE"],
"CARDIO",
"BAD",
"BEGINNER",
"test@6.com",
"password6"
),
new User(
"George",
30,
185,
90,
true,
"george.png",
4,
"Perdre du gras",
[],
"BIKING",
"TERRIBLE",
"SPORTY",
"test@7.com",
"password7"
),
new User(
"Hanna",
24,
158,
54,
false,
"hanna.png",
2,
"Se tonifier",
[],
"RANDO",
"GOOD",
"BEGINNER",
"test@8.com",
"password8"
),
new User(
"Ivan",
50,
175,
95,
true,
"ivan.png",
1,
"Rééducation",
["ARTHROSE"],
"WALKING",
"BAD",
"BEGINNER",
"test@9.com",
"password9"
),
new User(
"Julia",
29,
170,
60,
false,
"julia.png",
3,
"Rester active",
[],
"ELSE",
"GOOD",
"SPORTY",
"test@10.com",
"password10"
),
];
login(email: string, password: string): User | undefined {
return this.users.find((x) => x.email === email && x.password === password);
}
}

@ -0,0 +1,14 @@
import {Workout} from "@/model/Workout";
import {create} from "zustand/react";
import {Exercice} from "@/model/Exercice";
type State = {
selectedExercice: Exercice | null;
setExercice: (exercice: Exercice) => void;
}
export const useExerciceStore = create<State>((set) => ({
selectedExercice: null,
setExercice: (exercice: Exercice) => set({ selectedExercice: exercice })
}));

@ -0,0 +1,12 @@
import {Workout} from "@/model/Workout";
import {create} from "zustand/react";
type State = {
selectedWorkout: Workout | null;
setWorkout: (workout: Workout) => void;
};
export const useWorkoutStore = create<State>((set) => ({
selectedWorkout: null,
setWorkout: (workout: Workout) => set({ selectedWorkout: workout }),
}));

@ -0,0 +1,5 @@
export class StringUtils {
public static IsNullOrEnptyWhiteSpace(str: string | undefined): boolean {
return str === null || str === undefined || str.trim() === "";
}
}
Loading…
Cancel
Save