Compare commits

...

9 Commits

Author SHA1 Message Date
Anthony RICHARD 57dc8d5d18 Setup for API
51 minutes ago
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
3 weeks ago
Anthony RICHARD 2c8db06f32 Add error view redirection
3 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 +0,0 @@
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
},
});
export default apiClient;

@ -1,5 +0,0 @@
export const EXERCICES = {
GETALL: '/exercices',
}

@ -0,0 +1,111 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router } from "expo-router";
import { getItemAsync } from "expo-secure-store";
import apiClient from "./service.api.client";
import {
ACCESS_TOKEN_PATH,
API_URL,
CLIENT_ID,
CLIENT_SECRET,
ENDPOINTS,
REFRESH_TOKEN_PATH,
} from "./service.api.constant";
type RequestOption = {
method?: string;
body?: any;
headers?: Record<string, string>;
};
export abstract class AbstractServiceApi {
protected async request(path: string, options?: RequestOption): Promise<any> {
const token = await getItemAsync(ACCESS_TOKEN_PATH);
const headers = this.buildHeaders(token, options?.headers);
const fetchOptions: RequestInit = {
method: options?.method ?? "GET",
headers,
};
if (options?.body && fetchOptions.method !== "GET") {
fetchOptions.body = JSON.stringify(options.body);
}
const res = await fetch(`${API_URL}${path}`, fetchOptions);
return await this.checkStatus(path, res, options);
}
private buildHeaders(
token?: string | null,
headers?: Record<string, string>
): Record<string, string> {
return {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
...(headers || {}),
};
}
private async checkStatus(
path: string,
res: Response,
options?: RequestOption
): Promise<any> {
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(REFRESH_TOKEN_PATH);
if (!refreshToken) return false;
const response = await this.getRefreshTokenResponse(refreshToken);
if (!response.ok) return false;
const { accessToken, refreshToken: newRefreshToken } =
await response.json();
await AsyncStorage.setItem(ACCESS_TOKEN_PATH, accessToken);
await AsyncStorage.setItem(REFRESH_TOKEN_PATH, newRefreshToken);
apiClient.defaults.headers.common[
"Authorization"
] = `Bearer ${accessToken}`;
return true;
} catch (e) {
return false;
}
}
private async getRefreshTokenResponse(
refreshToken: string
): Promise<Response> {
return await fetch(`${API_URL}/${ENDPOINTS.REFRESH_TOKEN}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
},
body: JSON.stringify({ refresh_token: refreshToken }),
});
}
}

@ -0,0 +1,57 @@
import { ESearchExerciceFilter } from "@/enum/enum.search-filter.exercice";
import { UpdateExerciceDTO } from "../dto/dto.catalog";
import { DeleteDTO } from "../dto/dto.generic";
import { ExerciceDTO } from "../dto/dto.training";
import { ICatalogService } from "../interface/service.interface.catalog";
import { AbstractServiceApi } from "./service.api.abstract";
import { ENDPOINTS } from "./service.api.constant";
export class CatalogServiceApi
extends AbstractServiceApi
implements ICatalogService
{
async getAllExercices(): Promise<ExerciceDTO[]> {
const data = await this.request(ENDPOINTS.CATALOG);
return data as ExerciceDTO[];
}
async getExercices(spec: ESearchExerciceFilter): Promise<ExerciceDTO[]> {
const param = new URLSearchParams({ spec: spec });
const data = await this.request(`${ENDPOINTS.CATALOG}?${param}`);
return data as ExerciceDTO[];
}
async getExercice(id: string): Promise<ExerciceDTO | undefined> {
const param = new URLSearchParams({ id });
const data = await this.request(`${ENDPOINTS.CATALOG}?${param}`);
return data as ExerciceDTO;
}
async addExercice(exercice: ExerciceDTO): Promise<ExerciceDTO> {
const data = await this.request(ENDPOINTS.CATALOG, {
method: "POST",
body: exercice,
});
return data as ExerciceDTO;
}
async editExercice(
id: string,
exercice: UpdateExerciceDTO
): Promise<UpdateExerciceDTO | undefined> {
const param = new URLSearchParams({ id: id });
const data = await this.request(`${ENDPOINTS.CATALOG}?${param}`, {
method: "PUT",
body: exercice,
});
return data as UpdateExerciceDTO;
}
async deleteExercice(id: string): Promise<DeleteDTO> {
const param = new URLSearchParams({ id: id });
const data = await this.request(`${ENDPOINTS.CATALOG}?${param}`, {
method: "DELETE",
});
return data as DeleteDTO;
}
}

@ -0,0 +1,13 @@
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",
},
});
export default apiClient;

@ -0,0 +1,27 @@
export const IDP_URL =
"https://codefirst.iut.uca.fr/containers/Optifit-optifit-ef-api/api/v1";
export const API_URL =
"https://codefirst.iut.uca.fr/containers/Optifit-optifit-ef-api/api/v1";
export const CLIENT_ID = "mobile-app";
export const CLIENT_SECRET = "super-secret";
export const SCOPES =
"openid profile training.generate training.read offline_access";
export const ACCESS_TOKEN_PATH = "access-token";
export const REFRESH_TOKEN_PATH = "refresh-token";
export const ENDPOINTS = {
// User
LOGIN: "/login",
REFRESH_TOKEN: "/refresh-token",
// Exercice
CATALOG: "/exercices",
// Training
TRAINING: "/trainings",
};
export type EndpointType = keyof typeof ENDPOINTS;

@ -0,0 +1,44 @@
import { ESearchTrainingFilter } from "@/enum/enum.search-filter.training";
import {
ExerciceDTO,
SessionDTO,
TrainingDTO,
UpdateTrainingDTO,
} from "../dto/dto.training";
import { ITrainingService } from "../interface/service.interface.training";
import { AbstractServiceApi } from "./service.api.abstract";
export class TrainingServiceApi
extends AbstractServiceApi
implements ITrainingService
{
getAllTrainings(): Promise<TrainingDTO[]> {
throw new Error("Method not implemented.");
}
getTrainings(spec: ESearchTrainingFilter): Promise<TrainingDTO[]> {
throw new Error("Method not implemented.");
}
getTraining(id: string): Promise<TrainingDTO | undefined> {
throw new Error("Method not implemented.");
}
getSession(id: string): Promise<SessionDTO | undefined> {
throw new Error("Method not implemented.");
}
getExercice(id: string): Promise<ExerciceDTO | undefined> {
throw new Error("Method not implemented.");
}
createTraining(training: TrainingDTO): Promise<TrainingDTO | undefined> {
throw new Error("Method not implemented.");
}
editTraining(
training: UpdateTrainingDTO
): Promise<UpdateTrainingDTO> | undefined {
throw new Error("Method not implemented.");
}
duplicateTraining(id: string): Promise<TrainingDTO | undefined> {
throw new Error("Method not implemented.");
}
deleteTraining(id: string): Promise<TrainingDTO | undefined> {
throw new Error("Method not implemented.");
}
}

@ -0,0 +1,31 @@
import { User } from "@/model/User";
import { setItemAsync } from "expo-secure-store";
import { IUserService } from "../interface/service.interface.user";
import { AbstractServiceApi } from "./service.api.abstract";
export class UserServiceApi extends AbstractServiceApi 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;
}
}

@ -0,0 +1,19 @@
import { ETarget } from "@/enum/enum.target";
export type ExerciceTemplateDTO = {
Id: string;
Name: string;
Description: string;
Target: ETarget;
Image: string;
Video: string;
};
export type UpdateExerciceDTO = {
Id: string;
Name?: string;
Description?: string;
Target?: ETarget;
Image?: string;
Video?: string;
};

@ -0,0 +1,3 @@
export type DeleteDTO = {
Id: string;
};

@ -0,0 +1,54 @@
import { ECategory } from "@/enum/enum.category";
import { ETarget } from "@/enum/enum.target";
export type ExerciceDTO = {
Id: string;
Name: string;
Description: string;
Target: ETarget;
Image: string;
Video: string;
Duration: number;
NbSet: number;
RestTime: number;
NbRep: number;
Weight?: number;
Passed: boolean;
};
export type SessionDTO = {
Id: string;
Name: string;
Description: string;
Category: ECategory;
Exercices: ExerciceDTO[];
};
export type TrainingDTO = {
Id: string;
Name: string;
Description: string;
Sessions: SessionDTO[];
};
export type CreateSessionDTO = {
Name: string;
Description: string;
Category: ECategory;
Exercices: ExerciceDTO[];
};
export type CreateTrainingDTO = {
Name: string;
description: string;
Category: ECategory;
Exercices: ExerciceDTO[];
};
export type UpdateTrainingDTO = {
Id: string;
Name?: string;
description?: string;
Category?: ECategory;
Exercices?: ExerciceDTO[];
};

@ -0,0 +1,35 @@
import { EHealthProblem } from "@/enum/enum.health-problem";
import { ESleepLevel } from "@/enum/enum.sleep-level";
import { ESport } from "@/enum/enum.sport";
import { ESportLevel } from "@/enum/enum.sport-level";
export type UserDTO = {
Name: string;
Age: number;
Weight: number;
Height: number;
Sexe: boolean; // true = Male, false = Female
ProfilePict: string;
NbSessionPerWeek: number;
Goal: string;
SleepLevel: ESleepLevel;
SportLevel: ESportLevel;
Sport: ESport[];
HealthProblems: EHealthProblem[];
};
export type UpdateUserDTO = {
Id?: string;
Name?: string;
Age?: number;
Weight?: number;
Height?: number;
Sexe?: boolean; // true = Male, false = Female
ProfilePict?: string;
NbSessionPerWeek?: number;
Goal?: string;
SleepLevel?: ESleepLevel;
SportLevel?: ESportLevel;
Sport?: ESport[];
HealthProblems?: EHealthProblem[];
};

@ -0,0 +1,16 @@
import { ESearchExerciceFilter } from "@/enum/enum.search-filter.exercice";
import { UpdateExerciceDTO } from "../dto/dto.catalog";
import { DeleteDTO } from "../dto/dto.generic";
import { ExerciceDTO } from "../dto/dto.training";
export interface ICatalogService {
getAllExercices(): Promise<ExerciceDTO[]>;
getExercices(spec: ESearchExerciceFilter): Promise<ExerciceDTO[]>;
getExercice(id: string): Promise<ExerciceDTO | undefined>;
addExercice(exercice: ExerciceDTO): Promise<ExerciceDTO>;
editExercice(
id: string,
exercice: UpdateExerciceDTO
): Promise<UpdateExerciceDTO | undefined>;
deleteExercice(id: string): Promise<DeleteDTO>;
}

@ -0,0 +1,21 @@
import { ESearchTrainingFilter } from "@/enum/enum.search-filter.training";
import {
ExerciceDTO,
SessionDTO,
TrainingDTO,
UpdateTrainingDTO,
} from "../dto/dto.training";
export interface ITrainingService {
getAllTrainings(): Promise<TrainingDTO[]>;
getTrainings(spec: ESearchTrainingFilter): Promise<TrainingDTO[]>;
getTraining(id: string): Promise<TrainingDTO | undefined>;
getSession(id: string): Promise<SessionDTO | undefined>;
getExercice(id: string): Promise<ExerciceDTO | undefined>;
createTraining(training: TrainingDTO): Promise<TrainingDTO | undefined>;
editTraining(
training: UpdateTrainingDTO
): Promise<UpdateTrainingDTO> | undefined;
duplicateTraining(id: string): Promise<TrainingDTO | undefined>;
deleteTraining(id: string): Promise<TrainingDTO | undefined>;
}

@ -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,10 @@
import { ICatalogService } from "./interface/service.interface.catalog";
import { ITrainingService } from "./interface/service.interface.training";
import { IUserService } from "./interface/service.interface.user";
import { CatalogServiceStub } from "./stub/service.stub.catalog";
import { TrainingServiceStub } from "./stub/service.stub.training";
import { UserServiceStub } from "./stub/service.stub.user";
export const CatalogServiceRef: ICatalogService = new CatalogServiceStub();
export const UserServiceRef: IUserService = new UserServiceStub();
export const TrainingServiceRef: ITrainingService = new TrainingServiceStub();

@ -0,0 +1,120 @@
import { ESearchExerciceFilter } from "@/enum/enum.search-filter.exercice";
import { AbstractServiceApi } from "../api/service.api.abstract";
import { UpdateExerciceDTO } from "../dto/dto.catalog";
import { DeleteDTO } from "../dto/dto.generic";
import { ExerciceDTO } from "../dto/dto.training";
import { ICatalogService } from "../interface/service.interface.catalog";
export class CatalogServiceStub
extends AbstractServiceApi
implements ICatalogService
{
exercices: ExerciceDTO[] = [
{
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",
NbSet: 3,
NbRep: 15,
Target: "ARM",
RestTime: 0,
Passed: false,
},
{
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",
NbSet: 4,
NbRep: 20,
Target: "LEG",
RestTime: 0,
Passed: false,
},
{
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",
NbSet: 3,
NbRep: 1,
Target: "CARDIO",
RestTime: 0,
Passed: false,
},
{
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",
NbSet: 3,
NbRep: 12,
Target: "LEG",
RestTime: 0,
Passed: false,
},
{
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",
NbSet: 4,
NbRep: 20,
Target: "ARM",
RestTime: 0,
Passed: false,
},
];
getAllExercices(): Promise<ExerciceDTO[]> {
return Promise.resolve(this.exercices);
}
getExercices(spec: ESearchExerciceFilter): Promise<ExerciceDTO[]> {
return Promise.resolve(this.exercices);
}
getExercice(id: string): Promise<ExerciceDTO | undefined> {
return Promise.resolve(this.exercices.find((x) => x.Id === id));
}
addExercice(exercice: ExerciceDTO): Promise<ExerciceDTO> {
this.exercices.push(exercice);
return Promise.resolve(exercice);
}
editExercice(
id: string,
exercice: UpdateExerciceDTO
): Promise<UpdateExerciceDTO | undefined> {
const ex = this.exercices.find((x) => x.Id === id);
if (ex != null) {
Object.entries(exercice).forEach(([key, value]) => {
if (value !== undefined) {
// @ts-ignore : typage des clés non dynamique
ex[key] = value;
}
});
}
return Promise.resolve(ex);
}
deleteExercice(id: string): Promise<DeleteDTO> {
const dto: DeleteDTO = {
Id: id,
};
return Promise.resolve(dto);
}
}

@ -0,0 +1,284 @@
import { ESearchTrainingFilter } from "@/enum/enum.search-filter.training";
import {
ExerciceDTO,
SessionDTO,
TrainingDTO,
UpdateTrainingDTO,
} from "../dto/dto.training";
import { ITrainingService } from "../interface/service.interface.training";
export class TrainingServiceStub implements ITrainingService {
private readonly trainings: TrainingDTO[] = [
{
Id: "t1",
Name: "Full Body Starter",
Description: "Programme d'initiation complet pour débutants.",
Sessions: [
{
Id: "s1",
Name: "Jour 1 - Haut du corps",
Description: "Focus sur les bras, épaules et poitrine.",
Category: "WARM_UP",
Exercices: [
{
Id: "e1",
Name: "Pompes",
Description: "Renforce les pectoraux et les triceps.",
Target: "CHEST",
Image: "pompes.jpg",
Video: "pompes.mp4",
Duration: 10,
NbSet: 3,
RestTime: 60,
NbRep: 12,
Passed: false,
},
{
Id: "e2",
Name: "Curl haltère",
Description: "Travail des biceps.",
Target: "ARM",
Image: "curl.jpg",
Video: "curl.mp4",
Duration: 15,
NbSet: 3,
RestTime: 45,
NbRep: 10,
Weight: 8,
Passed: false,
},
],
},
{
Id: "s2",
Name: "Jour 2 - Bas du corps",
Description: "Travail des jambes et fessiers.",
Category: "TRAINING",
Exercices: [
{
Id: "e3",
Name: "Squats",
Description: "Renforce les jambes et fessiers.",
Target: "LEG",
Image: "squat.jpg",
Video: "squat.mp4",
Duration: 0,
NbSet: 4,
RestTime: 60,
NbRep: 15,
Passed: false,
},
{
Id: "e4",
Name: "Fentes",
Description: "Développe les quadriceps et ischio-jambiers.",
Target: "LEG",
Image: "fente.jpg",
Video: "fente.mp4",
Duration: 0,
NbSet: 3,
RestTime: 45,
NbRep: 12,
Passed: false,
},
],
},
],
},
{
Id: "t2",
Name: "Cardio Express",
Description: "Séances courtes et intenses pour brûler des calories.",
Sessions: [
{
Id: "s3",
Name: "HIIT 20 min",
Description: "Enchaînement rapide d'exercices.",
Category: "WARM_UP",
Exercices: [
{
Id: "e5",
Name: "Burpees",
Description: "Exercice complet pour le cardio.",
Target: "CARDIO",
Image: "burpee.jpg",
Video: "burpee.mp4",
Duration: 30,
NbSet: 4,
RestTime: 30,
NbRep: 0,
Passed: false,
},
{
Id: "e6",
Name: "Jumping Jacks",
Description: "Échauffement et endurance.",
Target: "CARDIO",
Image: "jumpingjack.jpg",
Video: "jumpingjack.mp4",
Duration: 60,
NbSet: 3,
RestTime: 20,
NbRep: 0,
Passed: false,
},
],
},
],
},
{
Id: "t3",
Name: "Renforcement Core",
Description: "Travail spécifique du gainage et des abdos.",
Sessions: [
{
Id: "s4",
Name: "Abdos explosifs",
Description: "Séance abdos intense.",
Category: "TRAINING",
Exercices: [
{
Id: "e7",
Name: "Crunchs",
Description: "Renforcement du grand droit abdominal.",
Target: "CHEST",
Image: "crunch.jpg",
Video: "crunch.mp4",
Duration: 0,
NbSet: 4,
RestTime: 30,
NbRep: 20,
Passed: false,
},
{
Id: "e8",
Name: "Planche",
Description: "Gainage isométrique.",
Target: "CHEST",
Image: "plank.jpg",
Video: "plank.mp4",
Duration: 60,
NbSet: 3,
RestTime: 30,
NbRep: 0,
Passed: false,
},
],
},
],
},
{
Id: "t4",
Name: "Force & Masse",
Description: "Programme orienté prise de muscle.",
Sessions: [
{
Id: "s5",
Name: "Pecs & Triceps",
Description: "Travail des muscles du haut du torse.",
Category: "TRAINING",
Exercices: [
{
Id: "e9",
Name: "Développé couché",
Description: "Exercice principal pour les pectoraux.",
Target: "CHEST",
Image: "benchpress.jpg",
Video: "benchpress.mp4",
Duration: 0,
NbSet: 4,
RestTime: 90,
NbRep: 8,
Weight: 40,
Passed: false,
},
{
Id: "e10",
Name: "Dips",
Description: "Renforcement des triceps.",
Target: "ARM",
Image: "dips.jpg",
Video: "dips.mp4",
Duration: 0,
NbSet: 3,
RestTime: 60,
NbRep: 10,
Passed: false,
},
],
},
],
},
{
Id: "t5",
Name: "Mobilité & Étirements",
Description: "Programme doux pour améliorer la souplesse.",
Sessions: [
{
Id: "s6",
Name: "Étirements du matin",
Description: "Routine pour bien démarrer la journée.",
Category: "STRETCHING",
Exercices: [
{
Id: "e11",
Name: "Étirement dos",
Description: "Soulage le bas du dos.",
Target: "BACK",
Image: "stretch_back.jpg",
Video: "stretch_back.mp4",
Duration: 45,
NbSet: 2,
RestTime: 30,
NbRep: 0,
Passed: false,
},
{
Id: "e12",
Name: "Étirement ischio-jambiers",
Description: "Améliore la flexibilité des jambes.",
Target: "LEG",
Image: "stretch_hamstrings.jpg",
Video: "stretch_hamstrings.mp4",
Duration: 60,
NbSet: 2,
RestTime: 30,
NbRep: 0,
Passed: false,
},
],
},
],
},
];
getAllTrainings(): Promise<TrainingDTO[]> {
return Promise.resolve(this.trainings);
}
getTrainings(spec: ESearchTrainingFilter): Promise<TrainingDTO[]> {
return Promise.resolve(this.trainings);
}
getTraining(id: string): Promise<TrainingDTO | undefined> {
return Promise.resolve(this.trainings.find((x) => x.Id === id));
}
getSession(id: string): Promise<SessionDTO | undefined> {
throw new Error("Method not implemented.");
}
getExercice(id: string): Promise<ExerciceDTO | undefined> {
throw new Error("Method not implemented.");
}
createTraining(training: TrainingDTO): Promise<TrainingDTO | undefined> {
throw new Error("Method not implemented.");
}
editTraining(
training: UpdateTrainingDTO
): Promise<UpdateTrainingDTO> | undefined {
throw new Error("Method not implemented.");
}
duplicateTraining(id: string): Promise<TrainingDTO | undefined> {
throw new Error("Method not implemented.");
}
deleteTraining(id: string): Promise<TrainingDTO | undefined> {
throw new Error("Method not implemented.");
}
}

@ -0,0 +1,167 @@
import { User } from "@/model/User";
import { IUserService } from "../interface/service.interface.user";
export class UserServiceStub 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: "FOOTBALL",
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: "HANDBALL",
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,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;
}
};

@ -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,34 +73,36 @@ 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 (NameRef.current?.isOk()) {
if (currentQuestionIndex < questions.length - 1) {
setCurrentQuestionIndex(currentQuestionIndex + 1);
} else {
Collect();
}
}
};
function Collect() {
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);
}
router.replace("/(tabs)/(home)/HomeScreen");
}

@ -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 (
<ImageBackground
className="h-full w-full"
source={require("assets/images/TrainingPresentation.png")}
>
<SafeAreaView>
<View>
<InternalError/>
<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>
<View className="flex-1 items-center justify-center">
<Text className="text-xl font-semibold">
Contenu de l'entraînement
</Text>
</View>
</SafeAreaView>
</ImageBackground>
);
}
//<Text className="m-7 font-extrabold">Welcome to Add Screen </Text>
// <Text>We will do it soon</Text>

@ -1,11 +1,13 @@
import React from "react";
import { Stack } from "expo-router";
import React from "react";
export default function RootoLayout() {
return (
<Stack screenOptions={{
<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,6 +1,6 @@
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 (
@ -9,6 +9,5 @@ export default function HelpsScreen() {
<Blocked />
</View>
</SafeAreaView>
);
}

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

@ -1,22 +1,25 @@
import { getExercices } from "@/api/services/ExercicesServices";
import { ExerciceDTO } from "@/api/service/dto/dto.training";
import { CatalogServiceRef } from "@/api/service/service.ref";
import ActivitiesComponent from "@/components/ActivitiesComponent";
import CalendarComponent from "@/components/CalendarComponent";
import WelcomeComponent from "@/components/WelcomeComponent";
import WorkoutCardComponent from "@/components/WorkoutCardComponent";
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 [exercices, setExercices] = useState<ExerciceDTO[]>([]);
const exerciceService = CatalogServiceRef;
const getRandomNumber = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
useEffect(() => {
const fetchData = async () => {
try {
const data = await getExercices();
const data = await exerciceService.getAllExercices();
setExercices(data);
} catch (err: any) {}
};
@ -47,7 +50,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={{
<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 Screen from "@/components/ui/Screen";
import { useSession } from "@/ctx";
import { router } from "expo-router";
import React from "react";
export default function ProfileScreen() {
const { signOut } = useSession();
@ -12,13 +12,16 @@ export default function ProfileScreen() {
router.replace("/log-in");
};
const goToQuiz = () => {
router.replace("/quiz");
};
return (
<SafeAreaView>
<View>
<Text className="m-7 font-extrabold">Welcome Screen </Text>
<Text>We will do it soon</Text>
</View>
<Screen className="justify-center items-center grow">
<Button className="mb-4" onPress={goToQuiz}>
Relancer le quiz
</Button>
<Button onPress={disconnect}>Se déconnecter</Button>
</SafeAreaView>
</Screen>
);
}

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

@ -1,20 +1,20 @@
import { getExercices } from "@/api/services/ExercicesServices";
import { ExerciceDTO } from "@/api/service/dto/dto.training";
import { CatalogServiceRef } from "@/api/service/service.ref";
import WorkoutCardComponent from "@/components/WorkoutCardComponent";
import { Workout } from "@/model/Workout";
import { useRouter } from "expo-router";
import React, { useEffect, useState } from "react";
import { FlatList, Text, TouchableOpacity, View } from "react-native";
import { FlatList, SafeAreaView, Text, TouchableOpacity } from "react-native";
export default function ExercicesScreen() {
const [exercices, setExercices] = useState<Workout[]>([]);
const [exercices, setExercices] = useState<ExerciceDTO[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const router = useRouter();
const exerciceService = CatalogServiceRef;
useEffect(() => {
const fetchData = async () => {
try {
const data = await getExercices();
const data = await exerciceService.getAllExercices();
setExercices(data);
} catch (err: any) {
setError(err.message);
@ -35,24 +35,16 @@ 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) },
})
}
>
<TouchableOpacity className="flex-1 m-2">
<WorkoutCardComponent exercise={item} background={"black"} />
</TouchableOpacity>
)}
/>
</View>
</SafeAreaView>
);
}

@ -0,0 +1,175 @@
import { ExerciceDTO } from "@/api/service/dto/dto.training";
import { TrainingServiceRef } from "@/api/service/service.ref";
import { useWorkoutStore } from "@/store/workoutStore";
import { Ionicons } from "@expo/vector-icons";
import React, { useEffect, useState } from "react";
import {
Dimensions,
Image,
ImageBackground,
ScrollView,
Text,
TouchableOpacity,
View,
} from "react-native";
import Modal from "react-native-modal";
const screenHeight = Dimensions.get("window").height;
export default function WorkoutScreen() {
let session = useWorkoutStore((s) => s.selectedWorkout);
const [isModalVisible, setModalVisible] = useState(false);
const [totalDuration, setTotalDuration] = useState(0);
const [totalReps, setTotalReps] = useState(0);
const [exercices, setExercices] = useState<ExerciceDTO[] | undefined>();
const trainingService = TrainingServiceRef;
useEffect(() => {
trainingService.getTraining("t1").then((x) => {
if (x) {
const sessionData = x.Sessions[0];
if (!sessionData) return;
let duration = 0;
let reps = 0;
setExercices(sessionData.Exercices);
sessionData?.Exercices.forEach((ex) => {
duration += ex.Duration;
reps += ex.NbRep;
});
setTotalDuration(duration);
setTotalReps(reps);
}
});
}, []);
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">
<Text className="text-white text-4xl font-bold">
{session?.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">
{totalDuration + " 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">
{totalReps}
</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>
{exercices?.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.Name}
</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,31 @@
import { ExerciceDTO } from "@/api/service/dto/dto.training";
import WorkoutPresentationComponent from "@/components/WorkoutPresentationComponent";
import { useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { Text, View } from "react-native";
export default function WorkoutScreen() {
const params = useLocalSearchParams();
const [workout, setWorkout] = useState<ExerciceDTO | 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,12 +15,12 @@ export default function TabBarLayout() {
if (!session) {
return <Redirect href={"/log-in"} />;
}
console.log(session);
if (!session.isQuizDone()) {
return <Redirect href={"/quiz"} />;
}
return (
<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,17 +1,17 @@
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>
<Text>
This screen {pathname} doesn't exist: {pathname}
</Text>
<Button title="Retour Home" onPress={() => router.replace("/")} />
<Text>Go to home screen!</Text>
</View>

@ -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,9 +20,7 @@ export default function RootLayout() {
}
}, [loaded]);
if (!loaded) {
return null;
}
if (!loaded) return null;
return (
<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,10 +1,10 @@
import React, { useRef, useState } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import { CurveType, LineChart } from "react-native-gifted-charts";
import React, {useRef, useState} from "react";
export default function ActivitiesComponent() {
const ref = useRef(null)
const [dateSelected, setDateSelected] = useState('1d');
const ref = useRef(null);
const [dateSelected, setDateSelected] = useState("1d");
const [lineData, setLineData] = useState([
{ value: 4 },
{ value: 14 },
@ -14,15 +14,15 @@ export default function ActivitiesComponent() {
{ value: 28 },
]);
const months = ['1d','1w','1m','1y','All']
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; }[]>;
let newData: React.SetStateAction<{ value: number }[]>;
switch (selectedMonth) {
case '1d':
case "1d":
newData = [
{ value: 4 },
{ value: 14 },
@ -32,7 +32,7 @@ export default function ActivitiesComponent() {
{ value: 28 },
];
break;
case '1w':
case "1w":
newData = [
{ value: 8 },
{ value: 14 },
@ -43,7 +43,7 @@ export default function ActivitiesComponent() {
{ value: 4 },
];
break;
case '1m':
case "1m":
newData = [
{ value: 10 },
{ value: 20 },
@ -53,7 +53,7 @@ export default function ActivitiesComponent() {
{ value: 60 },
];
break;
case '1y':
case "1y":
newData = [
{ value: 15 },
{ value: 25 },
@ -63,7 +63,7 @@ export default function ActivitiesComponent() {
{ value: 65 },
];
break;
case 'All':
case "All":
newData = [
{ value: 5 },
{ value: 15 },
@ -85,13 +85,14 @@ export default function ActivitiesComponent() {
{months.map((item, index) => {
return (
<TouchableOpacity
key={index}
key={item}
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)}>
onPress={() => changeMonthSelected(index)}
>
<Text className="font-extrabold">{months[index]}</Text>
</TouchableOpacity>
);

@ -1,6 +1,6 @@
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());

@ -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 React from "react";
import { useSession } from "@/ctx";
import React from "react";
import { Image, Text, View } from "react-native";
export default function HeaderProfileComponent() {
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"
<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>
<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 { Workout } from "@/model/Workout";
import { ExerciceDTO } from "@/api/service/dto/dto.training";
import Text from "@/components/ui/Text";
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";
interface WorkoutCardComponentProps {
exercise?: Workout;
background?: String;
exercise?: ExerciceDTO;
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 `;
};
}: Readonly<WorkoutCardComponentProps>) {
const router = useRouter();
return (
<View className="h-full rounded-2xl overflow-hidden bg-black">
@ -34,7 +26,7 @@ export default function WorkoutCardComponent({
<View className="flex-row space-x-4 h-44 items-top justify-center ">
<View className="flex-row items-top">
<Text className="text-white text-sm ml-1">
{exercise?.duration} min
{exercise?.Duration} min
</Text>
</View>
<View className="flex-row justify-center">
@ -46,27 +38,28 @@ export default function WorkoutCardComponent({
</View>
<View className="flex-row">
<Text className="text-white text-sm ml-1">
{exercise?.name} kcal
{exercise?.Name} kcal
</Text>
</View>
</View>
</View>
<View className="absolute bottom-0 left-0 right-0 p-4 bg-opacity-50">
<Text className="text-white text-lg font-bold">{exercise?.name}</Text>
<Text className="text-gray-300 text-sm">
{exercise?.nbRepetitions}
</Text>
<Text className="text-white text-lg font-bold">{exercise?.Name}</Text>
<Text className="text-gray-300 text-sm">{exercise?.NbRep}</Text>
<View className="flex-row items-center mt-2">
<Text className="text-white text-xs bg-gray-800 py-1 px-3 rounded-full">
{exercise?.name}
{exercise?.Name}
</Text>
</View>
</View>
<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" />

@ -1,21 +1,18 @@
import { ExerciceDTO } from "@/api/service/dto/dto.training";
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
workout: ExerciceDTO;
};
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,23 +34,20 @@ 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}
{workout.NbSet} x {workout.NbRep}
</Text>
<Text className="text-white text-4xl font-bold mt-2">
{workout.name}
{workout.Name}
</Text>
</View>
{/* Barre de progression */}
<View className="mb-5">
<LinearProgressBar duration={workout.duration} />
<LinearProgressBar duration={workout.Duration} />
</View>
</View>
</Screen>

@ -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/service/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);
}

@ -1,4 +1,4 @@
import { ESportLevel, SportLevels } from "@/model/enums/Enums";
import { ESportLevel, SportLevels } from "@/enum/enum.sport-level";
import React, { useImperativeHandle, useState } from "react";
import { View } from "react-native";
import Checkbox from "../CheckBox";
@ -34,7 +34,7 @@ export default React.forwardRef<ActivityQuestionRef, QuestionChildProps>(
selected = index;
}
});
return SportLevels[SportLevels.length - 1 - selected];
return SportLevels[selected];
},
}));
@ -47,11 +47,10 @@ export default React.forwardRef<ActivityQuestionRef, QuestionChildProps>(
}
const data: IData[] = [
{ label: "Athlète", antIcon: "smile-circle" },
{ label: "Très sportif", antIcon: "smileo" },
{ label: "Un peu sportif", antIcon: "meh" },
{ label: "Peu sportif", antIcon: "frowno" },
{ label: "Pas du tout sportif", antIcon: "frown" },
{ label: "Sportif", antIcon: "meh" },
{ label: "Débutant", antIcon: "frowno" },
{ label: "Pas sportif", antIcon: "frown" },
];
return (
@ -63,7 +62,7 @@ export default React.forwardRef<ActivityQuestionRef, QuestionChildProps>(
<View className="gap-2">
{data.map((item, index) => (
<Checkbox
key={index}
key={item.label}
label={item.label}
value={checkedItems[index]}
onChange={() => handleChange(index)}

@ -1,4 +1,4 @@
import { EGoal, Goals } from "@/model/enums/Enums";
import { EGoal, Goals } from "@/enum/enum.goal";
import React, {
forwardRef,
ReactElement,
@ -70,7 +70,7 @@ export default forwardRef<GoalQuestionRef, QuestionChildProps>(
<View className="gap-2">
{data.map((item, index) => (
<CheckBox
key={index}
key={item.label}
label={item.label}
value={checkedItems[index]}
onChange={() => handleChange(index)}

@ -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,4 +1,4 @@
import { ESleepLevel, SleepLevels } from "@/model/enums/Enums";
import { ESleepLevel, SleepLevels } from "@/enum/enum.sleep-level";
import React, { useImperativeHandle, useState } from "react";
import { View } from "react-native";
import Checkbox from "../CheckBox";
@ -29,7 +29,7 @@ export default React.forwardRef<SleepQuestionRef, QuestionChildProps>(
selected = index;
}
});
return SleepLevels[SleepLevels.length - 1 - selected];
return SleepLevels[selected];
},
}));
@ -41,12 +41,12 @@ export default React.forwardRef<SleepQuestionRef, QuestionChildProps>(
const data: IData[] = [
{
label: "Excellent",
label: "Excellente",
icon: "smile-circle",
endText: ">8 heures",
},
{
label: "Bien",
label: "Bonne",
icon: "smileo",
endText: "7-8 heures",
},
@ -61,7 +61,7 @@ export default React.forwardRef<SleepQuestionRef, QuestionChildProps>(
endText: "3-4 heures",
},
{
label: "Insomniaque",
label: "Terrible",
icon: "frown",
endText: "<2 heures",
},
@ -76,7 +76,7 @@ export default React.forwardRef<SleepQuestionRef, QuestionChildProps>(
<View className="gap-2">
{data.map((item, index) => (
<Checkbox
key={index}
key={item.label}
label={item.label}
value={checkedItems[index]}
onChange={() => handleChange(index)}

@ -1,4 +1,4 @@
import { ESport, Sports } from "@/model/enums/Enums";
import { ESport, Sports } from "@/enum/enum.sport";
import React, {
forwardRef,
ReactElement,
@ -68,7 +68,7 @@ export default forwardRef<SportQuestionRef, QuestionChildProps>(
<View className="flex-row justify-center flex-wrap gap-2">
{data.map((item, index) => (
<CheckBox
key={index}
key={item.label}
label={item.label}
value={checkedItems[index]}
onChange={() => handleChange(index)}

@ -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,10 @@
export const ECategory = {
WARM_UP: "WARM_UP",
TRAINING: "TRAINING",
STRETCHING: "STRETCHING",
NONE: "NONE",
} as const;
export type ECategory = keyof typeof ECategory;
export const Categories: ECategory[] = Object.values(ECategory);

@ -0,0 +1,12 @@
export const EGoal = {
WEIGHT_LOSS: "WEIGHT_LOSS",
MASS_GAIN: "MASS_GAIN",
FITNESS: "FITNESS",
IMPROVE_STAMINA: "IMPROVE_STAMINA",
KEEP_FIT: "KEEP_FIT",
NONE: "NONE",
} as const;
export type EGoal = keyof typeof EGoal;
export const Goals: EGoal[] = Object.values(EGoal);

@ -0,0 +1,9 @@
export const EHealthProblem = {
ARTHROSE: "ARTHROSE",
MIGRAINE: "MIGRAINE",
NONE: "NONE",
} as const;
export type EHealthProblem = keyof typeof EHealthProblem;
export const HealthProblems: EHealthProblem[] = Object.values(EHealthProblem);

@ -0,0 +1,11 @@
export const ESearchExerciceFilter = {
NAME: "NAME",
TARGET: "TARGET",
NONE: "NONE",
} as const;
export type ESearchExerciceFilter = keyof typeof ESearchExerciceFilter;
export const searchExerciceFilters: ESearchExerciceFilter[] = Object.values(
ESearchExerciceFilter
);

@ -0,0 +1,12 @@
export const ESearchTrainingFilter = {
NAME: "NAME",
DATE: "DATE",
DESCRIPTION: "DESCRIPTION",
NONE: "NONE",
} as const;
export type ESearchTrainingFilter = keyof typeof ESearchTrainingFilter;
export const SearchTrainingFilters: ESearchTrainingFilter[] = Object.values(
ESearchTrainingFilter
);

@ -0,0 +1,12 @@
export const ESleepLevel = {
EXCELLENT: "EXCELLENT",
GOOD: "GOOD",
BAD: "BAD",
VERY_BAD: "VERY_BAD",
TERRIBLE: "TERRIBLE",
NONE: "NONE",
} as const;
export type ESleepLevel = keyof typeof ESleepLevel;
export const SleepLevels: ESleepLevel[] = Object.values(ESleepLevel);

@ -0,0 +1,11 @@
export const ESportLevel = {
VERY_SPORTY: "VERY_SPORTY",
SPORTY: "SPORTY",
BEGINNER: "BEGINNER",
NOT_SPORTY: "NOT_SPORTY",
NONE: "NONE",
} as const;
export type ESportLevel = keyof typeof ESportLevel;
export const SportLevels: ESportLevel[] = Object.values(ESportLevel);

@ -0,0 +1,17 @@
export const Esport = {
FOOTBALL: "FOOTBALL",
BASKETBALL: "BASKETBALL",
HANDBALL: "HANDBALL",
TENNIS: "TENNIS",
WALKING: "WALKING",
RUNNING: "RUNNING",
SKATEBOARD: "SKATEBOARD",
BIKING: "BIKING",
YOGA: "YOGA",
ELSE: "ELSE",
NONE: "NONE",
} as const;
export type ESport = keyof typeof Esport;
export const Sports: ESport[] = Object.values(Esport);

@ -0,0 +1,12 @@
export const ETarget = {
CHEST: "CHEST",
LEG: "LEG",
ARM: "ARM",
CARDIO: "CARDIO",
BACK: "BACK",
NONE: "NONE",
} as const;
export type ETarget = keyof typeof ETarget;
export const Target: ETarget[] = Object.values(ETarget);

@ -1,19 +0,0 @@
export const useExercices = () => {
const [exercices, setExercices] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchExercices = async () => {
try {
const response = await apiClient.get(EXERCICES.GETALL);
setExercices(response.data);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
};
fetchExercices();
}, []);
return { exercices, loading, error };
}

@ -3,40 +3,55 @@ import {
ESleepLevel,
ESport,
ESportLevel,
} from "./enums/Enums";
} from "../enum/Enums.js";
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 +0,0 @@
export interface Workout {
id: string;
name: string;
description: string;
duration: number;
image: string;
video: string;
nbSeries: number;
nbRepetitions: number;
}

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

@ -1,42 +0,0 @@
export const Goals = [
"WEIGHT_LOSE",
"IMPROVE_MUSCLE",
"WEIGHT_GAIN",
"IMPROVE_STAMINA",
"KEEP_FIT",
] as const;
export const SportLevels = [
"NOT_SPORTY",
"BEGINNER",
"SPORTY",
"VERY_SPORTY",
"PERFORMER",
] as const;
export const Sports = [
"RUNNING",
"WALKING",
"RANDO",
"SKATEBOARD",
"BIKING",
"BASKETBALL",
"CARDIO",
"YOGA",
"ELSE",
] as const;
export const SleepLevels = [
"TERRIBLE",
"VERY_BAD",
"BAD",
"GOOD",
"EXCELLENT",
] as const;
export type EGoal = (typeof Goals)[number];
export type ESportLevel = (typeof SportLevels)[number];
export type ESport = (typeof Sports)[number];
export type ESleepLevel = (typeof SleepLevels)[number];
export type EHealthProblem = "ARTHROSE" | "MIGRAINE";

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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save