Setup for API #20
Merged
anthony.richard
merged 1 commits from Setup_For_API
into WORK_COMMUN
2 days ago
@ -1,8 +0,0 @@
|
||||
export const AUTH = {
|
||||
LOGIN: "/login",
|
||||
REFRESH_TOKEN: "/refresh-token",
|
||||
};
|
||||
|
||||
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,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.");
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { User } from "@/model/User";
|
||||
import { setItemAsync } from "expo-secure-store";
|
||||
import { AbstractService as AbstractAPIService } from "../abstract.service";
|
||||
import { IUserService } from "./user.service.interface";
|
||||
import { IUserService } from "../interface/service.interface.user";
|
||||
import { AbstractServiceApi } from "./service.api.abstract";
|
||||
|
||||
export class UserAPIService extends AbstractAPIService implements IUserService {
|
||||
export class UserServiceApi extends AbstractServiceApi implements IUserService {
|
||||
async login(email: string, password: string): Promise<User> {
|
||||
const body = new URLSearchParams({
|
||||
grant_type: "password",
|
@ -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>;
|
||||
}
|
@ -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.");
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export interface IExerciceService {
|
||||
getExercices(): Promise<any>;
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
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();
|
@ -1,156 +1,175 @@
|
||||
import React, { useState } from "react";
|
||||
import { View, Text, ImageBackground, TouchableOpacity, ScrollView, Dimensions, Image } from "react-native";
|
||||
import Modal from "react-native-modal";
|
||||
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 {useWorkoutStore} from "@/store/workoutStore";
|
||||
import {Workout} from "@/model/Workout";
|
||||
import {useRouter} from "expo-router";
|
||||
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() {
|
||||
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>
|
||||
|
||||
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-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>
|
||||
<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>
|
||||
|
||||
<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 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>
|
||||
</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,
|
||||
}}
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
// router.push("/(tabs)/(exercice)/ExercicesScreen")
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<Ionicons name="play-circle" size={30} color="#ff7a00" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -1,32 +1,31 @@
|
||||
import {router, useLocalSearchParams} from 'expo-router';
|
||||
import { View, Text, Image } from 'react-native';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {Workout} from "@/model/Workout";
|
||||
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<Workout | null>(null);
|
||||
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>;
|
||||
useEffect(() => {
|
||||
if (typeof params.workout === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(params.workout);
|
||||
setWorkout(parsed);
|
||||
} catch (error) {}
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!workout) {
|
||||
return (
|
||||
<View>
|
||||
<WorkoutPresentationComponent
|
||||
workout={workout}
|
||||
/>r
|
||||
</View>
|
||||
<Text style={{ padding: 20, margin: 30 }}>Aucune donnée reçue.</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<WorkoutPresentationComponent workout={workout} />r
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -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 };
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
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[];
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import {Session} from "@/model/Session";
|
||||
|
||||
export interface Training {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
sessions: Session[];
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
@ -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";
|
@ -1,14 +1,12 @@
|
||||
import {Workout} from "@/model/Workout";
|
||||
import {create} from "zustand/react";
|
||||
import {Exercice} from "@/model/Exercice";
|
||||
|
||||
import { ExerciceDTO } from "@/api/service/dto/dto.training";
|
||||
import { create } from "zustand/react";
|
||||
|
||||
type State = {
|
||||
selectedExercice: Exercice | null;
|
||||
setExercice: (exercice: Exercice) => void;
|
||||
}
|
||||
selectedExercice: ExerciceDTO | null;
|
||||
setExercice: (exercice: ExerciceDTO) => void;
|
||||
};
|
||||
|
||||
export const useExerciceStore = create<State>((set) => ({
|
||||
selectedExercice: null,
|
||||
setExercice: (exercice: Exercice) => set({ selectedExercice: exercice })
|
||||
}));
|
||||
selectedExercice: null,
|
||||
setExercice: (exercice: ExerciceDTO) => set({ selectedExercice: exercice }),
|
||||
}));
|
||||
|
@ -1,12 +1,12 @@
|
||||
import {Workout} from "@/model/Workout";
|
||||
import {create} from "zustand/react";
|
||||
import { SessionDTO } from "@/api/service/dto/dto.training";
|
||||
import { create } from "zustand/react";
|
||||
|
||||
type State = {
|
||||
selectedWorkout: Workout | null;
|
||||
setWorkout: (workout: Workout) => void;
|
||||
selectedWorkout: SessionDTO | null;
|
||||
setWorkout: (workout: SessionDTO) => void;
|
||||
};
|
||||
|
||||
export const useWorkoutStore = create<State>((set) => ({
|
||||
selectedWorkout: null,
|
||||
setWorkout: (workout: Workout) => set({ selectedWorkout: workout }),
|
||||
}));
|
||||
selectedWorkout: null,
|
||||
setWorkout: (workout: SessionDTO) => set({ selectedWorkout: workout }),
|
||||
}));
|
||||
|
Loading…
Reference in new issue