Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
|
1887022681 | 7 days ago |
|
7dd7b2a482 | 1 week ago |
|
019b67cce0 | 1 week ago |
![]() |
c8696f223d | 2 weeks ago |
![]() |
2c8db06f32 | 2 weeks ago |
![]() |
90b1e450e1 | 3 weeks ago |
![]() |
a3e567f291 | 4 weeks ago |
![]() |
4cba2025cf | 4 weeks ago |
@ -1,13 +1,13 @@
|
||||
import axios from 'axios';
|
||||
import axios from "axios";
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: 'https://codefirst.iut.uca.fr/containers/Optifit-optifit-ef-api/api/v1/',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
// ajoute ici les headers supplémentaires, ex. Auth
|
||||
},
|
||||
baseURL:
|
||||
"https://codefirst.iut.uca.fr/containers/Optifit-optifit-ef-api/api/v1/",
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
export default apiClient;
|
||||
|
@ -1,4 +1,7 @@
|
||||
|
||||
export const AUTH = {
|
||||
LOGIN: "/login",
|
||||
REFRESH_TOKEN: "/refresh-token",
|
||||
};
|
||||
|
||||
export const EXERCICES = {
|
||||
GETALL: '/exercices',
|
||||
|
@ -1,12 +0,0 @@
|
||||
import apiClient from "../client";
|
||||
import {EXERCICES} from "../endpoints";
|
||||
|
||||
export const getExercices = async () => {
|
||||
try {
|
||||
const response = await apiClient.get(EXERCICES.GETALL);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
@ -0,0 +1,77 @@
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { router } from "expo-router";
|
||||
import { getItemAsync } from "expo-secure-store";
|
||||
import apiClient from "../client";
|
||||
import { AUTH } from "../endpoints";
|
||||
|
||||
export abstract class AbstractService {
|
||||
protected IDP_URL =
|
||||
"https://codefirst.iut.uca.fr/containers/Optifit-optifit-ef-api/api/v1";
|
||||
protected URL =
|
||||
"https://codefirst.iut.uca.fr/containers/Optifit-optifit-ef-api/api/v1";
|
||||
|
||||
protected CLIENT_ID = "mobile-app";
|
||||
protected CLIENT_SECRET = "super-secret";
|
||||
|
||||
protected SCOPES =
|
||||
"openid profile training.generate training.read offline_access";
|
||||
|
||||
protected ACCESS_TOKEN_PATH = "access-token";
|
||||
protected REFRESH_TOKEN_PATH = "refresh-token";
|
||||
|
||||
protected async request(
|
||||
path: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<any> {
|
||||
const token = await getItemAsync(this.ACCESS_TOKEN_PATH);
|
||||
const res = await fetch(`${this.URL}${path}`, {
|
||||
...options,
|
||||
headers: {
|
||||
...(options.headers || {}),
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
switch (res.status) {
|
||||
case 401:
|
||||
await this.tryRefreshToken();
|
||||
return this.request(path, options);
|
||||
case 403:
|
||||
router.replace("/(error)/NotAllowedProblem");
|
||||
return;
|
||||
case 500:
|
||||
router.replace("/(error)/InternalErrorProblem");
|
||||
return;
|
||||
case 503:
|
||||
router.replace("/(error)/BlockedProblem");
|
||||
return;
|
||||
default:
|
||||
return await res.json();
|
||||
}
|
||||
}
|
||||
|
||||
private async tryRefreshToken(): Promise<boolean> {
|
||||
try {
|
||||
const refreshToken = await AsyncStorage.getItem(this.REFRESH_TOKEN_PATH);
|
||||
|
||||
if (!refreshToken) return false;
|
||||
|
||||
const response = await apiClient.post(AUTH.REFRESH_TOKEN, {
|
||||
refreshToken,
|
||||
});
|
||||
|
||||
const { accessToken, refreshToken: newRefreshToken } = response.data;
|
||||
|
||||
await AsyncStorage.setItem(this.ACCESS_TOKEN_PATH, accessToken);
|
||||
await AsyncStorage.setItem(this.REFRESH_TOKEN_PATH, newRefreshToken);
|
||||
|
||||
apiClient.defaults.headers.common[
|
||||
"Authorization"
|
||||
] = `Bearer ${accessToken}`;
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { EXERCICES } from "@/api/endpoints";
|
||||
import { Workout } from "@/model/Workout";
|
||||
import { AbstractService } from "../abstract.service";
|
||||
import { IExerciceService } from "./exercice.service.interface";
|
||||
|
||||
export class ExerciceAPIService
|
||||
extends AbstractService
|
||||
implements IExerciceService
|
||||
{
|
||||
async getExercices(): Promise<Workout[]> {
|
||||
const data = await this.request(EXERCICES.GETALL);
|
||||
return data.data.map((item: any) => Workout.fromJson(item));
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export interface IExerciceService {
|
||||
getExercices(): Promise<any>;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { IExerciceService } from "./exercice/exercice.service.interface";
|
||||
import { ExerciceStubService } from "./exercice/exercice.service.stub";
|
||||
import { IUserService } from "./user/user.service.interface";
|
||||
import { UserStubService } from "./user/user.service.stub";
|
||||
|
||||
export const ExerciceServiceRef: IExerciceService = new ExerciceStubService();
|
||||
export const UserServiceRef: IUserService = new UserStubService();
|
@ -0,0 +1,31 @@
|
||||
import { User } from "@/model/User";
|
||||
import { setItemAsync } from "expo-secure-store";
|
||||
import { AbstractService as AbstractAPIService } from "../abstract.service";
|
||||
import { IUserService } from "./user.service.interface";
|
||||
|
||||
export class UserAPIService extends AbstractAPIService implements IUserService {
|
||||
async login(email: string, password: string): Promise<User> {
|
||||
const body = new URLSearchParams({
|
||||
grant_type: "password",
|
||||
client_id: email,
|
||||
client_secret: this.CLIENT_SECRET,
|
||||
email,
|
||||
password,
|
||||
scope: this.SCOPES,
|
||||
});
|
||||
|
||||
const res = await fetch(`${this.IDP_URL}/connect/token`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: body.toString(),
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error(`Auth failed: ${res.status}`);
|
||||
const json = await res.json();
|
||||
|
||||
await setItemAsync(this.ACCESS_TOKEN_PATH, json.access_token);
|
||||
await setItemAsync(this.REFRESH_TOKEN_PATH, json.refresh_token);
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { User } from "@/model/User";
|
||||
|
||||
export interface IUserService {
|
||||
login(email: string, password: string): User | undefined;
|
||||
login(email: string, password: string): Promise<User>;
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
import { User } from "@/model/User";
|
||||
import { IUserService } from "./user.service.interface";
|
||||
|
||||
export class UserStubService implements IUserService {
|
||||
private readonly users: User[] = [
|
||||
new User({
|
||||
name: "Alice",
|
||||
age: 28,
|
||||
height: 165,
|
||||
weight: 58,
|
||||
sexe: false,
|
||||
logo: "alice.png",
|
||||
nbSessionPerWeek: 3,
|
||||
goal: "Perdre du poids",
|
||||
healthProblems: [],
|
||||
sport: "YOGA",
|
||||
sleepLevel: "GOOD",
|
||||
sportLevel: "BEGINNER",
|
||||
email: "test@1.com",
|
||||
password: "password1",
|
||||
}),
|
||||
new User({
|
||||
email: "test@2.com",
|
||||
password: "password2",
|
||||
}),
|
||||
new User({
|
||||
name: "Charlie",
|
||||
age: 22,
|
||||
height: 172,
|
||||
weight: 70,
|
||||
sexe: true,
|
||||
logo: "charlie.png",
|
||||
nbSessionPerWeek: 2,
|
||||
goal: "Se remettre en forme",
|
||||
healthProblems: [],
|
||||
sport: "BIKING",
|
||||
sleepLevel: "GOOD",
|
||||
sportLevel: "BEGINNER",
|
||||
email: "test@3.com",
|
||||
password: "password3",
|
||||
}),
|
||||
new User({
|
||||
name: "Diana",
|
||||
age: 31,
|
||||
height: 160,
|
||||
weight: 55,
|
||||
sexe: false,
|
||||
logo: "diana.png",
|
||||
nbSessionPerWeek: 5,
|
||||
goal: "Préparer un marathon",
|
||||
healthProblems: [],
|
||||
sport: "RUNNING",
|
||||
sleepLevel: "GOOD",
|
||||
sportLevel: "VERY_SPORTY",
|
||||
email: "test@4.com",
|
||||
password: "password4",
|
||||
}),
|
||||
new User({
|
||||
name: "Ethan",
|
||||
age: 40,
|
||||
height: 180,
|
||||
weight: 88,
|
||||
sexe: true,
|
||||
logo: "ethan.png",
|
||||
nbSessionPerWeek: 1,
|
||||
goal: "Maintenir sa forme",
|
||||
healthProblems: ["MIGRAINE"],
|
||||
sport: "WALKING",
|
||||
sleepLevel: "BAD",
|
||||
sportLevel: "SPORTY",
|
||||
email: "test@5.com",
|
||||
password: "password5",
|
||||
}),
|
||||
new User({
|
||||
name: "Fiona",
|
||||
age: 26,
|
||||
height: 167,
|
||||
weight: 62,
|
||||
sexe: false,
|
||||
logo: "fiona.png",
|
||||
nbSessionPerWeek: 3,
|
||||
goal: "Renforcer le dos",
|
||||
healthProblems: ["MIGRAINE"],
|
||||
sport: "CARDIO",
|
||||
sleepLevel: "BAD",
|
||||
sportLevel: "BEGINNER",
|
||||
email: "test@6.com",
|
||||
password: "password6",
|
||||
}),
|
||||
new User({
|
||||
name: "George",
|
||||
age: 30,
|
||||
height: 185,
|
||||
weight: 90,
|
||||
sexe: true,
|
||||
logo: "george.png",
|
||||
nbSessionPerWeek: 4,
|
||||
goal: "Perdre du gras",
|
||||
healthProblems: [],
|
||||
sport: "BIKING",
|
||||
sleepLevel: "TERRIBLE",
|
||||
sportLevel: "SPORTY",
|
||||
email: "test@7.com",
|
||||
password: "password7",
|
||||
}),
|
||||
new User({
|
||||
name: "Hanna",
|
||||
age: 24,
|
||||
height: 158,
|
||||
weight: 54,
|
||||
sexe: false,
|
||||
logo: "hanna.png",
|
||||
nbSessionPerWeek: 2,
|
||||
goal: "Se tonifier",
|
||||
healthProblems: [],
|
||||
sport: "RANDO",
|
||||
sleepLevel: "GOOD",
|
||||
sportLevel: "BEGINNER",
|
||||
email: "test@8.com",
|
||||
password: "password8",
|
||||
}),
|
||||
new User({
|
||||
name: "Ivan",
|
||||
age: 50,
|
||||
height: 175,
|
||||
weight: 95,
|
||||
sexe: true,
|
||||
logo: "ivan.png",
|
||||
nbSessionPerWeek: 1,
|
||||
goal: "Rééducation",
|
||||
healthProblems: ["ARTHROSE"],
|
||||
sport: "WALKING",
|
||||
sleepLevel: "BAD",
|
||||
sportLevel: "BEGINNER",
|
||||
email: "test@9.com",
|
||||
password: "password9",
|
||||
}),
|
||||
new User({
|
||||
name: "Julia",
|
||||
age: 29,
|
||||
height: 170,
|
||||
weight: 60,
|
||||
sexe: false,
|
||||
logo: "julia.png",
|
||||
nbSessionPerWeek: 3,
|
||||
goal: "Rester active",
|
||||
healthProblems: [],
|
||||
sport: "ELSE",
|
||||
sleepLevel: "GOOD",
|
||||
sportLevel: "SPORTY",
|
||||
email: "test@10.com",
|
||||
password: "password10",
|
||||
}),
|
||||
];
|
||||
|
||||
async login(email: string, password: string): Promise<User> {
|
||||
const user = this.users.find(
|
||||
(x) => x.email === email && x.password === password
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User not found.");
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,16 +1,33 @@
|
||||
import {SafeAreaView, View, Text} from "react-native";
|
||||
import React from "react";
|
||||
import InternalError from "@/components/error/InternalErrorProblem";
|
||||
import {
|
||||
ImageBackground,
|
||||
SafeAreaView,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
|
||||
export default function AddScreen() {
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<View>
|
||||
<InternalError/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ImageBackground
|
||||
className="h-full w-full"
|
||||
source={require("assets/images/TrainingPresentation.png")}
|
||||
>
|
||||
<SafeAreaView>
|
||||
<View className="flex-row justify-between items-center mb-4">
|
||||
<TouchableOpacity className="bg-blue-500 px-4 py-2 rounded"></TouchableOpacity>
|
||||
|
||||
<TouchableOpacity className="bg-green-500 px-4 py-2 rounded">
|
||||
<Text className="text-white font-bold">Action</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
//<Text className="m-7 font-extrabold">Welcome to Add Screen </Text>
|
||||
// <Text>We will do it soon</Text>
|
||||
<View className="flex-1 items-center justify-center">
|
||||
<Text className="text-xl font-semibold">
|
||||
Contenu de l'entraînement
|
||||
</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</ImageBackground>
|
||||
);
|
||||
}
|
||||
|
@ -1,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,19 +0,0 @@
|
||||
import React from "react";
|
||||
import {Stack} from "expo-router";
|
||||
|
||||
export default function RootoLayout() {
|
||||
return (
|
||||
<Stack screenOptions={{
|
||||
headerShown: false,
|
||||
}}
|
||||
initialRouteName={"ExercicesScreen"}
|
||||
>
|
||||
<Stack.Screen name="ExercicesScreen" />
|
||||
|
||||
<Stack.Screen name="WorkoutScreen"/>
|
||||
|
||||
</Stack>
|
||||
|
||||
);
|
||||
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
import {SafeAreaView, Text, View} from "react-native";
|
||||
import Blocked from "@/app/(error)/BlockedProblem";
|
||||
import React from "react";
|
||||
import Blocked from "@/components/error/BlockedProblem";
|
||||
import { SafeAreaView, View } from "react-native";
|
||||
|
||||
export default function HelpsScreen() {
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<View>
|
||||
<Blocked/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
|
||||
);
|
||||
}
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<View>
|
||||
<Blocked />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { Stack } from "expo-router";
|
||||
import React from "react";
|
||||
import {Stack} from "expo-router";
|
||||
import HelpsScreen from "@/app/(tabs)/(help)/HelpsScreen";
|
||||
|
||||
export default function RootoLayout() {
|
||||
return (
|
||||
<Stack screenOptions={{
|
||||
headerShown: false,
|
||||
}}>
|
||||
<Stack.Screen name="HelpsScreen" />
|
||||
</Stack>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
return (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="HelpsScreen" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
@ -1,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 />;
|
||||
}
|
||||
|
@ -0,0 +1,156 @@
|
||||
import React, { useState } from "react";
|
||||
import { View, Text, ImageBackground, TouchableOpacity, ScrollView, Dimensions, Image } from "react-native";
|
||||
import Modal from "react-native-modal";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import {useWorkoutStore} from "@/store/workoutStore";
|
||||
import {Workout} from "@/model/Workout";
|
||||
import {useRouter} from "expo-router";
|
||||
|
||||
const screenHeight = Dimensions.get("window").height;
|
||||
|
||||
export default function WorkoutScreen() {
|
||||
const router = useRouter();
|
||||
const workout = useWorkoutStore((s) => s.selectedWorkout);
|
||||
const [isModalVisible, setModalVisible] = useState(false);
|
||||
|
||||
if (!workout) return <Text>Pas de workout</Text>;
|
||||
|
||||
const exercises: any[] = [
|
||||
{
|
||||
id: "1",
|
||||
title: "Back Warmup",
|
||||
duration: "05:30",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Bent Over Row",
|
||||
duration: "10:00",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "Renegade Row",
|
||||
duration: "08:45",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-black">
|
||||
|
||||
<ImageBackground
|
||||
source={require("assets/images/TrainingPresentation.png")}
|
||||
className="flex-1 justify-between px-6 pb-10 pt-14"
|
||||
imageStyle={{ resizeMode: "cover" }}
|
||||
>
|
||||
<View className="flex-row justify-between">
|
||||
<TouchableOpacity className="bg-black/50 p-2 rounded-full">
|
||||
<Ionicons name="chevron-back" size={20} color="white" />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity className="bg-black/50 p-2 rounded-full">
|
||||
<Ionicons name="settings-outline" size={20} color="white" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<View className="flex-1 justify-end">
|
||||
<View className="items-center">
|
||||
<View className="bg-black/50 px-6 py-2 rounded-full mb-4">
|
||||
<Text className="text-white text-base">{exercises.length} Total</Text>
|
||||
</View>
|
||||
<Text className="text-white text-4xl font-bold">{workout.name}</Text>
|
||||
</View>
|
||||
|
||||
<View className="flex-row justify-between mb-16 mt-16">
|
||||
<View className="items-center flex-1">
|
||||
<Ionicons name="time-outline" size={36} color="white" />
|
||||
<Text className="text-white font-bold text-xl mt-2">{workout.duration} min</Text>
|
||||
<Text className="text-gray-300 text-lg">Time</Text>
|
||||
</View>
|
||||
|
||||
<View className="h-20 w-1 bg-white mx-2" />
|
||||
|
||||
<View className="items-center flex-1">
|
||||
<Ionicons name="flame-outline" size={36} color="white" />
|
||||
<Text className="text-white font-bold text-xl mt-2">240kcal</Text>
|
||||
<Text className="text-gray-300 text-lg">Calories</Text>
|
||||
</View>
|
||||
|
||||
<View className="h-20 w-1 bg-white mx-2" />
|
||||
|
||||
<View className="items-center flex-1">
|
||||
<Ionicons name="barbell-outline" size={36} color="white" />
|
||||
<Text className="text-white font-bold text-xl mt-2">{workout.nbRepetitions}</Text>
|
||||
<Text className="text-gray-300 text-lg">Sets</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
<View className="flex-row">
|
||||
<TouchableOpacity
|
||||
className="flex-1 bg-black/50 p-4 rounded-xl flex-row justify-center items-center"
|
||||
onPress={() => setModalVisible(true)}
|
||||
>
|
||||
<Text className="text-white mr-2">Details</Text>
|
||||
<Ionicons name="document-text-outline" size={18} color="white" />
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity className="flex-1 bg-orange-500 p-4 rounded-xl flex-row justify-center items-center">
|
||||
<Text className="text-white font-bold mr-2">Start</Text>
|
||||
<Ionicons name="timer-outline" size={18} color="white" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ImageBackground>
|
||||
|
||||
<Modal
|
||||
isVisible={isModalVisible}
|
||||
onBackdropPress={() => setModalVisible(false)}
|
||||
swipeDirection="down"
|
||||
onSwipeComplete={() => setModalVisible(false)}
|
||||
style={{
|
||||
justifyContent: "flex-end",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: screenHeight * 0.65,
|
||||
backgroundColor: "white",
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
padding: 20,
|
||||
}}
|
||||
>
|
||||
<ScrollView>
|
||||
<Text className="text-center text-gray-700 mb-4">
|
||||
Liste des exercices
|
||||
</Text>
|
||||
|
||||
{exercises.map((ex, index) => (
|
||||
<View
|
||||
key={ex.id}
|
||||
className="flex-row bg-gray-100 rounded-xl items-center p-3 mb-4"
|
||||
>
|
||||
<Image source={require("assets/images/TrainingPresentation.png")} className="w-16 h-16 rounded-md mr-3" />
|
||||
<View className="flex-1">
|
||||
<Text className="font-semibold text-black">
|
||||
{index + 1}. {ex.title}
|
||||
</Text>
|
||||
<View className="flex-row items-center mt-1">
|
||||
<Ionicons name="time-outline" size={14} color="gray" />
|
||||
<Text className="text-gray-500 text-xs ml-1">{ex.duration}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
// router.push("/(tabs)/(exercice)/ExercicesScreen")
|
||||
}}>
|
||||
<Ionicons name="play-circle" size={30} color="#ff7a00" />
|
||||
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import {router, useLocalSearchParams} from 'expo-router';
|
||||
import { View, Text, Image } from 'react-native';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {Workout} from "@/model/Workout";
|
||||
import WorkoutPresentationComponent from "@/components/WorkoutPresentationComponent";
|
||||
|
||||
export default function WorkoutScreen() {
|
||||
const params = useLocalSearchParams();
|
||||
const [workout, setWorkout] = useState<Workout | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof params.workout === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(params.workout);
|
||||
setWorkout(parsed);
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!workout) {
|
||||
return <Text style={{ padding: 20, margin: 30 }}>Aucune donnée reçue.</Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<WorkoutPresentationComponent
|
||||
workout={workout}
|
||||
/>r
|
||||
</View>
|
||||
);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import {Stack} from "expo-router";
|
||||
|
||||
export default function RootoLayout() {
|
||||
return (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
}}
|
||||
initialRouteName="ExercicesScreen"
|
||||
>
|
||||
<Stack.Screen name="ExercicesScreen" />
|
||||
<Stack.Screen name="WorkoutScreen" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
@ -1,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>
|
||||
);
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 354 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 278 KiB |
Before Width: | Height: | Size: 278 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,113 +1,114 @@
|
||||
import {Text, TouchableOpacity, View} from "react-native";
|
||||
import {CurveType, LineChart} from "react-native-gifted-charts";
|
||||
import React, {useRef, useState} from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Text, TouchableOpacity, View } from "react-native";
|
||||
import { CurveType, LineChart } from "react-native-gifted-charts";
|
||||
|
||||
export default function ActivitiesComponent() {
|
||||
const ref = useRef(null)
|
||||
const [dateSelected, setDateSelected] = useState('1d');
|
||||
const [lineData, setLineData] = useState([
|
||||
{value: 4},
|
||||
{value: 14},
|
||||
{value: 8},
|
||||
{value: 38},
|
||||
{value: 36},
|
||||
{value: 28},
|
||||
]);
|
||||
const ref = useRef(null);
|
||||
const [dateSelected, setDateSelected] = useState("1d");
|
||||
const [lineData, setLineData] = useState([
|
||||
{ value: 4 },
|
||||
{ value: 14 },
|
||||
{ value: 8 },
|
||||
{ value: 38 },
|
||||
{ value: 36 },
|
||||
{ value: 28 },
|
||||
]);
|
||||
|
||||
const months = ['1d','1w','1m','1y','All']
|
||||
const changeMonthSelected = (ind: number) => {
|
||||
const selectedMonth = months[ind];
|
||||
setDateSelected(selectedMonth);
|
||||
const months = ["1d", "1w", "1m", "1y", "All"];
|
||||
const changeMonthSelected = (ind: number) => {
|
||||
const selectedMonth = months[ind];
|
||||
setDateSelected(selectedMonth);
|
||||
|
||||
// Update lineData based on the selected month
|
||||
let newData: React.SetStateAction<{ value: number; }[]>;
|
||||
switch (selectedMonth) {
|
||||
case '1d':
|
||||
newData = [
|
||||
{value: 4},
|
||||
{value: 14},
|
||||
{value: 8},
|
||||
{value: 38},
|
||||
{value: 36},
|
||||
{value: 28},
|
||||
];
|
||||
break;
|
||||
case '1w':
|
||||
newData = [
|
||||
{value: 8},
|
||||
{value: 14},
|
||||
{value: 8},
|
||||
{value: 38},
|
||||
{value: 14},
|
||||
{value: 28},
|
||||
{value: 4},
|
||||
];
|
||||
break;
|
||||
case '1m':
|
||||
newData = [
|
||||
{value: 10},
|
||||
{value: 20},
|
||||
{value: 30},
|
||||
{value: 40},
|
||||
{value: 50},
|
||||
{value: 60},
|
||||
];
|
||||
break;
|
||||
case '1y':
|
||||
newData = [
|
||||
{value: 15},
|
||||
{value: 25},
|
||||
{value: 35},
|
||||
{value: 45},
|
||||
{value: 55},
|
||||
{value: 65},
|
||||
];
|
||||
break;
|
||||
case 'All':
|
||||
newData = [
|
||||
{value: 5},
|
||||
{value: 15},
|
||||
{value: 25},
|
||||
{value: 35},
|
||||
{value: 45},
|
||||
{value: 55},
|
||||
];
|
||||
break;
|
||||
default:
|
||||
newData = [];
|
||||
}
|
||||
setLineData(newData);
|
||||
};
|
||||
// Update lineData based on the selected month
|
||||
let newData: React.SetStateAction<{ value: number }[]>;
|
||||
switch (selectedMonth) {
|
||||
case "1d":
|
||||
newData = [
|
||||
{ value: 4 },
|
||||
{ value: 14 },
|
||||
{ value: 8 },
|
||||
{ value: 38 },
|
||||
{ value: 36 },
|
||||
{ value: 28 },
|
||||
];
|
||||
break;
|
||||
case "1w":
|
||||
newData = [
|
||||
{ value: 8 },
|
||||
{ value: 14 },
|
||||
{ value: 8 },
|
||||
{ value: 38 },
|
||||
{ value: 14 },
|
||||
{ value: 28 },
|
||||
{ value: 4 },
|
||||
];
|
||||
break;
|
||||
case "1m":
|
||||
newData = [
|
||||
{ value: 10 },
|
||||
{ value: 20 },
|
||||
{ value: 30 },
|
||||
{ value: 40 },
|
||||
{ value: 50 },
|
||||
{ value: 60 },
|
||||
];
|
||||
break;
|
||||
case "1y":
|
||||
newData = [
|
||||
{ value: 15 },
|
||||
{ value: 25 },
|
||||
{ value: 35 },
|
||||
{ value: 45 },
|
||||
{ value: 55 },
|
||||
{ value: 65 },
|
||||
];
|
||||
break;
|
||||
case "All":
|
||||
newData = [
|
||||
{ value: 5 },
|
||||
{ value: 15 },
|
||||
{ value: 25 },
|
||||
{ value: 35 },
|
||||
{ value: 45 },
|
||||
{ value: 55 },
|
||||
];
|
||||
break;
|
||||
default:
|
||||
newData = [];
|
||||
}
|
||||
setLineData(newData);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="bg-gray-200 rounded-2xl p-1 h-full">
|
||||
<View className=" m-2 flex-row justify-center rounded-2xl bg-white">
|
||||
{months.map((item, index) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={index}
|
||||
className={`w-16 h-10 flex items-center justify-center rounded-xl ${
|
||||
dateSelected === item
|
||||
? "bg-orange-500 border-2 border-orange-300"
|
||||
: "bg-transparent "
|
||||
}`}
|
||||
onPress={() => changeMonthSelected(index)}>
|
||||
<Text className="font-extrabold">{months[index]}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<LineChart
|
||||
scrollRef={ref}
|
||||
data={lineData}
|
||||
areaChart
|
||||
curved
|
||||
initialSpacing={0}
|
||||
rotateLabel
|
||||
isAnimated={true}
|
||||
startFillColor="orange"
|
||||
curveType={CurveType.QUADRATIC}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View className="bg-gray-200 rounded-2xl p-1 h-full">
|
||||
<View className=" m-2 flex-row justify-center rounded-2xl bg-white">
|
||||
{months.map((item, index) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={index}
|
||||
className={`w-16 h-10 flex items-center justify-center rounded-xl ${
|
||||
dateSelected === item
|
||||
? "bg-orange-500 border-2 border-orange-300"
|
||||
: "bg-transparent "
|
||||
}`}
|
||||
onPress={() => changeMonthSelected(index)}
|
||||
>
|
||||
<Text className="font-extrabold">{months[index]}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<LineChart
|
||||
scrollRef={ref}
|
||||
data={lineData}
|
||||
areaChart
|
||||
curved
|
||||
initialSpacing={0}
|
||||
rotateLabel
|
||||
isAnimated={true}
|
||||
startFillColor="orange"
|
||||
curveType={CurveType.QUADRATIC}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -1,51 +1,51 @@
|
||||
import {FlatList, TouchableOpacity,Text, View} from "react-native";
|
||||
import {useState} from "react";
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
import { FlatList, Text, TouchableOpacity, View } from "react-native";
|
||||
|
||||
export default function CalendarComponent() {
|
||||
const [selectedDay] = useState(dayjs().date());
|
||||
const [selectedDay] = useState(dayjs().date());
|
||||
|
||||
const days = Array.from({ length: 7 }, (_, index) => {
|
||||
const day = dayjs().add(index, "day");
|
||||
return {
|
||||
id: day.date(),
|
||||
label: day.format("ddd"),
|
||||
};
|
||||
});
|
||||
const days = Array.from({ length: 7 }, (_, index) => {
|
||||
const day = dayjs().add(index, "day");
|
||||
return {
|
||||
id: day.date(),
|
||||
label: day.format("ddd"),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<View className="bg-transparent">
|
||||
<FlatList
|
||||
horizontal
|
||||
data={days}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{ gap: 12 }} // Espacement entre les items
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
className={`w-16 h-20 flex items-center justify-center rounded-xl ${
|
||||
selectedDay === item.id
|
||||
? "bg-orange-500 border-2 border-orange-300"
|
||||
: "bg-black"
|
||||
}`}
|
||||
>
|
||||
<Text
|
||||
className={`text-sm ${
|
||||
selectedDay === item.id ? "text-white" : "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</Text>
|
||||
<Text
|
||||
className={`text-lg font-bold ${
|
||||
selectedDay === item.id ? "text-white" : "text-gray-200"
|
||||
}`}
|
||||
>
|
||||
{item.id}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View className="bg-transparent">
|
||||
<FlatList
|
||||
horizontal
|
||||
data={days}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{ gap: 12 }} // Espacement entre les items
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
className={`w-16 h-20 flex items-center justify-center rounded-xl ${
|
||||
selectedDay === item.id
|
||||
? "bg-orange-500 border-2 border-orange-300"
|
||||
: "bg-black"
|
||||
}`}
|
||||
>
|
||||
<Text
|
||||
className={`text-sm ${
|
||||
selectedDay === item.id ? "text-white" : "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</Text>
|
||||
<Text
|
||||
className={`text-lg font-bold ${
|
||||
selectedDay === item.id ? "text-white" : "text-gray-200"
|
||||
}`}
|
||||
>
|
||||
{item.id}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
@ -1,28 +1,29 @@
|
||||
import {Image, Text, TouchableOpacity, View} from "react-native";
|
||||
import {Ionicons} from "@expo/vector-icons";
|
||||
import { useSession } from "@/ctx";
|
||||
import React from "react";
|
||||
import {useSession} from "@/ctx";
|
||||
import { Image, Text, View } from "react-native";
|
||||
|
||||
export default function HeaderProfileComponent() {
|
||||
const {session} = useSession();
|
||||
const { session } = useSession();
|
||||
|
||||
return (
|
||||
<View className="rounded-2xl overflow-hidden shadow-lg h-auto p-4">
|
||||
<View className="flex-row items-center justify-between">
|
||||
<View className="flex-row items-center w-full">
|
||||
<Image
|
||||
className="h-16 w-16 rounded-2xl"
|
||||
source={require("assets/images/sigma-profile.jpeg")}
|
||||
/>
|
||||
|
||||
return (
|
||||
<View className="rounded-2xl overflow-hidden shadow-lg h-auto p-4">
|
||||
|
||||
<View className="flex-row items-center justify-between">
|
||||
<View className="flex-row items-center w-full">
|
||||
<Image className="h-16 w-16 rounded-2xl"
|
||||
source={require("assets/images/sigma-profile.jpeg")}
|
||||
/>
|
||||
|
||||
<View>
|
||||
<Text className="text-gray-500 font-semibold ml-4">Prêt pour t'entrainer ?</Text>
|
||||
<Text className="text-black text-4xl ml-4 mt-0.5">{session}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Text className="text-gray-500 font-semibold ml-4">
|
||||
Prêt pour t'entrainer ?
|
||||
</Text>
|
||||
<Text className="text-black text-4xl ml-4 mt-0.5">
|
||||
{session?.name}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
import {TargetEnum} from "@/model/enums/Target.enum";
|
||||
|
||||
export interface Exercice {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
target: TargetEnum;
|
||||
image: string;
|
||||
video: string;
|
||||
duration: number;
|
||||
nbSeries: number;
|
||||
restTime: number;
|
||||
nbRepetitions: number;
|
||||
weight: number;
|
||||
passed: boolean;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import {Exercice} from "@/model/Exercice";
|
||||
import {CategoryEnum} from "@/model/enums/Category.enum";
|
||||
|
||||
export interface Session {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
category: CategoryEnum;
|
||||
exercises: Exercice[];
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import {Session} from "@/model/Session";
|
||||
|
||||
export interface Training {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
sessions: Session[];
|
||||
}
|
@ -1,10 +1,118 @@
|
||||
export interface Workout {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
duration: number;
|
||||
image: string;
|
||||
video: string;
|
||||
nbSeries: number;
|
||||
nbRepetitions: number;
|
||||
export class Workout {
|
||||
private _id?: string;
|
||||
private _name?: string;
|
||||
private _description?: string;
|
||||
private _duration?: number;
|
||||
private _image?: string;
|
||||
private _video?: string;
|
||||
private _nbSeries?: number;
|
||||
private _nbRepetitions?: number;
|
||||
|
||||
constructor({
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
duration,
|
||||
image,
|
||||
video,
|
||||
nbSeries,
|
||||
nbRepetitions,
|
||||
}: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
image?: string;
|
||||
video?: string;
|
||||
nbSeries?: number;
|
||||
nbRepetitions?: number;
|
||||
} = {}) {
|
||||
this._id = id;
|
||||
this._name = name;
|
||||
this._description = description;
|
||||
this._duration = duration;
|
||||
this._image = image;
|
||||
this._video = video;
|
||||
this._nbSeries = nbSeries;
|
||||
this._nbRepetitions = nbRepetitions;
|
||||
}
|
||||
|
||||
// Getters
|
||||
get id(): string | undefined {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get name(): string | undefined {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
get description(): string | undefined {
|
||||
return this._description;
|
||||
}
|
||||
|
||||
get duration(): number | undefined {
|
||||
return this._duration;
|
||||
}
|
||||
|
||||
get image(): string | undefined {
|
||||
return this._image;
|
||||
}
|
||||
|
||||
get video(): string | undefined {
|
||||
return this._video;
|
||||
}
|
||||
|
||||
get nbSeries(): number | undefined {
|
||||
return this._nbSeries;
|
||||
}
|
||||
|
||||
get nbRepetitions(): number | undefined {
|
||||
return this._nbRepetitions;
|
||||
}
|
||||
|
||||
// Setters
|
||||
set id(value: string | undefined) {
|
||||
this._id = value;
|
||||
}
|
||||
|
||||
set name(value: string | undefined) {
|
||||
this._name = value;
|
||||
}
|
||||
|
||||
set description(value: string | undefined) {
|
||||
this._description = value;
|
||||
}
|
||||
|
||||
set duration(value: number | undefined) {
|
||||
this._duration = value;
|
||||
}
|
||||
|
||||
set image(value: string | undefined) {
|
||||
this._image = value;
|
||||
}
|
||||
|
||||
set video(value: string | undefined) {
|
||||
this._video = value;
|
||||
}
|
||||
|
||||
set nbSeries(value: number | undefined) {
|
||||
this._nbSeries = value;
|
||||
}
|
||||
|
||||
set nbRepetitions(value: number | undefined) {
|
||||
this._nbRepetitions = value;
|
||||
}
|
||||
|
||||
static fromJson(json: any): Workout {
|
||||
return new Workout({
|
||||
id: json.id,
|
||||
name: json.name,
|
||||
description: json.description,
|
||||
duration: json.duration,
|
||||
image: json.image,
|
||||
video: json.video,
|
||||
nbSeries: json.nbSeries,
|
||||
nbRepetitions: json.nbRepetitions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
export enum CategoryEnum {
|
||||
NONE = 'none',
|
||||
TRAINING = 'training',
|
||||
STRETCHING = 'stretching',
|
||||
WARMUP = 'warmup'
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
export enum TargetEnum {
|
||||
NONE = 'none',
|
||||
ARM= 'arms',
|
||||
BACK = 'back',
|
||||
CARDIO = 'cardio',
|
||||
CHEST = 'chest',
|
||||
LEG = 'legs'
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
import { User } from "@/model/User";
|
||||
import { IUserService } from "./user.service.interface";
|
||||
|
||||
export class UserServiceStub implements IUserService {
|
||||
private readonly users: User[] = [
|
||||
new User(
|
||||
"Alice",
|
||||
28,
|
||||
165,
|
||||
58,
|
||||
false,
|
||||
"alice.png",
|
||||
3,
|
||||
"Perdre du poids",
|
||||
[],
|
||||
"YOGA",
|
||||
"GOOD",
|
||||
"BEGINNER",
|
||||
"test@1.com",
|
||||
"password1"
|
||||
),
|
||||
new User(
|
||||
"Bob",
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
"test@2.com",
|
||||
"password2"
|
||||
),
|
||||
new User(
|
||||
"Charlie",
|
||||
22,
|
||||
172,
|
||||
70,
|
||||
true,
|
||||
"charlie.png",
|
||||
2,
|
||||
"Se remettre en forme",
|
||||
[],
|
||||
"BIKING",
|
||||
"GOOD",
|
||||
"BEGINNER",
|
||||
"test@3.com",
|
||||
"password3"
|
||||
),
|
||||
new User(
|
||||
"Diana",
|
||||
31,
|
||||
160,
|
||||
55,
|
||||
false,
|
||||
"diana.png",
|
||||
5,
|
||||
"Préparer un marathon",
|
||||
[],
|
||||
"RUNNING",
|
||||
"GOOD",
|
||||
"VERY_SPORTY",
|
||||
"test@4.com",
|
||||
"password4"
|
||||
),
|
||||
new User(
|
||||
"Ethan",
|
||||
40,
|
||||
180,
|
||||
88,
|
||||
true,
|
||||
"ethan.png",
|
||||
1,
|
||||
"Maintenir sa forme",
|
||||
["MIGRAINE"],
|
||||
"WALKING",
|
||||
"BAD",
|
||||
"SPORTY",
|
||||
"test@5.com",
|
||||
"password5"
|
||||
),
|
||||
new User(
|
||||
"Fiona",
|
||||
26,
|
||||
167,
|
||||
62,
|
||||
false,
|
||||
"fiona.png",
|
||||
3,
|
||||
"Renforcer le dos",
|
||||
["MIGRAINE"],
|
||||
"CARDIO",
|
||||
"BAD",
|
||||
"BEGINNER",
|
||||
"test@6.com",
|
||||
"password6"
|
||||
),
|
||||
new User(
|
||||
"George",
|
||||
30,
|
||||
185,
|
||||
90,
|
||||
true,
|
||||
"george.png",
|
||||
4,
|
||||
"Perdre du gras",
|
||||
[],
|
||||
"BIKING",
|
||||
"TERRIBLE",
|
||||
"SPORTY",
|
||||
"test@7.com",
|
||||
"password7"
|
||||
),
|
||||
new User(
|
||||
"Hanna",
|
||||
24,
|
||||
158,
|
||||
54,
|
||||
false,
|
||||
"hanna.png",
|
||||
2,
|
||||
"Se tonifier",
|
||||
[],
|
||||
"RANDO",
|
||||
"GOOD",
|
||||
"BEGINNER",
|
||||
"test@8.com",
|
||||
"password8"
|
||||
),
|
||||
new User(
|
||||
"Ivan",
|
||||
50,
|
||||
175,
|
||||
95,
|
||||
true,
|
||||
"ivan.png",
|
||||
1,
|
||||
"Rééducation",
|
||||
["ARTHROSE"],
|
||||
"WALKING",
|
||||
"BAD",
|
||||
"BEGINNER",
|
||||
"test@9.com",
|
||||
"password9"
|
||||
),
|
||||
new User(
|
||||
"Julia",
|
||||
29,
|
||||
170,
|
||||
60,
|
||||
false,
|
||||
"julia.png",
|
||||
3,
|
||||
"Rester active",
|
||||
[],
|
||||
"ELSE",
|
||||
"GOOD",
|
||||
"SPORTY",
|
||||
"test@10.com",
|
||||
"password10"
|
||||
),
|
||||
];
|
||||
|
||||
login(email: string, password: string): User | undefined {
|
||||
return this.users.find((x) => x.email === email && x.password === password);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import {Workout} from "@/model/Workout";
|
||||
import {create} from "zustand/react";
|
||||
import {Exercice} from "@/model/Exercice";
|
||||
|
||||
|
||||
type State = {
|
||||
selectedExercice: Exercice | null;
|
||||
setExercice: (exercice: Exercice) => void;
|
||||
}
|
||||
|
||||
export const useExerciceStore = create<State>((set) => ({
|
||||
selectedExercice: null,
|
||||
setExercice: (exercice: Exercice) => set({ selectedExercice: exercice })
|
||||
}));
|
@ -0,0 +1,12 @@
|
||||
import {Workout} from "@/model/Workout";
|
||||
import {create} from "zustand/react";
|
||||
|
||||
type State = {
|
||||
selectedWorkout: Workout | null;
|
||||
setWorkout: (workout: Workout) => void;
|
||||
};
|
||||
|
||||
export const useWorkoutStore = create<State>((set) => ({
|
||||
selectedWorkout: null,
|
||||
setWorkout: (workout: Workout) => set({ selectedWorkout: workout }),
|
||||
}));
|
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1,5 @@
|
||||
export class StringUtils {
|
||||
public static IsNullOrEnptyWhiteSpace(str: string | undefined): boolean {
|
||||
return str === null || str === undefined || str.trim() === "";
|
||||
}
|
||||
}
|
Loading…
Reference in new issue