From 1f751581a82c6145a592fd12dd7b561a2a877e83 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 15 Nov 2024 16:32:17 +0100 Subject: [PATCH 001/283] refactor: ajout du dossier Home --- routes/StackNavigator.tsx | 2 +- screens/{ => Home}/Home.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename screens/{ => Home}/Home.tsx (95%) diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index e6d9420..28b5292 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -1,6 +1,6 @@ import {createNativeStackNavigator} from "@react-navigation/native-stack"; import {NavigationContainer} from "@react-navigation/native"; -import Home from "../screens/Home"; +import Home from "../screens/Home/Home"; import Question from "../screens/Question/Question"; import CreationQuiz from "../screens/CreationQuiz/CreationQuiz"; import FinQuiz from "../screens/FinQuiz/FinQuiz"; diff --git a/screens/Home.tsx b/screens/Home/Home.tsx similarity index 95% rename from screens/Home.tsx rename to screens/Home/Home.tsx index e682b25..83ff7a9 100644 --- a/screens/Home.tsx +++ b/screens/Home/Home.tsx @@ -1,9 +1,9 @@ import {StyleSheet, View, Text, Modal, TextInput, Image} from "react-native"; -import DefaultButton from "../components/DefaultButton"; -import {TextsStyles} from "../styles/TextsStyles"; +import DefaultButton from "../../components/DefaultButton"; +import {TextsStyles} from "../../styles/TextsStyles"; import {useTranslation} from "react-i18next"; import { useState } from "react"; -import { ButtonsStyles } from "../styles/ButtonsStyles"; +import { ButtonsStyles } from "../../styles/ButtonsStyles"; interface Props { navigation: any; -- GitLab From 40eae390fc26c5add850c41ca3fa26e9473c75ca Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 15 Nov 2024 16:38:53 +0100 Subject: [PATCH 002/283] trad: ajout de la traduction de la page Home --- screens/Home/Home.tsx | 4 ++-- translation/languages/en.json | 4 +++- translation/languages/fr.json | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/screens/Home/Home.tsx b/screens/Home/Home.tsx index 83ff7a9..a4ac3f2 100644 --- a/screens/Home/Home.tsx +++ b/screens/Home/Home.tsx @@ -46,8 +46,8 @@ export default function Home({navigation}: Props) { }}> <View style={styles.centeredView}> <View style={styles.modalView}> - <Text style={TextsStyles.subtitleText}>Rejoindre Quiz</Text> - <TextInput placeholder="code de la partie" style={styles.codeTextInput} keyboardType="numeric"/> + <Text style={TextsStyles.subtitleText}>{t.("app.screens.home.joinQuiz")}</Text> + <TextInput placeholder={t("app.screens.home.codeQuiz")} style={styles.codeTextInput} keyboardType="numeric"/> <DefaultButton text="V" handleButtonPressed={handleButtonValidatePressed} buttonStyle={styles.button}></DefaultButton> </View> </View> diff --git a/translation/languages/en.json b/translation/languages/en.json index cd0578e..eb76cb0 100644 --- a/translation/languages/en.json +++ b/translation/languages/en.json @@ -4,7 +4,9 @@ "home": { "welcome": "Welcome to Vili", "play": "Play", - "createQuiz": "Create a Quiz" + "createQuiz": "Create a Quiz", + "joinQuiz": "Join a Quiz", + "codeQuiz": "Quiz Code" } } } diff --git a/translation/languages/fr.json b/translation/languages/fr.json index 3cd4cfe..8a93a2f 100644 --- a/translation/languages/fr.json +++ b/translation/languages/fr.json @@ -4,7 +4,9 @@ "home": { "welcome": "Bienvenue sur Vili", "play": "Jouer", - "createQuiz": "Créer un Quizz" + "createQuiz": "Créer un Quizz", + "joinQuiz": "Rejoindre un Quizz", + "codeQuiz": "Code de la partie" } } } -- GitLab From db0ceb187848d6dd61ea3aaac15645e50aec374c Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 19 Nov 2024 21:28:29 +0100 Subject: [PATCH 003/283] feat: mise a jour expo --- app.json | 1 + package-lock.json | 5957 ++++++++++++--------------------------------- package.json | 16 +- 3 files changed, 1619 insertions(+), 4355 deletions(-) diff --git a/app.json b/app.json index 74d20a7..0363479 100644 --- a/app.json +++ b/app.json @@ -3,6 +3,7 @@ "name": "vili", "slug": "vili", "version": "1.0.0", + "newArchEnabled": true, "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", diff --git a/package-lock.json b/package-lock.json index 02eb398..393cf26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,27 +8,41 @@ "name": "vili", "version": "1.0.0", "dependencies": { - "@react-native-picker/picker": "2.7.5", + "@react-native-picker/picker": "^2.9.0", "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", - "expo": "~51.0.39", - "expo-status-bar": "~1.12.1", + "expo": "^52.0.8", + "expo-status-bar": "~2.0.0", "he": "^1.2.0", "i18n-js": "^4.5.0", "i18next": "^23.16.5", - "react": "18.2.0", + "react": "^18.3.1", "react-i18next": "^15.1.1", - "react-native": "0.74.5", - "react-native-safe-area-context": "4.10.5", - "react-native-screens": "3.31.1" + "react-native": "^0.76.2", + "react-native-safe-area-context": "^4.12.0", + "react-native-screens": "~4.1.0" }, "devDependencies": { "@babel/core": "^7.20.0", "@types/he": "^1.2.3", - "@types/react": "~18.2.45", + "@types/react": "~18.3.12", "typescript": "~5.3.3" } }, + "node_modules/@0no-co/graphql.web": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.0.11.tgz", + "integrity": "sha512-xuSJ9WXwTmtngWkbdEoopMo6F8NLtjy84UNAMsAr5C3/2SgAL/dEU10TMqTIsipqPQ8HA/7WzeqQ9DEQxSvPPA==", + "license": "MIT", + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + }, + "peerDependenciesMeta": { + "graphql": { + "optional": true + } + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -207,18 +221,6 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", @@ -582,25 +584,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-proposal-class-properties": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", @@ -650,23 +633,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", @@ -684,15 +650,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -701,19 +668,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, + "peer": true, "engines": { "node": ">=6.9.0" }, @@ -721,61 +681,52 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" @@ -859,7 +810,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -870,6 +820,30 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", @@ -957,6 +931,36 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", @@ -1009,7 +1013,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-remap-async-to-generator": "^7.25.9", @@ -1075,7 +1078,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" @@ -1274,7 +1276,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" @@ -1339,7 +1340,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1473,7 +1473,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1489,7 +1488,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1539,7 +1537,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -1555,7 +1552,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" @@ -1731,7 +1727,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", "regenerator-transform": "^0.15.2" @@ -2177,6 +2172,25 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse--for-generate-function-map": { + "name": "@babel/traverse", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", @@ -2203,67 +2217,61 @@ } }, "node_modules/@expo/cli": { - "version": "0.18.31", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.18.31.tgz", - "integrity": "sha512-v9llw9fT3Uv+TCM6Xllo54t672CuYtinEQZ2LPJ2EJsCwuTc4Cd2gXQaouuIVD21VoeGQnr5JtJuWbF97sBKzQ==", + "version": "0.21.6", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.21.6.tgz", + "integrity": "sha512-xGeQUK/IX5Wzg2ngXV89wL2s1tWaMJR+sxF8HgGwJqFshC0ikQSV0TRCtz4SCdDjfM7dTPkf4UvOSA45zUMXmA==", "license": "MIT", "dependencies": { + "@0no-co/graphql.web": "^1.0.8", "@babel/runtime": "^7.20.0", - "@expo/code-signing-certificates": "0.0.5", - "@expo/config": "~9.0.0-beta.0", - "@expo/config-plugins": "~8.0.8", - "@expo/devcert": "^1.0.0", - "@expo/env": "~0.3.0", - "@expo/image-utils": "^0.5.0", - "@expo/json-file": "^8.3.0", - "@expo/metro-config": "0.18.11", + "@expo/code-signing-certificates": "^0.0.5", + "@expo/config": "~10.0.4", + "@expo/config-plugins": "~9.0.3", + "@expo/devcert": "^1.1.2", + "@expo/env": "~0.4.0", + "@expo/image-utils": "^0.6.0", + "@expo/json-file": "^9.0.0", + "@expo/metro-config": "~0.19.0", "@expo/osascript": "^2.0.31", "@expo/package-manager": "^1.5.0", - "@expo/plist": "^0.1.0", - "@expo/prebuild-config": "7.0.9", - "@expo/rudder-sdk-node": "1.1.1", + "@expo/plist": "^0.2.0", + "@expo/prebuild-config": "^8.0.16", + "@expo/rudder-sdk-node": "^1.1.1", "@expo/spawn-async": "^1.7.2", "@expo/xcpretty": "^4.3.0", - "@react-native/dev-middleware": "0.74.85", - "@urql/core": "2.3.6", - "@urql/exchange-retry": "0.3.0", + "@react-native/dev-middleware": "0.76.2", + "@urql/core": "^5.0.6", + "@urql/exchange-retry": "^1.3.0", "accepts": "^1.3.8", - "arg": "5.0.2", + "arg": "^5.0.2", "better-opn": "~3.0.2", "bplist-creator": "0.0.7", "bplist-parser": "^0.3.1", "cacache": "^18.0.2", "chalk": "^4.0.0", "ci-info": "^3.3.0", + "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "env-editor": "^0.4.1", "fast-glob": "^3.3.2", - "find-yarn-workspace-root": "~2.0.0", "form-data": "^3.0.1", - "freeport-async": "2.0.0", + "freeport-async": "^2.0.0", "fs-extra": "~8.1.0", "getenv": "^1.0.0", - "glob": "^7.1.7", - "graphql": "15.8.0", - "graphql-tag": "^2.10.1", - "https-proxy-agent": "^5.0.1", - "internal-ip": "4.3.0", + "glob": "^10.4.2", + "internal-ip": "^4.3.0", "is-docker": "^2.0.0", "is-wsl": "^2.1.1", - "js-yaml": "^3.13.1", - "json-schema-deref-sync": "^0.13.0", "lodash.debounce": "^4.0.8", - "md5hex": "^1.0.0", "minimatch": "^3.0.4", - "node-fetch": "^2.6.7", "node-forge": "^1.3.1", - "npm-package-arg": "^7.0.0", - "open": "^8.3.0", - "ora": "3.4.0", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", "picomatch": "^3.0.1", - "pretty-bytes": "5.6.0", - "progress": "2.0.3", + "pretty-bytes": "^5.6.0", + "pretty-format": "^29.7.0", + "progress": "^2.0.3", "prompts": "^2.3.2", "qrcode-terminal": "0.11.0", "require-from-string": "^2.0.2", @@ -2272,17 +2280,17 @@ "resolve-from": "^5.0.0", "resolve.exports": "^2.0.2", "semver": "^7.6.0", - "send": "^0.18.0", + "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", - "tar": "^6.0.5", + "tar": "^6.2.1", "temp-dir": "^2.0.0", "tempy": "^0.7.1", "terminal-link": "^2.1.1", - "text-table": "^0.2.0", - "url-join": "4.0.0", + "undici": "^6.18.2", + "unique-string": "~2.0.0", "wrap-ansi": "^7.0.0", "ws": "^8.12.1" }, @@ -2290,6 +2298,50 @@ "expo-internal": "build/bin/cli" } }, + "node_modules/@expo/cli/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@expo/cli/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@expo/cli/node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@expo/cli/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -2313,39 +2365,40 @@ } }, "node_modules/@expo/config": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-9.0.4.tgz", - "integrity": "sha512-g5ns5u1JSKudHYhjo1zaSfkJ/iZIcWmUmIQptMJZ6ag1C0ShL2sj8qdfU8MmAMuKLOgcIfSaiWlQnm4X3VJVkg==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-10.0.4.tgz", + "integrity": "sha512-pkvdPqKTaP6+Qvc8aTmDLQ9Dfwp98P1GO37MFKwsF5XormfN/9/eN8HfIRoM6d3uSIVKCcWW3X2yAEbNmOyfXw==", "license": "MIT", "dependencies": { "@babel/code-frame": "~7.10.4", - "@expo/config-plugins": "~8.0.8", - "@expo/config-types": "^51.0.3", - "@expo/json-file": "^8.3.0", + "@expo/config-plugins": "~9.0.0", + "@expo/config-types": "^52.0.0", + "@expo/json-file": "^9.0.0", + "deepmerge": "^4.3.1", "getenv": "^1.0.0", - "glob": "7.1.6", + "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", + "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4", - "sucrase": "3.34.0" + "sucrase": "3.35.0" } }, "node_modules/@expo/config-plugins": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-8.0.11.tgz", - "integrity": "sha512-oALE1HwnLFthrobAcC9ocnR9KXLzfWEjgIe4CPe+rDsfC6GDs8dGYCXfRFoCEzoLN4TGYs9RdZ8r0KoCcNrm2A==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-9.0.9.tgz", + "integrity": "sha512-pbgbY3SwCMwkijhfe163J05BrTx4MqzeaV+nVgUMs7vRcjHY1tfM57Pdv6SPtgeDvZ8fvdXFXXzkJva+a7C9Bw==", "license": "MIT", "dependencies": { - "@expo/config-types": "^51.0.3", - "@expo/json-file": "~8.3.0", - "@expo/plist": "^0.1.0", + "@expo/config-types": "^52.0.0", + "@expo/json-file": "~9.0.0", + "@expo/plist": "^0.2.0", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", - "debug": "^4.3.1", - "find-up": "~5.0.0", + "debug": "^4.3.5", "getenv": "^1.0.0", - "glob": "7.1.6", + "glob": "^10.4.2", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slash": "^3.0.0", @@ -2354,22 +2407,45 @@ "xml2js": "0.6.0" } }, + "node_modules/@expo/config-plugins/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@expo/config-plugins/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@expo/config-plugins/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2388,9 +2464,9 @@ } }, "node_modules/@expo/config-types": { - "version": "51.0.3", - "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-51.0.3.tgz", - "integrity": "sha512-hMfuq++b8VySb+m9uNNrlpbvGxYc8OcFCUX9yTmi9tlx6A4k8SDabWFBgmnr4ao3wEArvWrtUQIfQCVtPRdpKA==", + "version": "52.0.1", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-52.0.1.tgz", + "integrity": "sha512-vD8ZetyKV7U29lR6+NJohYeoLYTH+eNYXJeNiSOrWCz0witJYY11meMmEnpEaVbN89EfC6uauSUOa6wihtbyPQ==", "license": "MIT" }, "node_modules/@expo/config/node_modules/@babel/code-frame": { @@ -2402,22 +2478,45 @@ "@babel/highlight": "^7.10.4" } }, - "node_modules/@expo/config/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", + "node_modules/@expo/config/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "balanced-match": "^1.0.0" + } + }, + "node_modules/@expo/config/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@expo/config/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2509,9 +2608,9 @@ } }, "node_modules/@expo/env": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@expo/env/-/env-0.3.0.tgz", - "integrity": "sha512-OtB9XVHWaXidLbHvrVDeeXa09yvTl3+IQN884sO6PhIi2/StXfgSH/9zC7IvzrDB8kW3EBJ1PPLuCUJ2hxAT7Q==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@expo/env/-/env-0.4.0.tgz", + "integrity": "sha512-g2JYFqck3xKIwJyK+8LxZ2ENZPWtRgjFWpeht9abnKgzXVXBeSNECFBkg+WQjQocSIdxXhEWM6hz4ZAe7Tc4ng==", "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -2521,10 +2620,43 @@ "getenv": "^1.0.0" } }, + "node_modules/@expo/fingerprint": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.11.2.tgz", + "integrity": "sha512-WPibADqymGSKkNNnrGfw4dRipz7F8DwMSv7zb6T9oTGtdRiObrUpGmtBXmvo6z9MqWkNRprEJNxPjvkkvMvwhQ==", + "license": "MIT", + "dependencies": { + "@expo/spawn-async": "^1.7.2", + "arg": "^5.0.2", + "chalk": "^4.1.2", + "debug": "^4.3.4", + "find-up": "^5.0.0", + "getenv": "^1.0.0", + "minimatch": "^3.0.4", + "p-limit": "^3.1.0", + "resolve-from": "^5.0.0", + "semver": "^7.6.0" + }, + "bin": { + "fingerprint": "bin/cli.js" + } + }, + "node_modules/@expo/fingerprint/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@expo/image-utils": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.5.1.tgz", - "integrity": "sha512-U/GsFfFox88lXULmFJ9Shfl2aQGcwoKPF7fawSCLixIKtMCpsI+1r0h+5i0nQnmt9tHuzXZDL8+Dg1z6OhkI9A==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.6.3.tgz", + "integrity": "sha512-v/JbCKBrHeudxn1gN1TgfPE/pWJSlLPrl29uXJBgrJFQVkViQvUHQNDhaS+UEa9wYI5HHh7XYmtzAehyG4L+GA==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", @@ -2532,20 +2664,11 @@ "fs-extra": "9.0.0", "getenv": "^1.0.0", "jimp-compact": "0.16.1", - "node-fetch": "^2.6.0", "parse-png": "^2.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0", - "tempy": "0.3.0" - } - }, - "node_modules/@expo/image-utils/node_modules/crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==", - "license": "MIT", - "engines": { - "node": ">=4" + "temp-dir": "~2.0.0", + "unique-string": "~2.0.0" } }, "node_modules/@expo/image-utils/node_modules/fs-extra": { @@ -2596,50 +2719,6 @@ "node": ">=10" } }, - "node_modules/@expo/image-utils/node_modules/temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@expo/image-utils/node_modules/tempy": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.3.0.tgz", - "integrity": "sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==", - "license": "MIT", - "dependencies": { - "temp-dir": "^1.0.0", - "type-fest": "^0.3.1", - "unique-string": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@expo/image-utils/node_modules/type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=6" - } - }, - "node_modules/@expo/image-utils/node_modules/unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg==", - "license": "MIT", - "dependencies": { - "crypto-random-string": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@expo/image-utils/node_modules/universalify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", @@ -2650,13 +2729,13 @@ } }, "node_modules/@expo/json-file": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-8.3.3.tgz", - "integrity": "sha512-eZ5dld9AD0PrVRiIWpRkm5aIoWBw3kAyd8VkuWEy92sEthBKDDDHAnK2a0dw0Eil6j7rK7lS/Qaq/Zzngv2h5A==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-9.0.0.tgz", + "integrity": "sha512-M+55xFVrFzDcgMDf+52lPDLjKB5xwRfStWlv/b/Vu2OLgxGZLWpxoPYjlRoHqxjPbCQIi2ZCbobK+0KuNhsELg==", "license": "MIT", "dependencies": { "@babel/code-frame": "~7.10.4", - "json5": "^2.2.2", + "json5": "^2.2.3", "write-file-atomic": "^2.3.0" } }, @@ -2670,31 +2749,40 @@ } }, "node_modules/@expo/metro-config": { - "version": "0.18.11", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.18.11.tgz", - "integrity": "sha512-/uOq55VbSf9yMbUO1BudkUM2SsGW1c5hr9BnhIqYqcsFv0Jp5D3DtJ4rljDKaUeNLbwr6m7pqIrkSMq5NrYf4Q==", + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.19.4.tgz", + "integrity": "sha512-2SWwYN8MZvMIRawWEr+1RBYncitPwu2VMACRYig+wBycJ9fsPb6BMVmBYi+3MHDUlJHNy/Bqfw++jn1eqBFETQ==", "license": "MIT", "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", - "@expo/config": "~9.0.0-beta.0", - "@expo/env": "~0.3.0", - "@expo/json-file": "~8.3.0", + "@expo/config": "~10.0.4", + "@expo/env": "~0.4.0", + "@expo/json-file": "~9.0.0", "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "debug": "^4.3.2", - "find-yarn-workspace-root": "~2.0.0", "fs-extra": "^9.1.0", "getenv": "^1.0.0", - "glob": "^7.2.3", + "glob": "^10.4.2", "jsc-safe-url": "^0.2.4", - "lightningcss": "~1.19.0", + "lightningcss": "~1.27.0", + "minimatch": "^3.0.4", "postcss": "~8.4.32", "resolve-from": "^5.0.0" } }, + "node_modules/@expo/metro-config/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@expo/metro-config/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -2710,6 +2798,41 @@ "node": ">=10" } }, + "node_modules/@expo/metro-config/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@expo/metro-config/node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@expo/metro-config/node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -2732,9 +2855,9 @@ } }, "node_modules/@expo/osascript": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.1.3.tgz", - "integrity": "sha512-aOEkhPzDsaAfolSswObGiYW0Pf0ROfR9J2NBRLQACdQ6uJlyAMiPF45DVEVknAU9juKh0y8ZyvC9LXqLEJYohA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.1.4.tgz", + "integrity": "sha512-LcPjxJ5FOFpqPORm+5MRLV0CuYWMthJYV6eerF+lQVXKlvgSn3EOqaHC3Vf3H+vmB0f6G4kdvvFtg40vG4bIhA==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", @@ -2745,21 +2868,21 @@ } }, "node_modules/@expo/package-manager": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.5.2.tgz", - "integrity": "sha512-IuA9XtGBilce0q8cyxtWINqbzMB1Fia0Yrug/O53HNuRSwQguV/iqjV68bsa4z8mYerePhcFgtvISWLAlNEbUA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.6.1.tgz", + "integrity": "sha512-4rT46wP/94Ll+CWXtFKok1Lbo9XncSUtErFOo/9/3FVughGbIfdG4SKZOAWIpr9wxwEfkyhHfAP9q71ONlWODw==", "license": "MIT", "dependencies": { - "@expo/json-file": "^8.3.0", + "@expo/json-file": "^9.0.0", "@expo/spawn-async": "^1.7.2", "ansi-regex": "^5.0.0", "chalk": "^4.0.0", "find-up": "^5.0.0", - "find-yarn-workspace-root": "~2.0.0", "js-yaml": "^3.13.1", - "micromatch": "^4.0.2", - "npm-package-arg": "^7.0.0", + "micromatch": "^4.0.8", + "npm-package-arg": "^11.0.0", "ora": "^3.4.0", + "resolve-workspace-root": "^2.0.0", "split": "^1.0.1", "sudo-prompt": "9.1.1" } @@ -2771,9 +2894,9 @@ "license": "MIT" }, "node_modules/@expo/plist": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.1.3.tgz", - "integrity": "sha512-GW/7hVlAylYg1tUrEASclw1MMk9FP4ZwyFAY/SUTJIhPDQHtfOlXREyWV3hhrHdX/K+pS73GNgdfT6E/e+kBbg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.2.0.tgz", + "integrity": "sha512-F/IZJQaf8OIVnVA6XWUeMPC3OH6MV00Wxf0WC0JhTQht2QgjyHUa3U5Gs3vRtDq8tXNsZneOQRDVwpaOnd4zTQ==", "license": "MIT", "dependencies": { "@xmldom/xmldom": "~0.7.7", @@ -2782,25 +2905,22 @@ } }, "node_modules/@expo/prebuild-config": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-7.0.9.tgz", - "integrity": "sha512-9i6Cg7jInpnGEHN0jxnW0P+0BexnePiBzmbUvzSbRXpdXihYUX2AKMu73jgzxn5P1hXOSkzNS7umaY+BZ+aBag==", + "version": "8.0.17", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.0.17.tgz", + "integrity": "sha512-HM+XpDox3fAZuXZXvy55VRcBbsZSDijGf8jI8i/pexgWvtsnt1ouelPXRuE1pXDicMX+lZO83QV+XkyLmBEXYQ==", "license": "MIT", "dependencies": { - "@expo/config": "~9.0.0-beta.0", - "@expo/config-plugins": "~8.0.8", - "@expo/config-types": "^51.0.3", - "@expo/image-utils": "^0.5.0", - "@expo/json-file": "^8.3.0", - "@react-native/normalize-colors": "0.74.85", + "@expo/config": "~10.0.4", + "@expo/config-plugins": "~9.0.0", + "@expo/config-types": "^52.0.0", + "@expo/image-utils": "^0.6.0", + "@expo/json-file": "^9.0.0", + "@react-native/normalize-colors": "0.76.2", "debug": "^4.3.1", "fs-extra": "^9.0.0", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" - }, - "peerDependencies": { - "expo-modules-autolinking": ">=0.8.1" } }, "node_modules/@expo/prebuild-config/node_modules/fs-extra": { @@ -2938,30 +3058,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@graphql-typed-document-node/core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", - "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", - "license": "MIT", - "peerDependencies": { - "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3067,51 +3163,93 @@ "node": ">=12" } }, - "node_modules/@jest/create-cache-key-function": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", - "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "p-locate": "^4.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "license": "MIT", "dependencies": { - "@types/istanbul-lib-report": "*" + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/create-cache-key-function/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/environment": { @@ -3129,59 +3267,75 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/environment/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers": { + "node_modules/@jest/transform": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "license": "MIT", "dependencies": { + "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { + "node_modules/@jest/transform/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", @@ -3198,50 +3352,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -3369,50 +3479,149 @@ "node": ">=14" } }, - "node_modules/@react-native-community/cli": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-13.6.9.tgz", - "integrity": "sha512-hFJL4cgLPxncJJd/epQ4dHnMg5Jy/7Q56jFvA3MHViuKpzzfTCJCB+pGY54maZbtym53UJON9WTGpM3S81UfjQ==", + "node_modules/@react-native-picker/picker": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.9.0.tgz", + "integrity": "sha512-khEhIW/uhfMqq/+tvg4rEAiPGT8GX+Y6QydlP2TSMSmRHoSJK+ShXvXZXSr4Sii4imkj4BwvLunGywwtQDODqg==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/@react-native/assets-registry": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.2.tgz", + "integrity": "sha512-0CTWv/FqJzU1vsyx2JpCkyLSUOePU7DdKgFvtHdwOxFpOw3aBecszqZDGJADYV9WSZQlq6RV0HmIaWycGYCOMA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.76.2.tgz", + "integrity": "sha512-a1IfRho/ZUVbvzSu3JWkxsvqyEI7IXApPQikhGWw4e24QYsIYHdlIULs3rb0840lqpO1dbbuudfO7lmkpkbkMg==", "license": "MIT", "dependencies": { - "@react-native-community/cli-clean": "13.6.9", - "@react-native-community/cli-config": "13.6.9", - "@react-native-community/cli-debugger-ui": "13.6.9", - "@react-native-community/cli-doctor": "13.6.9", - "@react-native-community/cli-hermes": "13.6.9", - "@react-native-community/cli-server-api": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "@react-native-community/cli-types": "13.6.9", - "chalk": "^4.1.2", - "commander": "^9.4.1", - "deepmerge": "^4.3.0", - "execa": "^5.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "prompts": "^2.4.2", - "semver": "^7.5.2" + "@react-native/codegen": "0.76.2" }, - "bin": { - "rnc-cli": "build/bin.js" + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.76.2.tgz", + "integrity": "sha512-/kbxZqy70mGONv23uZg7lm7ZCE4dO5dgMzVPz6QsveXIRHQBRLsSC+9w2iZEnYWpLayoWFmTbq8ZG+4W32D3bA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/template": "^7.25.0", + "@react-native/babel-plugin-codegen": "0.76.2", + "babel-plugin-syntax-hermes-parser": "^0.25.1", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@react-native-community/cli-clean": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-13.6.9.tgz", - "integrity": "sha512-7Dj5+4p9JggxuVNOjPbduZBAP1SUgNhLKVw5noBUzT/3ZpUZkDM+RCSwyoyg8xKWoE4OrdUAXwAFlMcFDPKykA==", + "node_modules/@react-native/codegen": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.76.2.tgz", + "integrity": "sha512-rIgdI5mHHnNTzAeDYH+ivKMIcv6vr04Ol+TmX77n1HjJkzMhQqSHWcX+Pq9oiu7l2zKkymadrw6OPD8VPgre8g==", "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2" + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.23.1", + "invariant": "^2.2.4", + "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.76.2.tgz", + "integrity": "sha512-ZRL8oTGSMwXqTsVkRL9AVW8C/AZRnxCcFfhestsx//SrQt3J/hbtDOHTIGkkt5AEA0zEvb/UAAyIAN/wuN4llw==", + "license": "MIT", + "dependencies": { + "@react-native/dev-middleware": "0.76.2", + "@react-native/metro-babel-transformer": "0.76.2", + "chalk": "^4.0.0", + "execa": "^5.1.1", + "invariant": "^2.2.4", + "metro": "^0.81.0", + "metro-config": "^0.81.0", + "metro-core": "^0.81.0", + "node-fetch": "^2.2.0", + "readline": "^1.3.0", + "semver": "^7.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@react-native-community/cli-server-api": "*" + }, + "peerDependenciesMeta": { + "@react-native-community/cli-server-api": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-clean/node_modules/execa": { + "node_modules/@react-native/community-cli-plugin/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", @@ -3435,7 +3644,7 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@react-native-community/cli-clean/node_modules/get-stream": { + "node_modules/@react-native/community-cli-plugin/node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", @@ -3447,7 +3656,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { + "node_modules/@react-native/community-cli-plugin/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", @@ -3459,7 +3668,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-clean/node_modules/mimic-fn": { + "node_modules/@react-native/community-cli-plugin/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", @@ -3468,7 +3677,7 @@ "node": ">=6" } }, - "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { + "node_modules/@react-native/community-cli-plugin/node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", @@ -3480,7 +3689,7 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli-clean/node_modules/onetime": { + "node_modules/@react-native/community-cli-plugin/node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", @@ -3495,1386 +3704,119 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@react-native-community/cli-config": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-13.6.9.tgz", - "integrity": "sha512-rFfVBcNojcMm+KKHE/xqpqXg8HoKl4EC7bFHUrahMJ+y/tZll55+oX/PGG37rzB8QzP2UbMQ19DYQKC1G7kXeg==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", - "deepmerge": "^4.3.0", - "fast-glob": "^3.3.2", - "joi": "^17.2.1" + "node_modules/@react-native/community-cli-plugin/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@react-native-community/cli-debugger-ui": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-13.6.9.tgz", - "integrity": "sha512-TkN7IdFmGPPvTpAo3nCAH9uwGCPxWBEAwpqEZDrq0NWllI7Tdie8vDpGdrcuCcKalmhq6OYnkXzeBah7O1Ztpw==", - "license": "MIT", - "dependencies": { - "serve-static": "^1.13.1" + "node_modules/@react-native/debugger-frontend": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.76.2.tgz", + "integrity": "sha512-FIcz24Oya2wIO7rZD3dxVyK8t5ZD6Fojl9o7lrjnTWqMedcevRTtdSOIAf4ypksYH/x7HypovE2Zp8U65Xv0Mw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=18" } }, - "node_modules/@react-native-community/cli-doctor": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-13.6.9.tgz", - "integrity": "sha512-5quFaLdWFQB+677GXh5dGU9I5eg2z6Vg4jOX9vKnc9IffwyIFAyJfCZHrxLSRPDGNXD7biDQUdoezXYGwb6P/A==", + "node_modules/@react-native/dev-middleware": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.76.2.tgz", + "integrity": "sha512-qiowXpxofLk0lpIZps7fyyp9NiKlqBwh0R0yVub5l4EJcqjLonjsznYAHbusnPW9kb9MQSdovGPNv5b8RadJww==", "license": "MIT", "dependencies": { - "@react-native-community/cli-config": "13.6.9", - "@react-native-community/cli-platform-android": "13.6.9", - "@react-native-community/cli-platform-apple": "13.6.9", - "@react-native-community/cli-platform-ios": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "deepmerge": "^4.3.0", - "envinfo": "^7.10.0", - "execa": "^5.0.0", - "hermes-profile-transformer": "^0.0.6", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "semver": "^7.5.2", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1", - "yaml": "^2.2.1" + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.76.2", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.2.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "selfsigned": "^2.4.1", + "serve-static": "^1.13.1", + "ws": "^6.2.3" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "node_modules/@react-native/dev-middleware/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" + "ms": "2.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/@react-native/dev-middleware/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "async-limiter": "~1.0.0" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/@react-native/gradle-plugin": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.76.2.tgz", + "integrity": "sha512-KC5/uAeLoeD1dOjymx6gnNFHGGLB22xNYjrjrJNK5r0bw2O2KXp4rpB5VCT/2H5B48cVC0xPB7RIKOFrDHr5bQ==", "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/@react-native/js-polyfills": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.76.2.tgz", + "integrity": "sha512-OXunyNn33fa7gQ6iU5rQcYZQsO7OkJIAr/TgVdoHxpOB4i+ZGsfv6df3JKriBVT1ZZm6ZTlKyIa4QpLq3p0dmw==", "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.76.2.tgz", + "integrity": "sha512-OIYhmWfN+HDyQLzoEg+2P0h7OopYk4djggg0M+k5e1a+g2dFNJILO/BsDobM8uLA8hAzClAJyJLZbPo5jeqdMA==", "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.76.2", + "hermes-parser": "0.23.1", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">=10" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "*" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@react-native-community/cli-hermes": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-13.6.9.tgz", - "integrity": "sha512-GvwiwgvFw4Ws+krg2+gYj8sR3g05evmNjAHkKIKMkDTJjZ8EdyxbkifRUs1ZCq3TMZy2oeblZBXCJVOH4W7ZbA==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-platform-android": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6" - } - }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-13.6.9.tgz", - "integrity": "sha512-9KsYGdr08QhdvT3Ht7e8phQB3gDX9Fs427NJe0xnoBh+PDPTI2BD5ks5ttsH8CzEw8/P6H8tJCHq6hf2nxd9cw==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.2.4", - "logkitty": "^0.7.1" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-13.6.9.tgz", - "integrity": "sha512-KoeIHfhxMhKXZPXmhQdl6EE+jGKWwoO9jUVWgBvibpVmsNjo7woaG/tfJMEWfWF3najX1EkQAoJWpCDBMYWtlA==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.0.12", - "ora": "^5.4.1" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-13.6.9.tgz", - "integrity": "sha512-CiUcHlGs8vE0CAB4oi1f+dzniqfGuhWPNrDvae2nm8dewlahTBwIcK5CawyGezjcJoeQhjBflh9vloska+nlnw==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-platform-apple": "13.6.9" - } - }, - "node_modules/@react-native-community/cli-server-api": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-13.6.9.tgz", - "integrity": "sha512-W8FSlCPWymO+tlQfM3E0JmM8Oei5HZsIk5S0COOl0MRi8h0NmHI4WSTF2GCfbFZkcr2VI/fRsocoN8Au4EZAug==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-debugger-ui": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.1", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^6.2.2" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { - "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "license": "MIT", - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT" - }, - "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/@react-native-community/cli-tools": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-13.6.9.tgz", - "integrity": "sha512-OXaSjoN0mZVw3nrAwcY1PC0uMfyTd9fz7Cy06dh+EJc+h0wikABsVRzV8cIOPrVV+PPEEXE0DBrH20T2puZzgQ==", - "license": "MIT", - "dependencies": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^7.5.2", - "shell-quote": "^1.7.3", - "sudo-prompt": "^9.0.0" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "license": "MIT", - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", - "license": "MIT" - }, - "node_modules/@react-native-community/cli-types": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-13.6.9.tgz", - "integrity": "sha512-RLxDppvRxXfs3hxceW/mShi+6o5yS+kFPnPqZTaMKKR5aSg7LwDpLQW4K2D22irEG8e6RKDkZUeH9aL3vO2O0w==", - "license": "MIT", - "dependencies": { - "joi": "^17.2.1" - } - }, - "node_modules/@react-native-community/cli/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/@react-native-community/cli/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@react-native-picker/picker": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.7.5.tgz", - "integrity": "sha512-vhMaOLkXSUb+YKVbukMJToU4g+89VMhBG2U9+cLYF8X8HtFRidrHjohGqT8/OyesDuKIXeLIP+UFYI9Q9CRA9Q==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/@react-native/assets-registry": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz", - "integrity": "sha512-1XmRhqQchN+pXPKEKYdpJlwESxVomJOxtEnIkbo7GAlaN2sym84fHEGDXAjLilih5GVPpcpSmFzTy8jx3LtaFg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.87.tgz", - "integrity": "sha512-+vJYpMnENFrwtgvDfUj+CtVJRJuUnzAUYT0/Pb68Sq9RfcZ5xdcCuUgyf7JO+akW2VTBoJY427wkcxU30qrWWw==", - "license": "MIT", - "dependencies": { - "@react-native/codegen": "0.74.87" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/babel-preset": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.74.87.tgz", - "integrity": "sha512-hyKpfqzN2nxZmYYJ0tQIHG99FQO0OWXp/gVggAfEUgiT+yNKas1C60LuofUsK7cd+2o9jrpqgqW4WzEDZoBlTg==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.74.87", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/codegen": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.74.87.tgz", - "integrity": "sha512-GMSYDiD+86zLKgMMgz9z0k6FxmRn+z6cimYZKkucW4soGbxWsbjUAZoZ56sJwt2FJ3XVRgXCrnOCgXoH/Bkhcg==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.0", - "glob": "^7.1.1", - "hermes-parser": "0.19.1", - "invariant": "^2.2.4", - "jscodeshift": "^0.14.0", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" - } - }, - "node_modules/@react-native/community-cli-plugin": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.74.87.tgz", - "integrity": "sha512-EgJG9lSr8x3X67dHQKQvU6EkO+3ksVlJHYIVv6U/AmW9dN80BEFxgYbSJ7icXS4wri7m4kHdgeq2PQ7/3vvrTQ==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-server-api": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "@react-native/dev-middleware": "0.74.87", - "@react-native/metro-babel-transformer": "0.74.87", - "chalk": "^4.0.0", - "execa": "^5.1.1", - "metro": "^0.80.3", - "metro-config": "^0.80.3", - "metro-core": "^0.80.3", - "node-fetch": "^2.2.0", - "querystring": "^0.2.1", - "readline": "^1.3.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/debugger-frontend": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.74.87.tgz", - "integrity": "sha512-MN95DJLYTv4EqJc+9JajA3AJZSBYJz2QEJ3uWlHrOky2vKrbbRVaW1ityTmaZa2OXIvNc6CZwSRSE7xCoHbXhQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/dev-middleware": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.74.87.tgz", - "integrity": "sha512-7TmZ3hTHwooYgIHqc/z87BMe1ryrIqAUi+AF7vsD+EHCGxHFdMjSpf1BZ2SUPXuLnF2cTiTfV2RwhbPzx0tYIA==", - "license": "MIT", - "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.74.87", - "@rnx-kit/chromium-edge-launcher": "^1.0.0", - "chrome-launcher": "^0.15.2", - "connect": "^3.6.5", - "debug": "^2.2.0", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "selfsigned": "^2.4.1", - "serve-static": "^1.13.1", - "temp-dir": "^2.0.0", - "ws": "^6.2.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/@react-native/community-cli-plugin/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/@react-native/debugger-frontend": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.74.85.tgz", - "integrity": "sha512-gUIhhpsYLUTYWlWw4vGztyHaX/kNlgVspSvKe2XaPA7o3jYKUoNLc3Ov7u70u/MBWfKdcEffWq44eSe3j3s5JQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/dev-middleware": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.74.85.tgz", - "integrity": "sha512-BRmgCK5vnMmHaKRO+h8PKJmHHH3E6JFuerrcfE3wG2eZ1bcSr+QTu8DAlpxsDWvJvHpCi8tRJGauxd+Ssj/c7w==", - "license": "MIT", - "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.74.85", - "@rnx-kit/chromium-edge-launcher": "^1.0.0", - "chrome-launcher": "^0.15.2", - "connect": "^3.6.5", - "debug": "^2.2.0", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "selfsigned": "^2.4.1", - "serve-static": "^1.13.1", - "temp-dir": "^2.0.0", - "ws": "^6.2.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/@react-native/dev-middleware/node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.74.87.tgz", - "integrity": "sha512-T+VX0N1qP+U9V4oAtn7FTX7pfsoVkd1ocyw9swYXgJqU2fK7hC9famW7b3s3ZiufPGPr1VPJe2TVGtSopBjL6A==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/js-polyfills": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.74.87.tgz", - "integrity": "sha512-M5Evdn76CuVEF0GsaXiGi95CBZ4IWubHqwXxV9vG9CC9kq0PSkoM2Pn7Lx7dgyp4vT7ccJ8a3IwHbe+5KJRnpw==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.74.87.tgz", - "integrity": "sha512-UsJCO24sNax2NSPBmV1zLEVVNkS88kcgAiYrZHtYSwSjpl4WZ656tIeedBfiySdJ94Hr3kQmBYLipV5zk0NI1A==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.74.87", - "hermes-parser": "0.19.1", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/normalize-colors": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.85.tgz", - "integrity": "sha512-pcE4i0X7y3hsAE0SpIl7t6dUc0B0NZLd1yv7ssm4FrLhWG+CGyIq4eFDXpmPU1XHmL5PPySxTAjEMiwv6tAmOw==", - "license": "MIT" + "node_modules/@react-native/normalize-colors": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.76.2.tgz", + "integrity": "sha512-ICoOpaTLPsFQjNLSM00NgQr6wal300cZZonHVSDXKntX+BfkLeuCHRtr/Mn+klTtW+/1v2/2FRm9dXjvyGf9Dw==", + "license": "MIT" }, "node_modules/@react-native/virtualized-lists": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.74.87.tgz", - "integrity": "sha512-lsGxoFMb0lyK/MiplNKJpD+A1EoEUumkLrCjH4Ht+ZlG8S0BfCxmskLZ6qXn3BiDSkLjfjI/qyZ3pnxNBvkXpQ==", + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.76.2.tgz", + "integrity": "sha512-FzXvkHgKvJGf0pSuLy6878cxJ6mxWKgZsH9s2kO4LWJocI8Bi3ViDx7IGAWYuvN+Fnue5TKaqGPhfD+4XrKtYQ==", "license": "MIT", "dependencies": { "invariant": "^2.2.4", @@ -4965,35 +3907,6 @@ "nanoid": "^3.1.23" } }, - "node_modules/@rnx-kit/chromium-edge-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rnx-kit/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", - "integrity": "sha512-lzD84av1ZQhYUS+jsGqJiCMaJO2dn9u+RTT9n9q6D3SaKVwWqv+7AoRKqBu19bkwyE+iFRl1ymr40QS90jVFYg==", - "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.0.0", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=14.15" - } - }, - "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@segment/loosely-validate-event": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", @@ -5003,27 +3916,6 @@ "join-component": "^1.1.0" } }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "license": "BSD-3-Clause" - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -5048,6 +3940,56 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/he": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.3.tgz", @@ -5071,12 +4013,11 @@ } }, "node_modules/@types/istanbul-reports": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", - "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "*", "@types/istanbul-lib-report": "*" } }, @@ -5106,9 +4047,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.2.79", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz", - "integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==", + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -5123,9 +4064,9 @@ "license": "MIT" }, "node_modules/@types/yargs": { - "version": "13.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.12.tgz", - "integrity": "sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -5138,29 +4079,26 @@ "license": "MIT" }, "node_modules/@urql/core": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@urql/core/-/core-2.3.6.tgz", - "integrity": "sha512-PUxhtBh7/8167HJK6WqBv6Z0piuiaZHQGYbhwpNL9aIQmLROPEdaUYkY4wh45wPQXcTpnd11l0q3Pw+TI11pdw==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.0.8.tgz", + "integrity": "sha512-1GOnUw7/a9bzkcM0+U8U5MmxW2A7FE5YquuEmcJzTtW5tIs2EoS4F2ITpuKBjRBbyRjZgO860nWFPo1m4JImGA==", "license": "MIT", "dependencies": { - "@graphql-typed-document-node/core": "^3.1.0", - "wonka": "^4.0.14" - }, - "peerDependencies": { - "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + "@0no-co/graphql.web": "^1.0.5", + "wonka": "^6.3.2" } }, "node_modules/@urql/exchange-retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-0.3.0.tgz", - "integrity": "sha512-hHqer2mcdVC0eYnVNbWyi28AlGOPb2vjH3lP3/Bc8Lc8BjhMsDwFMm7WhoP5C1+cfbr/QJ6Er3H/L08wznXxfg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-1.3.0.tgz", + "integrity": "sha512-FLt+d81gP4oiHah4hWFDApimc+/xABWMU1AMYsZ1PVB0L0YPtrMCjbOp9WMM7hBzy4gbTDrG24sio0dCfSh/HQ==", "license": "MIT", "dependencies": { - "@urql/core": ">=2.3.1", - "wonka": "^4.0.14" + "@urql/core": "^5.0.0", + "wonka": "^6.3.2" }, "peerDependencies": { - "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "@urql/core": "^5.0.0" } }, "node_modules/@xmldom/xmldom": { @@ -5210,18 +4148,6 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -5256,17 +4182,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "license": "MIT", - "dependencies": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -5322,12 +4237,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "license": "MIT" - }, "node_modules/application-config-path": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/application-config-path/-/application-config-path-0.1.1.tgz", @@ -5342,27 +4251,11 @@ }, "node_modules/argparse": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "sprintf-js": "~1.0.2" } }, "node_modules/array-union": { @@ -5374,28 +4267,6 @@ "node": ">=8" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -5414,15 +4285,6 @@ "node": ">=4" } }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", @@ -5444,28 +4306,65 @@ "node": ">= 4.0.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "license": "MIT", + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "license": "MIT", "dependencies": { - "possible-typed-array-names": "^1.0.0" + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.8.0" } }, - "node_modules/babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "license": "MIT", - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/babel-plugin-polyfill-corejs2": { @@ -5507,61 +4406,36 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-react-compiler": { - "version": "0.0.0-experimental-592953e-20240517", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-0.0.0-experimental-592953e-20240517.tgz", - "integrity": "sha512-OjG1SVaeQZaJrqkMFJatg8W/MTow8Ak5rx2SI0ETQBO1XvOk/XZGMbltNCPdFJLKghBYoBjC+Y3Ap/Xr7B01mA==", - "license": "MIT", - "dependencies": { - "@babel/generator": "7.2.0", - "@babel/types": "^7.19.0", - "chalk": "4", - "invariant": "^2.2.4", - "pretty-format": "^24", - "zod": "^3.22.4", - "zod-validation-error": "^2.1.0" - } + "node_modules/babel-plugin-react-native-web": { + "version": "0.19.13", + "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.19.13.tgz", + "integrity": "sha512-4hHoto6xaN23LCyZgL9LJZc3olmAxd7b6jDzlZnKXAh4rRAbZRKNBJoOOdp46OBqgy+K0t0guTj5/mhA8inymQ==", + "license": "MIT" }, - "node_modules/babel-plugin-react-compiler/node_modules/@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.25.1.tgz", + "integrity": "sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" + "hermes-parser": "0.25.1" } }, - "node_modules/babel-plugin-react-compiler/node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } + "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "license": "MIT" }, - "node_modules/babel-plugin-react-compiler/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" } }, - "node_modules/babel-plugin-react-native-web": { - "version": "0.19.13", - "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.19.13.tgz", - "integrity": "sha512-4hHoto6xaN23LCyZgL9LJZc3olmAxd7b6jDzlZnKXAh4rRAbZRKNBJoOOdp46OBqgy+K0t0guTj5/mhA8inymQ==", - "license": "MIT" - }, "node_modules/babel-plugin-transform-flow-enums": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", @@ -5571,10 +4445,36 @@ "@babel/plugin-syntax-flow": "^7.12.1" } }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/babel-preset-expo": { - "version": "11.0.15", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-11.0.15.tgz", - "integrity": "sha512-rgiMTYwqIPULaO7iZdqyL7aAff9QLOX6OWUtLZBlOrOTreGY1yHah/5+l8MvI6NVc/8Zj5LY4Y5uMSnJIuzTLw==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-12.0.1.tgz", + "integrity": "sha512-9T2o+aeKnHOtQhk/undQbibJv02bdCgfs68ZwgAdueljDBcs2oVfq41qG9XThYwa6Dn7CdfnoEUsIyFqBwjcVw==", "license": "MIT", "dependencies": { "@babel/plugin-proposal-decorators": "^7.12.9", @@ -5583,10 +4483,37 @@ "@babel/plugin-transform-parameters": "^7.22.15", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-preset": "0.74.87", - "babel-plugin-react-compiler": "0.0.0-experimental-592953e-20240517", - "babel-plugin-react-native-web": "~0.19.10", + "@react-native/babel-preset": "0.76.2", + "babel-plugin-react-native-web": "~0.19.13", "react-refresh": "^0.14.2" + }, + "peerDependencies": { + "babel-plugin-react-compiler": "^19.0.0-beta-9ee70a1-20241017", + "react-compiler-runtime": "^19.0.0-beta-8a03594-20241020" + }, + "peerDependenciesMeta": { + "babel-plugin-react-compiler": { + "optional": true + }, + "react-compiler-runtime": { + "optional": true + } + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/balanced-match": { @@ -5627,6 +4554,23 @@ "node": ">=12.0.0" } }, + "node_modules/better-opn/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -5645,31 +4589,6 @@ "node": "*" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bplist-creator": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz", @@ -5806,12 +4725,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, - "node_modules/builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==", - "license": "MIT" - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -5894,25 +4807,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -5947,15 +4841,12 @@ } }, "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/caniuse-lite": { @@ -6030,6 +4921,32 @@ "node": ">=12.13.0" } }, + "node_modules/chromium-edge-launcher": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", + "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "node_modules/chromium-edge-launcher/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -6104,15 +5021,6 @@ "node": ">=8" } }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -6145,12 +5053,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "license": "MIT" - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6333,9 +5235,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6371,69 +5273,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/dag-map": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/dag-map/-/dag-map-1.0.2.tgz", - "integrity": "sha512-+LSAiGFwQ9dRnRdOeaj7g47ZFJcOUPukAP8J3A3fuZ1g9Y44BG+P1sgApjLXTQPOzC4+7S9Wr8kXsfpINM4jpw==", - "license": "MIT" - }, - "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -6445,19 +5284,10 @@ "engines": { "node": ">=6.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/decode-uri-component": { @@ -6521,23 +5351,6 @@ "node": ">=0.8" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -6547,23 +5360,6 @@ "node": ">=8" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/del": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", @@ -6657,12 +5453,12 @@ } }, "node_modules/dotenv-expand": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", - "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", "license": "BSD-2-Clause", "dependencies": { - "dotenv": "^16.4.4" + "dotenv": "^16.4.5" }, "engines": { "node": ">=12" @@ -6722,18 +5518,6 @@ "node": ">=8" } }, - "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", - "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/eol": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz", @@ -6758,143 +5542,6 @@ "stackframe": "^1.3.4" } }, - "node_modules/errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -6988,9 +5635,9 @@ } }, "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "license": "MIT", "dependencies": { "nice-try": "^1.0.4", @@ -7055,94 +5702,126 @@ } }, "node_modules/expo": { - "version": "51.0.39", - "resolved": "https://registry.npmjs.org/expo/-/expo-51.0.39.tgz", - "integrity": "sha512-Cs/9xopyzJrpXWbyVUZnr37rprdFJorRgfSp6cdBfvbjxZeKnw2MEu7wJwV/s626i5lZTPGjZPHUF9uQvt51cg==", + "version": "52.0.8", + "resolved": "https://registry.npmjs.org/expo/-/expo-52.0.8.tgz", + "integrity": "sha512-EQwJFRS2UUTGTe++bcLN3b7TeSpSWhQzIPt4jIh+0iannbYxgXLE6oKApTJhh0dsMWDnZzU5a8DIdadJY8VZJA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "0.18.31", - "@expo/config": "9.0.4", - "@expo/config-plugins": "8.0.11", - "@expo/metro-config": "0.18.11", - "@expo/vector-icons": "^14.0.3", - "babel-preset-expo": "~11.0.15", - "expo-asset": "~10.0.10", - "expo-file-system": "~17.0.1", - "expo-font": "~12.0.10", - "expo-keep-awake": "~13.0.2", - "expo-modules-autolinking": "1.11.3", - "expo-modules-core": "1.12.26", + "@expo/cli": "0.21.6", + "@expo/config": "~10.0.4", + "@expo/config-plugins": "9.0.9", + "@expo/fingerprint": "0.11.2", + "@expo/metro-config": "0.19.4", + "@expo/vector-icons": "^14.0.0", + "babel-preset-expo": "~12.0.1", + "expo-asset": "~11.0.1", + "expo-constants": "~17.0.3", + "expo-file-system": "~18.0.4", + "expo-font": "~13.0.1", + "expo-keep-awake": "~14.0.1", + "expo-modules-autolinking": "2.0.2", + "expo-modules-core": "2.0.4", "fbemitter": "^3.0.0", + "web-streams-polyfill": "^3.3.2", "whatwg-url-without-unicode": "8.0.0-3" }, "bin": { "expo": "bin/cli" + }, + "peerDependencies": { + "@expo/dom-webview": "*", + "@expo/metro-runtime": "*", + "react": "*", + "react-native": "*", + "react-native-webview": "*" + }, + "peerDependenciesMeta": { + "@expo/dom-webview": { + "optional": true + }, + "@expo/metro-runtime": { + "optional": true + }, + "react-native-webview": { + "optional": true + } } }, "node_modules/expo-asset": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-10.0.10.tgz", - "integrity": "sha512-0qoTIihB79k+wGus9wy0JMKq7DdenziVx3iUkGvMAy2azscSgWH6bd2gJ9CGnhC6JRd3qTMFBL0ou/fx7WZl7A==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-11.0.1.tgz", + "integrity": "sha512-WatvD7JVC89EsllXFYcS/rji3ajVzE2B/USo0TqedsETixwyVCQfrrvCdCPQyuKghrxVNEj8bQ/Qbea/RZLYjg==", "license": "MIT", "dependencies": { - "expo-constants": "~16.0.0", + "@expo/image-utils": "^0.6.0", + "expo-constants": "~17.0.0", "invariant": "^2.2.4", "md5-file": "^3.2.3" }, "peerDependencies": { - "expo": "*" + "expo": "*", + "react": "*", + "react-native": "*" } }, "node_modules/expo-constants": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-16.0.2.tgz", - "integrity": "sha512-9tNY3OVO0jfiMzl7ngb6IOyR5VFzNoN5OOazUWoeGfmMqVB5kltTemRvKraK9JRbBKIw+SOYLEmF0sEqgFZ6OQ==", + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.0.3.tgz", + "integrity": "sha512-lnbcX2sAu8SucHXEXxSkhiEpqH+jGrf+TF+MO6sHWIESjwOUVVYlT8qYdjR9xbxWmqFtrI4KV44FkeJf2DaFjQ==", "license": "MIT", "dependencies": { - "@expo/config": "~9.0.0", - "@expo/env": "~0.3.0" + "@expo/config": "~10.0.4", + "@expo/env": "~0.4.0" }, "peerDependencies": { - "expo": "*" + "expo": "*", + "react-native": "*" } }, "node_modules/expo-file-system": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-17.0.1.tgz", - "integrity": "sha512-dYpnZJqTGj6HCYJyXAgpFkQWsiCH3HY1ek2cFZVHFoEc5tLz9gmdEgTF6nFHurvmvfmXqxi7a5CXyVm0aFYJBw==", + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-18.0.4.tgz", + "integrity": "sha512-aAWEDwnu0XHOBYvQ9Q0+QIa+483vYJaC4IDsXyWQ73Rtsg273NZh5kYowY+cAocvoSmA99G6htrLBn11ax2bTQ==", "license": "MIT", + "dependencies": { + "web-streams-polyfill": "^3.3.2" + }, "peerDependencies": { - "expo": "*" + "expo": "*", + "react-native": "*" } }, "node_modules/expo-font": { - "version": "12.0.10", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-12.0.10.tgz", - "integrity": "sha512-Q1i2NuYri3jy32zdnBaHHCya1wH1yMAsI+3CCmj9zlQzlhsS9Bdwcj2W3c5eU5FvH2hsNQy4O+O1NnM6o/pDaQ==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-13.0.1.tgz", + "integrity": "sha512-8JE47B+6cLeKWr5ql8gU6YsPHjhrz1vMrTqYMm72No/8iW8Sb/uL4Oc0dpmbjq3hLLXBY0xPBQOgU7FQ6Y04Vg==", "license": "MIT", "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { - "expo": "*" + "expo": "*", + "react": "*" } }, "node_modules/expo-keep-awake": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-13.0.2.tgz", - "integrity": "sha512-kKiwkVg/bY0AJ5q1Pxnm/GvpeB6hbNJhcFsoOWDh2NlpibhCLaHL826KHUM+WsnJRbVRxJ+K9vbPRHEMvFpVyw==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-14.0.1.tgz", + "integrity": "sha512-c5mGCAIk2YM+Vsdy90BlEJ4ZX+KG5Au9EkJUIxXWlpnuKmDAJ3N+5nEZ7EUO1ZTheqoSBeAo4jJ8rTWPU+JXdw==", "license": "MIT", "peerDependencies": { - "expo": "*" + "expo": "*", + "react": "*" } }, "node_modules/expo-modules-autolinking": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.11.3.tgz", - "integrity": "sha512-oYh8EZEvYF5TYppxEKUTTJmbr8j7eRRnrIxzZtMvxLTXoujThVPMFS/cbnSnf2bFm1lq50TdDNABhmEi7z0ngQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.0.2.tgz", + "integrity": "sha512-n3jC7VoJLfOLGk8NWhEAvM5zSjbLh1kMUSo76nJupx5/vASxDdzihppYebrKrNXPHq5mcw8Jr+r7YB+8xHx7QQ==", "license": "MIT", "dependencies": { + "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0", "fast-glob": "^3.2.5", @@ -7192,19 +5871,23 @@ } }, "node_modules/expo-modules-core": { - "version": "1.12.26", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.12.26.tgz", - "integrity": "sha512-y8yDWjOi+rQRdO+HY+LnUlz8qzHerUaw/LUjKPU/mX8PRXP4UUPEEp5fjAwBU44xjNmYSHWZDwet4IBBE+yQUA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-2.0.4.tgz", + "integrity": "sha512-nNS40KYh1d7tWXCcEKBrSigIKCVfJwkPLhR/mniAoPzqevUDLVJNJjIgKfQL6kPlsViC3hwwgrUpKSlmWv2DFg==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" } }, "node_modules/expo-status-bar": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-1.12.1.tgz", - "integrity": "sha512-/t3xdbS8KB0prj5KG5w7z+wZPFlPtkgs95BsmrP/E7Q0xHXTcDcQ6Cu2FkFuRM+PKTb17cJDnLkawyS5vDLxMA==", - "license": "MIT" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-2.0.0.tgz", + "integrity": "sha512-vxxdpvpNDMTEc5uTiIrbTvySKKUsOACmfl8OZuUdjNle05oGqwtq3v5YObwym/njSByjoyuZX8UpXBZnxvarwQ==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } }, "node_modules/exponential-backoff": { "version": "3.1.1", @@ -7234,27 +5917,11 @@ "node": ">=8.6.0" } }, - "node_modules/fast-xml-parser": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", - "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" }, "node_modules/fastq": { "version": "1.17.1", @@ -7394,15 +6061,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-yarn-workspace-root": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", - "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", - "license": "Apache-2.0", - "dependencies": { - "micromatch": "^4.0.2" - } - }, "node_modules/flow-enums-runtime": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", @@ -7424,15 +6082,6 @@ "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==", "license": "BSD-2-Clause" }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.3" - } - }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -7548,33 +6197,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -7593,23 +6215,13 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8.0.0" } }, "node_modules/get-port": { @@ -7633,23 +6245,6 @@ "node": ">=6" } }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/getenv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz", @@ -7701,22 +6296,6 @@ "node": ">=4" } }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -7737,115 +6316,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/graphql": { - "version": "15.8.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", - "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==", - "license": "MIT", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/graphql-tag": { - "version": "2.12.6", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", - "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, "node_modules/hasown": { @@ -7870,60 +6353,36 @@ } }, "node_modules/hermes-estree": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.19.1.tgz", - "integrity": "sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.1.tgz", + "integrity": "sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg==", "license": "MIT" }, "node_modules/hermes-parser": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.19.1.tgz", - "integrity": "sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==", - "license": "MIT", - "dependencies": { - "hermes-estree": "0.19.1" - } - }, - "node_modules/hermes-profile-transformer": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", - "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.1.tgz", + "integrity": "sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA==", "license": "MIT", "dependencies": { - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=8" + "hermes-estree": "0.23.1" } }, "node_modules/hosted-git-info": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", - "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "license": "ISC", "dependencies": { - "lru-cache": "^6.0.0" + "lru-cache": "^10.0.1" }, "engines": { - "node": ">=10" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, "node_modules/html-parse-stringify": { @@ -7960,19 +6419,6 @@ "node": ">= 0.8" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -8136,20 +6582,6 @@ "node": ">=6" } }, - "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -8177,74 +6609,18 @@ "node": ">= 0.10" } }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "license": "MIT" }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "license": "MIT" }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", @@ -8260,36 +6636,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "license": "MIT", - "dependencies": { - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", @@ -8323,15 +6669,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -8344,60 +6681,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-invalid-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz", - "integrity": "sha512-aZMG0T3F34mTg4eTdszcGXx54oiZ4NtHSft3hWNJMGJXUUqdIj3cOZuHcU0nCWWcY3jd7yRe/3AEm3vSNTpBGQ==", - "license": "MIT", - "dependencies": { - "is-glob": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-invalid-path/node_modules/is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-invalid-path/node_modules/is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -8407,21 +6690,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-path-cwd": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", @@ -8435,142 +6703,30 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/is-valid-path": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz", - "integrity": "sha512-+kwPrVDu9Ms03L90Qaml+79+6DZHqHyRoANI6IsZJ/g8frhnfchDOBCa0RbQ6/kdHt5CS5OeIEyrYznNuVN+8A==", + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "license": "MIT", "dependencies": { - "is-invalid-path": "^0.1.0" + "isobject": "^3.0.1" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, "node_modules/is-wsl": { @@ -8606,6 +6762,31 @@ "node": ">=0.10.0" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -8638,48 +6819,38 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-node/node_modules/@jest/types": { + "node_modules/jest-get-type": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-node/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-environment-node/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "license": "MIT", + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, "node_modules/jest-message-util": { @@ -8702,73 +6873,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-message-util/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", @@ -8783,41 +6887,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock/node_modules/@jest/types": { + "node_modules/jest-regex-util": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -8835,41 +6913,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-util/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-util/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -8899,72 +6942,17 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "license": "MIT", "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/jest-worker": { "version": "29.7.0", @@ -9002,19 +6990,6 @@ "integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==", "license": "MIT" }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, "node_modules/join-component": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/join-component/-/join-component-1.1.0.tgz", @@ -9103,36 +7078,6 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "license": "MIT" }, - "node_modules/json-schema-deref-sync": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/json-schema-deref-sync/-/json-schema-deref-sync-0.13.0.tgz", - "integrity": "sha512-YBOEogm5w9Op337yb6pAT6ZXDqlxAsQCanM3grid8lMWNxRJO/zWEJi3ZzqDL8boWfwhTFym5EFrNgWwpqcBRg==", - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "dag-map": "~1.0.0", - "is-valid-path": "^0.1.1", - "lodash": "^4.17.13", - "md5": "~2.2.0", - "memory-cache": "~0.2.0", - "traverse": "~0.6.6", - "valid-url": "~1.0.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/json-schema-deref-sync/node_modules/md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha512-PlGG4z5mBANDGCKsYQe0CaUYHdZYZt8ZPZLmEt+Urf0W4GlpTX4HescwHU+dc9+Z/G/vZKYZYFrwgm9VxK6QOQ==", - "license": "BSD-3-Clause", - "dependencies": { - "charenc": "~0.0.1", - "crypt": "~0.0.1", - "is-buffer": "~1.1.1" - } - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -9207,9 +7152,9 @@ "license": "MIT" }, "node_modules/lightningcss": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.19.0.tgz", - "integrity": "sha512-yV5UR7og+Og7lQC+70DA7a8ta1uiOPnWPJfxa0wnxylev5qfo4P+4iMpzWAdYWOca4jdNQZii+bDL/l+4hUXIA==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.27.0.tgz", + "integrity": "sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^1.0.3" @@ -9222,20 +7167,22 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "1.19.0", - "lightningcss-darwin-x64": "1.19.0", - "lightningcss-linux-arm-gnueabihf": "1.19.0", - "lightningcss-linux-arm64-gnu": "1.19.0", - "lightningcss-linux-arm64-musl": "1.19.0", - "lightningcss-linux-x64-gnu": "1.19.0", - "lightningcss-linux-x64-musl": "1.19.0", - "lightningcss-win32-x64-msvc": "1.19.0" + "lightningcss-darwin-arm64": "1.27.0", + "lightningcss-darwin-x64": "1.27.0", + "lightningcss-freebsd-x64": "1.27.0", + "lightningcss-linux-arm-gnueabihf": "1.27.0", + "lightningcss-linux-arm64-gnu": "1.27.0", + "lightningcss-linux-arm64-musl": "1.27.0", + "lightningcss-linux-x64-gnu": "1.27.0", + "lightningcss-linux-x64-musl": "1.27.0", + "lightningcss-win32-arm64-msvc": "1.27.0", + "lightningcss-win32-x64-msvc": "1.27.0" } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.19.0.tgz", - "integrity": "sha512-wIJmFtYX0rXHsXHSr4+sC5clwblEMji7HHQ4Ub1/CznVRxtCFha6JIt5JZaNf8vQrfdZnBxLLC6R8pC818jXqg==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.27.0.tgz", + "integrity": "sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ==", "cpu": [ "arm64" ], @@ -9253,9 +7200,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.19.0.tgz", - "integrity": "sha512-Lif1wD6P4poaw9c/4Uh2z+gmrWhw/HtXFoeZ3bEsv6Ia4tt8rOJBdkfVaUJ6VXmpKHALve+iTyP2+50xY1wKPw==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.27.0.tgz", + "integrity": "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==", "cpu": [ "x64" ], @@ -9272,10 +7219,30 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.27.0.tgz", + "integrity": "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.19.0.tgz", - "integrity": "sha512-P15VXY5682mTXaiDtbnLYQflc8BYb774j2R84FgDLJTN6Qp0ZjWEFyN1SPqyfTj2B2TFjRHRUvQSSZ7qN4Weig==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.27.0.tgz", + "integrity": "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==", "cpu": [ "arm" ], @@ -9293,9 +7260,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.19.0.tgz", - "integrity": "sha512-zwXRjWqpev8wqO0sv0M1aM1PpjHz6RVIsBcxKszIG83Befuh4yNysjgHVplF9RTU7eozGe3Ts7r6we1+Qkqsww==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.27.0.tgz", + "integrity": "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==", "cpu": [ "arm64" ], @@ -9313,9 +7280,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.19.0.tgz", - "integrity": "sha512-vSCKO7SDnZaFN9zEloKSZM5/kC5gbzUjoJQ43BvUpyTFUX7ACs/mDfl2Eq6fdz2+uWhUh7vf92c4EaaP4udEtA==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.27.0.tgz", + "integrity": "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==", "cpu": [ "arm64" ], @@ -9333,9 +7300,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.19.0.tgz", - "integrity": "sha512-0AFQKvVzXf9byrXUq9z0anMGLdZJS+XSDqidyijI5njIwj6MdbvX2UZK/c4FfNmeRa2N/8ngTffoIuOUit5eIQ==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.27.0.tgz", + "integrity": "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==", "cpu": [ "x64" ], @@ -9353,9 +7320,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.19.0.tgz", - "integrity": "sha512-SJoM8CLPt6ECCgSuWe+g0qo8dqQYVcPiW2s19dxkmSI5+Uu1GIRzyKA0b7QqmEXolA+oSJhQqCmJpzjY4CuZAg==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.27.0.tgz", + "integrity": "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==", "cpu": [ "x64" ], @@ -9372,10 +7339,30 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.27.0.tgz", + "integrity": "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.19.0.tgz", - "integrity": "sha512-C+VuUTeSUOAaBZZOPT7Etn/agx/MatzJzGRkeV+zEABmPuntv1zihncsi+AyGmjkkzq3wVedEy7h0/4S84mUtg==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.27.0.tgz", + "integrity": "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==", "cpu": [ "x64" ], @@ -9514,159 +7501,6 @@ "node": ">=4" } }, - "node_modules/logkitty": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "license": "MIT", - "dependencies": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, - "bin": { - "logkitty": "bin/logkitty.js" - } - }, - "node_modules/logkitty/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/logkitty/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/logkitty/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/logkitty/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "license": "ISC" - }, - "node_modules/logkitty/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -9757,24 +7591,12 @@ "node": ">=0.10" } }, - "node_modules/md5hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/md5hex/-/md5hex-1.0.0.tgz", - "integrity": "sha512-c2YOUbp33+6thdCUi34xIyOU/a7bvGKj/3DB1iaPMTuPHf/Q2d5s4sn1FaCOO43XkXggnb08y5W2PU8UNYNLKQ==", - "license": "MIT" - }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, - "node_modules/memory-cache": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz", - "integrity": "sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA==", - "license": "BSD-2-Clause" - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -9791,18 +7613,18 @@ } }, "node_modules/metro": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.12.tgz", - "integrity": "sha512-1UsH5FzJd9quUsD1qY+zUG4JY3jo3YEMxbMYH9jT6NK3j4iORhlwTK8fYTfAUBhDKjgLfKjAh7aoazNE23oIRA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.81.0.tgz", + "integrity": "sha512-kzdzmpL0gKhEthZ9aOV7sTqvg6NuTxDV8SIm9pf9sO8VVEbKrQk5DNcwupOUjgPPFAuKUc2NkT0suyT62hm2xg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", @@ -9812,24 +7634,24 @@ "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.23.1", + "hermes-parser": "0.24.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.6.3", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.80.12", - "metro-cache": "0.80.12", - "metro-cache-key": "0.80.12", - "metro-config": "0.80.12", - "metro-core": "0.80.12", - "metro-file-map": "0.80.12", - "metro-resolver": "0.80.12", - "metro-runtime": "0.80.12", - "metro-source-map": "0.80.12", - "metro-symbolicate": "0.80.12", - "metro-transform-plugins": "0.80.12", - "metro-transform-worker": "0.80.12", + "metro-babel-transformer": "0.81.0", + "metro-cache": "0.81.0", + "metro-cache-key": "0.81.0", + "metro-config": "0.81.0", + "metro-core": "0.81.0", + "metro-file-map": "0.81.0", + "metro-resolver": "0.81.0", + "metro-runtime": "0.81.0", + "metro-source-map": "0.81.0", + "metro-symbolicate": "0.81.0", + "metro-transform-plugins": "0.81.0", + "metro-transform-worker": "0.81.0", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", @@ -9843,102 +7665,102 @@ "metro": "src/cli.js" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/metro-babel-transformer": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.12.tgz", - "integrity": "sha512-YZziRs0MgA3pzCkkvOoQRXjIoVjvrpi/yRlJnObyIvMP6lFdtyG4nUGIwGY9VXnBvxmXD6mPY2e+NSw6JAyiRg==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.81.0.tgz", + "integrity": "sha512-Dc0QWK4wZIeHnyZ3sevWGTnnSkIDDn/SWyfrn99zbKbDOCoCYy71PAn9uCRrP/hduKLJQOy+tebd63Rr9D8tXg==", "license": "MIT", "dependencies": { - "@babel/core": "^7.20.0", + "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.23.1", + "hermes-parser": "0.24.0", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.1.tgz", - "integrity": "sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.24.0.tgz", + "integrity": "sha512-LyoXLB7IFzeZW0EvAbGZacbxBN7t6KKSDqFJPo3Ydow7wDlrDjXwsdiAHV6XOdvEN9MEuWXsSIFN4tzpyrXIHw==", "license": "MIT" }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.1.tgz", - "integrity": "sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.24.0.tgz", + "integrity": "sha512-IJooSvvu2qNRe7oo9Rb04sUT4omtZqZqf9uq9WM25Tb6v3usmvA93UqfnnoWs5V0uYjEl9Al6MNU10MCGKLwpg==", "license": "MIT", "dependencies": { - "hermes-estree": "0.23.1" + "hermes-estree": "0.24.0" } }, "node_modules/metro-cache": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.12.tgz", - "integrity": "sha512-p5kNHh2KJ0pbQI/H7ZBPCEwkyNcSz7OUkslzsiIWBMPQGFJ/xArMwkV7I+GJcWh+b4m6zbLxE5fk6fqbVK1xGA==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.81.0.tgz", + "integrity": "sha512-DyuqySicHXkHUDZFVJmh0ygxBSx6pCKUrTcSgb884oiscV/ROt1Vhye+x+OIHcsodyA10gzZtrVtxIFV4l9I4g==", "license": "MIT", "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", - "metro-core": "0.80.12" + "metro-core": "0.81.0" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/metro-cache-key": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.12.tgz", - "integrity": "sha512-o4BspKnugg/pE45ei0LGHVuBJXwRgruW7oSFAeSZvBKA/sGr0UhOGY3uycOgWInnS3v5yTTfiBA9lHlNRhsvGA==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.81.0.tgz", + "integrity": "sha512-qX/IwtknP9bQZL78OK9xeSvLM/xlGfrs6SlUGgHvrxtmGTRSsxcyqxR+c+7ch1xr05n62Gin/O44QKg5V70rNQ==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/metro-config": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.12.tgz", - "integrity": "sha512-4rwOWwrhm62LjB12ytiuR5NgK1ZBNr24/He8mqCsC+HXZ+ATbrewLNztzbAZHtFsrxP4D4GLTGgh96pCpYLSAQ==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.81.0.tgz", + "integrity": "sha512-6CinEaBe3WLpRlKlYXXu8r1UblJhbwD6Gtnoib5U8j6Pjp7XxMG9h/DGMeNp9aGLDu1OieUqiXpFo7O0/rR5Kg==", "license": "MIT", "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.6.3", - "metro": "0.80.12", - "metro-cache": "0.80.12", - "metro-core": "0.80.12", - "metro-runtime": "0.80.12" + "metro": "0.81.0", + "metro-cache": "0.81.0", + "metro-core": "0.81.0", + "metro-runtime": "0.81.0" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/metro-core": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.12.tgz", - "integrity": "sha512-QqdJ/yAK+IpPs2HU/h5v2pKEdANBagSsc6DRSjnwSyJsCoHlmyJKCaCJ7KhWGx+N4OHxh37hoA8fc2CuZbx0Fw==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.81.0.tgz", + "integrity": "sha512-CVkM5YCOAFkNMvJai6KzA0RpztzfEKRX62/PFMOJ9J7K0uq/UkOFLxcgpcncMIrfy0PbfEj811b69tjULUQe1Q==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.80.12" + "metro-resolver": "0.81.0" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/metro-file-map": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.12.tgz", - "integrity": "sha512-sYdemWSlk66bWzW2wp79kcPMzwuG32x1ZF3otI0QZTmrnTaaTiGyhE66P1z6KR4n2Eu5QXiABa6EWbAQv0r8bw==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.81.0.tgz", + "integrity": "sha512-zMDI5uYhQCyxbye/AuFx/pAbsz9K+vKL7h1ShUXdN2fz4VUPiyQYRsRqOoVG1DsiCgzd5B6LW0YW77NFpjDQeg==", "license": "MIT", "dependencies": { "anymatch": "^3.0.3", @@ -9954,7 +7776,7 @@ "walker": "^1.0.7" }, "engines": { - "node": ">=18" + "node": ">=18.18" }, "optionalDependencies": { "fsevents": "^2.3.2" @@ -9976,81 +7798,73 @@ "license": "MIT" }, "node_modules/metro-minify-terser": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.12.tgz", - "integrity": "sha512-muWzUw3y5k+9083ZoX9VaJLWEV2Jcgi+Oan0Mmb/fBNMPqP9xVDuy4pOMn/HOiGndgfh/MK7s4bsjkyLJKMnXQ==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.81.0.tgz", + "integrity": "sha512-U2ramh3W822ZR1nfXgIk+emxsf5eZSg10GbQrT0ZizImK8IZ5BmJY+BHRIkQgHzWFpExOVxC7kWbGL1bZALswA==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/metro-resolver": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.12.tgz", - "integrity": "sha512-PR24gYRZnYHM3xT9pg6BdbrGbM/Cu1TcyIFBVlAk7qDAuHkUNQ1nMzWumWs+kwSvtd9eZGzHoucGJpTUEeLZAw==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.81.0.tgz", + "integrity": "sha512-Uu2Q+buHhm571cEwpPek8egMbdSTqmwT/5U7ZVNpK6Z2ElQBBCxd7HmFAslKXa7wgpTO2FAn6MqGeERbAtVDUA==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/metro-runtime": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.12.tgz", - "integrity": "sha512-LIx7+92p5rpI0i6iB4S4GBvvLxStNt6fF0oPMaUd1Weku7jZdfkCZzmrtDD9CSQ6EPb0T9NUZoyXIxlBa3wOCw==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.81.0.tgz", + "integrity": "sha512-6oYB5HOt37RuGz2eV4A6yhcl+PUTwJYLDlY9vhT+aVjbUWI6MdBCf69vc4f5K5Vpt+yOkjy+2LDwLS0ykWFwYw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/metro-source-map": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.12.tgz", - "integrity": "sha512-o+AXmE7hpvM8r8MKsx7TI21/eerYYy2DCDkWfoBkv+jNkl61khvDHlQn0cXZa6lrcNZiZkl9oHSMcwLLIrFmpw==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.81.0.tgz", + "integrity": "sha512-TzsVxhH83dyxg4A4+L1nzNO12I7ps5IHLjKGZH3Hrf549eiZivkdjYiq/S5lOB+p2HiQ+Ykcwtmcja95LIC62g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", + "@babel/traverse": "^7.25.3", + "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", + "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.80.12", + "metro-symbolicate": "0.81.0", "nullthrows": "^1.1.1", - "ob1": "0.80.12", + "ob1": "0.81.0", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "engines": { - "node": ">=18" - } - }, - "node_modules/metro-source-map/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "node": ">=18.18" } }, "node_modules/metro-symbolicate": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.12.tgz", - "integrity": "sha512-/dIpNdHksXkGHZXARZpL7doUzHqSNxgQ8+kQGxwpJuHnDhGkENxB5PS2QBaTDdEcmyTMjS53CN1rl9n1gR6fmw==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.81.0.tgz", + "integrity": "sha512-C/1rWbNTPYp6yzID8IPuQPpVGzJ2rbWYBATxlvQ9dfK5lVNoxcwz77hjcY8ISLsRRR15hyd/zbjCNKPKeNgE1Q==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.80.12", + "metro-source-map": "0.81.0", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "through2": "^2.0.1", @@ -10060,57 +7874,48 @@ "metro-symbolicate": "src/index.js" }, "engines": { - "node": ">=18" - } - }, - "node_modules/metro-symbolicate/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "node": ">=18.18" } }, "node_modules/metro-transform-plugins": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.12.tgz", - "integrity": "sha512-WQWp00AcZvXuQdbjQbx1LzFR31IInlkCDYJNRs6gtEtAyhwpMMlL2KcHmdY+wjDO9RPcliZ+Xl1riOuBecVlPA==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.81.0.tgz", + "integrity": "sha512-uErLAPBvttGCrmGSCa0dNHlOTk3uJFVEVWa5WDg6tQ79PRmuYRwzUgLhVzn/9/kyr75eUX3QWXN79Jvu4txt6Q==", "license": "MIT", "dependencies": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/metro-transform-worker": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.12.tgz", - "integrity": "sha512-KAPFN1y3eVqEbKLx1I8WOarHPqDMUa8WelWxaJCNKO/yHCP26zELeqTJvhsQup+8uwB6EYi/sp0b6TGoh6lOEA==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.81.0.tgz", + "integrity": "sha512-HrQ0twiruhKy0yA+9nK5bIe3WQXZcC66PXTvRIos61/EASLAP2DzEmW7IxN/MGsfZegN2UzqL2CG38+mOB45vg==", "license": "MIT", "dependencies": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "metro": "0.80.12", - "metro-babel-transformer": "0.80.12", - "metro-cache": "0.80.12", - "metro-cache-key": "0.80.12", - "metro-minify-terser": "0.80.12", - "metro-source-map": "0.80.12", - "metro-transform-plugins": "0.80.12", + "metro": "0.81.0", + "metro-babel-transformer": "0.81.0", + "metro-cache": "0.81.0", + "metro-cache-key": "0.81.0", + "metro-minify-terser": "0.81.0", + "metro-source-map": "0.81.0", + "metro-transform-plugins": "0.81.0", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/metro/node_modules/ci-info": { @@ -10129,18 +7934,18 @@ } }, "node_modules/metro/node_modules/hermes-estree": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.1.tgz", - "integrity": "sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.24.0.tgz", + "integrity": "sha512-LyoXLB7IFzeZW0EvAbGZacbxBN7t6KKSDqFJPo3Ydow7wDlrDjXwsdiAHV6XOdvEN9MEuWXsSIFN4tzpyrXIHw==", "license": "MIT" }, "node_modules/metro/node_modules/hermes-parser": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.1.tgz", - "integrity": "sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.24.0.tgz", + "integrity": "sha512-IJooSvvu2qNRe7oo9Rb04sUT4omtZqZqf9uq9WM25Tb6v3usmvA93UqfnnoWs5V0uYjEl9Al6MNU10MCGKLwpg==", "license": "MIT", "dependencies": { - "hermes-estree": "0.23.1" + "hermes-estree": "0.24.0" } }, "node_modules/metro/node_modules/ms": { @@ -10149,15 +7954,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/metro/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/metro/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -10216,18 +8012,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -10465,15 +8249,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "license": "MIT" }, - "node_modules/nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -10533,19 +8308,6 @@ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "license": "MIT" }, - "node_modules/node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -10556,24 +8318,30 @@ } }, "node_modules/npm-package-arg": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz", - "integrity": "sha512-xXxr8y5U0kl8dVkz2oK7yZjPBvqM2fwaO5l3Yg13p03v8+E3qQcD0JNhHzjL1vyGgxcKkD0cco+NLR72iuPk3g==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", "license": "ISC", "dependencies": { - "hosted-git-info": "^3.0.2", - "osenv": "^0.1.5", - "semver": "^5.6.0", - "validate-npm-package-name": "^3.0.0" + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-package-arg/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "license": "ISC", "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/npm-run-path": { @@ -10604,15 +8372,15 @@ "license": "MIT" }, "node_modules/ob1": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.12.tgz", - "integrity": "sha512-VMArClVT6LkhUGpnuEoBuyjG9rzUyEzg4PDkav6wK1cLhOK02gPCYFxoiB4mqVnrMhDpIzJcrGNAMVi9P+hXrw==", + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.81.0.tgz", + "integrity": "sha512-6Cvrkxt1tqaRdWqTAMcVYEiO5i1xcF9y7t06nFdjFqkfPsEloCf8WwhXdwBpNUkVYSQlSGS7cDgVQR86miBfBQ==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": ">=18.18" } }, "node_modules/object-assign": { @@ -10624,45 +8392,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -10706,17 +8435,16 @@ } }, "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "license": "MIT", "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10810,15 +8538,6 @@ "node": ">=4" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -10828,17 +8547,6 @@ "node": ">=0.10.0" } }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -11166,19 +8874,10 @@ "node": ">=4.0.0" } }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -11196,7 +8895,7 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -11216,56 +8915,46 @@ } }, "node_modules/pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "license": "MIT", "dependencies": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/pretty-format/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "license": "MIT", - "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, "engines": { - "node": ">=4" - } - }, - "node_modules/pretty-format/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-format/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -11359,16 +9048,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/querystring": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", - "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "license": "MIT", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -11423,9 +9102,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -11506,47 +9185,48 @@ "license": "MIT" }, "node_modules/react-native": { - "version": "0.74.5", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.74.5.tgz", - "integrity": "sha512-Bgg2WvxaGODukJMTZFTZBNMKVaROHLwSb8VAGEdrlvKwfb1hHg/3aXTUICYk7dwgAnb+INbGMwnF8yeAgIUmqw==", + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.76.2.tgz", + "integrity": "sha512-mkEBKGOmJxhfq8IOsvmk0QuTzlBt9vS+uo0gwbqfUmEDqoC359v80zhUf94WimYBrBkpRQWFbEu5iqMDHrYzlQ==", "license": "MIT", "dependencies": { "@jest/create-cache-key-function": "^29.6.3", - "@react-native-community/cli": "13.6.9", - "@react-native-community/cli-platform-android": "13.6.9", - "@react-native-community/cli-platform-ios": "13.6.9", - "@react-native/assets-registry": "0.74.87", - "@react-native/codegen": "0.74.87", - "@react-native/community-cli-plugin": "0.74.87", - "@react-native/gradle-plugin": "0.74.87", - "@react-native/js-polyfills": "0.74.87", - "@react-native/normalize-colors": "0.74.87", - "@react-native/virtualized-lists": "0.74.87", + "@react-native/assets-registry": "0.76.2", + "@react-native/codegen": "0.76.2", + "@react-native/community-cli-plugin": "0.76.2", + "@react-native/gradle-plugin": "0.76.2", + "@react-native/js-polyfills": "0.76.2", + "@react-native/normalize-colors": "0.76.2", + "@react-native/virtualized-lists": "0.76.2", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", + "babel-jest": "^29.7.0", + "babel-plugin-syntax-hermes-parser": "^0.23.1", "base64-js": "^1.5.1", "chalk": "^4.0.0", + "commander": "^12.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", + "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.6.3", "jsc-android": "^250231.0.0", "memoize-one": "^5.0.0", - "metro-runtime": "^0.80.3", - "metro-source-map": "^0.80.3", + "metro-runtime": "^0.81.0", + "metro-source-map": "^0.81.0", "mkdirp": "^0.5.1", "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", + "pretty-format": "^29.7.0", "promise": "^8.3.0", - "react-devtools-core": "^5.0.0", + "react-devtools-core": "^5.3.1", "react-refresh": "^0.14.0", - "react-shallow-renderer": "^16.15.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.24.0-canary-efb381bbf-20230505", + "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", - "ws": "^6.2.2", + "ws": "^6.2.3", "yargs": "^17.6.2" }, "bin": { @@ -11557,7 +9237,7 @@ }, "peerDependencies": { "@types/react": "^18.2.6", - "react": "18.2.0" + "react": "^18.2.0" }, "peerDependenciesMeta": { "@types/react": { @@ -11566,9 +9246,9 @@ } }, "node_modules/react-native-safe-area-context": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.10.5.tgz", - "integrity": "sha512-Wyb0Nqw2XJ6oZxW/cK8k5q7/UAhg/wbEG6UVf89rQqecDZTDA5ic//P9J6VvJRVZerzGmxWQpVuM7f+PRYUM4g==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.12.0.tgz", + "integrity": "sha512-ukk5PxcF4p3yu6qMZcmeiZgowhb5AsKRnil54YFUUAXVIS7PJcMHGGC+q44fCiBg44/1AJk5njGMez1m9H0BVQ==", "license": "MIT", "peerDependencies": { "react": "*", @@ -11576,9 +9256,9 @@ } }, "node_modules/react-native-screens": { - "version": "3.31.1", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.31.1.tgz", - "integrity": "sha512-8fRW362pfZ9y4rS8KY5P3DFScrmwo/vu1RrRMMx0PNHbeC9TLq0Kw1ubD83591yz64gLNHFLTVkTJmWeWCXKtQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.1.0.tgz", + "integrity": "sha512-tCBwe7fRMpoi/nIgZxE86N8b2SH8d5PlfGaQO8lgqlXqIyvwqm3u1HJCaA0tsacPyzhW7vVtRfQyq9e1j0S2gA==", "license": "MIT", "dependencies": { "react-freeze": "^1.0.0", @@ -11589,59 +9269,22 @@ "react-native": "*" } }, - "node_modules/react-native/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/react-native/node_modules/@react-native/normalize-colors": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.87.tgz", - "integrity": "sha512-Xh7Nyk/MPefkb0Itl5Z+3oOobeG9lfLb7ZOY2DKpFnoCE1TzBmib9vMNdFaLdSxLIP+Ec6icgKtdzYg8QUPYzA==", - "license": "MIT" - }, - "node_modules/react-native/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/react-native/node_modules/@types/yargs": { - "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "node_modules/react-native/node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.23.1.tgz", + "integrity": "sha512-uNLD0tk2tLUjGFdmCk+u/3FEw2o+BAwW4g+z2QVlxJrzZYOOPADroEcNtTPt5lNiScctaUmnsTkVEnOwZUOLhA==", "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "hermes-parser": "0.23.1" } }, - "node_modules/react-native/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "node_modules/react-native/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "license": "MIT", - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, "engines": { - "node": ">= 10" + "node": ">=18" } }, "node_modules/react-native/node_modules/promise": { @@ -11653,18 +9296,24 @@ "asap": "~2.0.6" } }, - "node_modules/react-native/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT" - }, "node_modules/react-native/node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "license": "MIT" }, + "node_modules/react-native/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/react-native/node_modules/ws": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", @@ -11683,19 +9332,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-shallow-renderer": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "license": "MIT", - "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -11776,29 +9412,10 @@ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.8.4" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", - "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/regexpu-core": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", @@ -11858,12 +9475,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "license": "ISC" - }, "node_modules/requireg": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/requireg/-/requireg-0.2.2.tgz", @@ -11912,6 +9523,12 @@ "node": ">=8" } }, + "node_modules/resolve-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-workspace-root/-/resolve-workspace-root-2.0.0.tgz", + "integrity": "sha512-IsaBUZETJD5WsI11Wt8PKHwaIe45or6pwNc8yflvLJ4DWtImK9kuLoH5kUva/2Mmx/RdIyr4aONNSa2v9LTJsw==", + "license": "MIT" + }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -11983,30 +9600,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -12027,23 +9620,6 @@ ], "license": "MIT" }, - "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-regex": "^1.1.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", @@ -12082,15 +9658,15 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz", + "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==", "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", @@ -12120,6 +9696,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/send/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -12267,44 +9852,6 @@ "node": ">= 0.8" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -12350,29 +9897,11 @@ "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12430,47 +9959,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, "node_modules/slugify": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", @@ -12481,12 +9969,12 @@ } }, "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", "engines": { - "node": ">= 8" + "node": ">=0.10.0" } }, "node_modules/source-map-js": { @@ -12717,55 +10205,6 @@ "node": ">=8" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -12827,12 +10266,6 @@ "node": ">=0.10.0" } }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "license": "MIT" - }, "node_modules/structured-headers": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-0.4.1.tgz", @@ -12840,14 +10273,14 @@ "license": "MIT" }, "node_modules/sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "7.1.6", + "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", @@ -12858,7 +10291,16 @@ "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, "node_modules/sucrase/node_modules/commander": { @@ -12871,21 +10313,35 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -13119,11 +10575,19 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "license": "MIT" + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } }, "node_modules/thenify": { "version": "3.3.1", @@ -13213,32 +10677,6 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, - "node_modules/traverse": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.10.tgz", - "integrity": "sha512-hN4uFRxbK+PX56DxYiGHsTn2dME3TVr9vbNqlQGcGcPhJAn+tdP126iA+TArMpI4YSgnTkMWyoLl5bf81Hi5TA==", - "license": "MIT", - "dependencies": { - "gopd": "^1.0.1", - "typedarray.prototype.slice": "^1.0.3", - "which-typed-array": "^1.1.15" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -13272,99 +10710,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.3.tgz", - "integrity": "sha512-8WbVAQAUlENo1q3c3zZYuy5k9VzBQvp8AX9WOtbvyWlLM1v5JaSRmjubLjzHF4JFtptjH/5c/i95yaElvcjC0A==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-errors": "^1.3.0", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-offset": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typescript": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", @@ -13405,19 +10750,13 @@ "node": "*" } }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "node_modules/undici": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", + "integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==", "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=18.17" } }, "node_modules/undici-types": { @@ -13550,12 +10889,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/url-join": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz", - "integrity": "sha512-EGXjXJZhIHiQMK2pQukuFcL303nskqIRzWvPvV5O8miOfwoUb9G+a/Cld60kUyeaybEI94wvVClT10DtfeAExA==", - "license": "MIT" - }, "node_modules/use-latest-callback": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.1.tgz", @@ -13589,18 +10922,13 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/valid-url": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", - "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==" - }, "node_modules/validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "license": "ISC", - "dependencies": { - "builtins": "^1.0.3" + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/vary": { @@ -13651,6 +10979,15 @@ "defaults": "^1.0.3" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -13711,51 +11048,10 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "license": "MIT", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "license": "ISC" - }, - "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/wonka": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz", - "integrity": "sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.4.tgz", + "integrity": "sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg==", "license": "MIT" }, "node_modules/wrap-ansi": { @@ -13932,18 +11228,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, - "node_modules/yaml": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", - "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -13982,27 +11266,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-validation-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-2.1.0.tgz", - "integrity": "sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.18.0" - } } } } diff --git a/package.json b/package.json index a7b1842..1bd89c5 100644 --- a/package.json +++ b/package.json @@ -9,24 +9,24 @@ "web": "expo start --web" }, "dependencies": { - "@react-native-picker/picker": "2.7.5", + "@react-native-picker/picker": "^2.9.0", "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", - "expo": "~51.0.39", - "expo-status-bar": "~1.12.1", + "expo": "^52.0.8", + "expo-status-bar": "~2.0.0", "he": "^1.2.0", "i18n-js": "^4.5.0", "i18next": "^23.16.5", - "react": "18.2.0", + "react": "^18.3.1", "react-i18next": "^15.1.1", - "react-native": "0.74.5", - "react-native-safe-area-context": "4.10.5", - "react-native-screens": "3.31.1" + "react-native": "^0.76.2", + "react-native-safe-area-context": "^4.12.0", + "react-native-screens": "~4.1.0" }, "devDependencies": { "@babel/core": "^7.20.0", "@types/he": "^1.2.3", - "@types/react": "~18.2.45", + "@types/react": "~18.3.12", "typescript": "~5.3.3" }, "private": true -- GitLab From 3724aaf580b4577464d915ef35c2e9b48ee32525 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 19 Nov 2024 21:35:39 +0100 Subject: [PATCH 004/283] fix: correction du path --- screens/Home/Home.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/Home/Home.tsx b/screens/Home/Home.tsx index a4ac3f2..5e4d8d6 100644 --- a/screens/Home/Home.tsx +++ b/screens/Home/Home.tsx @@ -25,7 +25,7 @@ export default function Home({navigation}: Props) { } return ( <View> - <Image source={require('../assets/FondTemplate.png')} style={styles.image}/> + <Image source={require('../../assets/FondTemplate.png')} style={styles.image}/> <View style={styles.container}> <View style={styles.titleContainer}> <Text style={styles.titleText}>{"SKITSKOLA"}</Text> -- GitLab From 340e859ef04f95b2dd9a3728deff66465efedcd5 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 19 Nov 2024 22:05:19 +0100 Subject: [PATCH 005/283] feat: traduction screen Question --- screens/Question/QuestionHeader.tsx | 8 +++++--- translation/languages/en.json | 5 +++++ translation/languages/fr.json | 5 +++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/screens/Question/QuestionHeader.tsx b/screens/Question/QuestionHeader.tsx index a06798d..6500cc6 100644 --- a/screens/Question/QuestionHeader.tsx +++ b/screens/Question/QuestionHeader.tsx @@ -1,20 +1,22 @@ import { View, StyleSheet, Text } from "react-native"; import { TextsStyles } from "../../styles/TextsStyles"; import QuizModel from "../../model/QuizModel"; +import {useTranslation} from "react-i18next"; interface Props { quiz?: QuizModel; } export default function QuestionHeader({quiz}: Props) { + const {t} = useTranslation(); return ( <View style={styles.container}> <View style={styles.infoQuizContainer}> - <Text style={TextsStyles.infoQuizText}>SCORE : {quiz ? quiz.score : 0}</Text> - <Text style={TextsStyles.infoQuizText}>CATEGORY : {quiz ? quiz.category : "?"}</Text> + <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.score")} : {quiz ? quiz.score : 0}</Text> + <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.category")} : {quiz ? quiz.category : "?"}</Text> </View> <View style={styles.QuizNumContainer}> - <Text style={TextsStyles.titleText}>QUESTION {quiz ? quiz.nbActualQuestion : "?"}/{quiz ? quiz.nbQuestions : "?"}</Text> + <Text style={TextsStyles.titleText}>{t("app.screens.question.question")} {quiz ? quiz.nbActualQuestion : "?"}/{quiz ? quiz.nbQuestions : "?"}</Text> </View> <View style={styles.QuizQuestionContainer}> <Text style={TextsStyles.subtitleText}>{quiz ? quiz.questions[quiz.nbActualQuestion - 1].question : "Loading..."}</Text> diff --git a/translation/languages/en.json b/translation/languages/en.json index eb76cb0..f9095aa 100644 --- a/translation/languages/en.json +++ b/translation/languages/en.json @@ -7,6 +7,11 @@ "createQuiz": "Create a Quiz", "joinQuiz": "Join a Quiz", "codeQuiz": "Quiz Code" + }, + "question": { + "score" : "SCORE", + "category" : "CATEGORIE", + "question" : "QUESTION" } } } diff --git a/translation/languages/fr.json b/translation/languages/fr.json index 8a93a2f..0a369d3 100644 --- a/translation/languages/fr.json +++ b/translation/languages/fr.json @@ -7,6 +7,11 @@ "createQuiz": "Créer un Quizz", "joinQuiz": "Rejoindre un Quizz", "codeQuiz": "Code de la partie" + }, + "question": { + "score" : "SCORE", + "category" : "CATEGORY", + "question" : "QUESTION" } } } -- GitLab From ae30001aad39f5c556bb296c8133b37a77ce135d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 19 Nov 2024 22:16:42 +0100 Subject: [PATCH 006/283] feat: traduction screen endQuiz --- screens/FinQuiz/FinQuiz.tsx | 10 ++++++---- translation/languages/en.json | 7 +++++++ translation/languages/fr.json | 7 +++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/screens/FinQuiz/FinQuiz.tsx b/screens/FinQuiz/FinQuiz.tsx index 3320e82..aa5cfc6 100644 --- a/screens/FinQuiz/FinQuiz.tsx +++ b/screens/FinQuiz/FinQuiz.tsx @@ -2,6 +2,7 @@ import { View, StyleSheet, Image, Text } from "react-native"; import DefaultButton from "../../components/DefaultButton"; import { TextsStyles } from "../../styles/TextsStyles"; import { ButtonsStyles } from "../../styles/ButtonsStyles"; +import {useTranslation} from "react-i18next"; interface Props { navigation: any; @@ -10,6 +11,7 @@ interface Props { export default function FinQuiz({ navigation, route }: Props) { const { score, quiz } = route.params; + const {t} = useTranslation(); const handleButtonRePlayPressed = () => { quiz.nbActualQuestion = 1; quiz.score = 0; @@ -31,11 +33,11 @@ export default function FinQuiz({ navigation, route }: Props) { return ( <View style={styles.container}> <Image source={require('../../assets/FondTemplate.png')} style={styles.image}/> - <Text style={TextsStyles.titleText}>Score : {score}</Text> - <Text style={TextsStyles.subtitleText}>{((score/quiz.nbQuestions)*100 > 50 ) ? "Congratulations, you won!" : "Too bad, you lost!"}</Text> + <Text style={TextsStyles.titleText}>{t("app.screens.endQuiz.score")} : {score}</Text> + <Text style={TextsStyles.subtitleText}>{((score/quiz.nbQuestions)*100 > 50 ) ? t("app.screens.endQuiz.lose") : t("app.screens.endQuiz.win")}</Text> <View style={styles.buttonContainer}> - <DefaultButton text={"Play again"} handleButtonPressed={handleButtonRePlayPressed} buttonStyle={ButtonsStyles.defaultButton}></DefaultButton> - <DefaultButton text={"Go to home"} handleButtonPressed={handleButtonHomePressed}></DefaultButton> + <DefaultButton text={t("app.screens.endQuiz.replay")} handleButtonPressed={handleButtonRePlayPressed} buttonStyle={ButtonsStyles.defaultButton}></DefaultButton> + <DefaultButton text={t("app.screens.endQuiz.goHome")} handleButtonPressed={handleButtonHomePressed}></DefaultButton> </View> </View> ) diff --git a/translation/languages/en.json b/translation/languages/en.json index f9095aa..017e7a5 100644 --- a/translation/languages/en.json +++ b/translation/languages/en.json @@ -12,6 +12,13 @@ "score" : "SCORE", "category" : "CATEGORIE", "question" : "QUESTION" + }, + "endQuiz": { + "win": "Congratulations, you won !", + "lose":"Congratulations, you won !", + "replay": "Play again", + "goHome": "Go to home", + "score": "Score" } } } diff --git a/translation/languages/fr.json b/translation/languages/fr.json index 0a369d3..49b4ae4 100644 --- a/translation/languages/fr.json +++ b/translation/languages/fr.json @@ -12,6 +12,13 @@ "score" : "SCORE", "category" : "CATEGORY", "question" : "QUESTION" + }, + "endQuiz": { + "win": "Félicitations, vous avez gagné !", + "lose":"Dommage, tu as perdu !", + "replay": "Rejouer", + "goHome": "Retour à l'accueil", + "score": "Score" } } } -- GitLab From f71d074a814dc8b8db1cead4d5c8b40e0db98406 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 19 Nov 2024 22:29:05 +0100 Subject: [PATCH 007/283] feat: traduction screen createQuiz --- screens/CreationQuiz/CreationQuizBody.tsx | 10 ++++++---- translation/languages/en.json | 6 ++++++ translation/languages/fr.json | 6 ++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/screens/CreationQuiz/CreationQuizBody.tsx b/screens/CreationQuiz/CreationQuizBody.tsx index 82c0047..92d01c0 100644 --- a/screens/CreationQuiz/CreationQuizBody.tsx +++ b/screens/CreationQuiz/CreationQuizBody.tsx @@ -2,6 +2,7 @@ import ComboBox from "../../components/comboBox/ComboBox"; import DefaultButton from "../../components/DefaultButton"; import {StyleSheet, View, Text} from "react-native"; import {useState} from "react"; +import {useTranslation} from "react-i18next"; const optionsDifficulty = [ 'easy', 'medium', 'hard']; const optionsQuestions = ['1', '50']; @@ -30,6 +31,7 @@ export default function CreationQuizBody({navigation}:Props) { const [difficulty, setDifficulty] = useState('easy'); const [nbQuestions, setNbQuestions] = useState('1'); const [theme, setTheme] = useState('General Knowledge'); + const {t} = useTranslation(); const handleAddQuizPressed = () => { navigation.reset({ @@ -46,12 +48,12 @@ export default function CreationQuizBody({navigation}:Props) { return ( <View style={styles.container}> <View style={styles.comboBoxContainer}> - <ComboBox title={"Difficulty"} options={optionsDifficulty} selectOption={setDifficulty} type={0}/> - <ComboBox title={"Nb. of questions"} options={optionsQuestions} selectOption={setNbQuestions} type={1}/> - <ComboBox title={"Theme"} options={optionsTheme} selectOption={setTheme} type={0}/> + <ComboBox title={t("app.screens.creationQuiz.difficulty")} options={optionsDifficulty} selectOption={setDifficulty} type={0}/> + <ComboBox title={t("app.screens.creationQuiz.nbQuestions")} options={optionsQuestions} selectOption={setNbQuestions} type={1}/> + <ComboBox title={t("app.screens.creationQuiz.theme")} options={optionsTheme} selectOption={setTheme} type={0}/> </View> <View style={styles.buttonContainer}> - <DefaultButton text={"Add Quiz "} handleButtonPressed={handleAddQuizPressed} buttonStyle={styles.buttonStyle}/> + <DefaultButton text={t("app.screens.creationQuiz.addQuiz")} handleButtonPressed={handleAddQuizPressed} buttonStyle={styles.buttonStyle}/> </View> </View> diff --git a/translation/languages/en.json b/translation/languages/en.json index 017e7a5..d28a947 100644 --- a/translation/languages/en.json +++ b/translation/languages/en.json @@ -19,6 +19,12 @@ "replay": "Play again", "goHome": "Go to home", "score": "Score" + }, + "creationQuiz": { + "difficulty": "Difficulty", + "nbQuestions": "Nb. of questions", + "theme": "Theme", + "addQuiz": "Add a Quiz" } } } diff --git a/translation/languages/fr.json b/translation/languages/fr.json index 49b4ae4..ec21e0f 100644 --- a/translation/languages/fr.json +++ b/translation/languages/fr.json @@ -19,6 +19,12 @@ "replay": "Rejouer", "goHome": "Retour à l'accueil", "score": "Score" + }, + "creationQuiz": { + "difficulty": "Difficulté", + "nbQuestions": "Nb. de questions", + "theme": "Thème", + "addQuiz": "Ajouter un Quiz" } } } -- GitLab From e0e59c0e95871bd140af7d49d3f66081941bdfae Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 19 Nov 2024 22:29:52 +0100 Subject: [PATCH 008/283] =?UTF-8?q?fix:=20erreur=20dans=20la=20traduction?= =?UTF-8?q?=20cat=C3=A9gory=20de=20l'=C3=A9cran=20question?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- translation/languages/en.json | 2 +- translation/languages/fr.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/translation/languages/en.json b/translation/languages/en.json index d28a947..078e5e2 100644 --- a/translation/languages/en.json +++ b/translation/languages/en.json @@ -10,7 +10,7 @@ }, "question": { "score" : "SCORE", - "category" : "CATEGORIE", + "category" : "CATEGORY", "question" : "QUESTION" }, "endQuiz": { diff --git a/translation/languages/fr.json b/translation/languages/fr.json index ec21e0f..0d8f298 100644 --- a/translation/languages/fr.json +++ b/translation/languages/fr.json @@ -10,7 +10,7 @@ }, "question": { "score" : "SCORE", - "category" : "CATEGORY", + "category" : "CATEGORIE", "question" : "QUESTION" }, "endQuiz": { -- GitLab From 5a642df9197517c2b2d216c1706be2d9951c8f10 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 20 Nov 2024 10:44:50 +0100 Subject: [PATCH 009/283] fix: rename des screens --- App.tsx | 1 - routes/StackNavigator.tsx | 12 ++++++------ screens/CreationQuiz/CreationQuiz.tsx | 9 --------- screens/{FinQuiz/FinQuiz.tsx => EndQuiz/EndQuiz.tsx} | 4 ++-- screens/GenerateQuiz/GenerateQuiz.tsx | 9 +++++++++ .../GenerateQuizBody.tsx} | 5 +++-- .../GenerateQuizHeader.tsx} | 0 .../Question.tsx => PlayingQuiz/PlayingQuiz.tsx} | 12 ++++++------ .../PlayingQuizBody.tsx} | 2 +- .../PlayingQuizHeader.tsx} | 2 +- 10 files changed, 28 insertions(+), 28 deletions(-) delete mode 100644 screens/CreationQuiz/CreationQuiz.tsx rename screens/{FinQuiz/FinQuiz.tsx => EndQuiz/EndQuiz.tsx} (95%) create mode 100644 screens/GenerateQuiz/GenerateQuiz.tsx rename screens/{CreationQuiz/CreationQuizBody.tsx => GenerateQuiz/GenerateQuizBody.tsx} (95%) rename screens/{CreationQuiz/CreationQuizHeader.tsx => GenerateQuiz/GenerateQuizHeader.tsx} (100%) rename screens/{Question/Question.tsx => PlayingQuiz/PlayingQuiz.tsx} (87%) rename screens/{Question/QuestionBody.tsx => PlayingQuiz/PlayingQuizBody.tsx} (96%) rename screens/{Question/QuestionHeader.tsx => PlayingQuiz/PlayingQuizHeader.tsx} (96%) diff --git a/App.tsx b/App.tsx index 2633a5c..96cb4a2 100644 --- a/App.tsx +++ b/App.tsx @@ -3,7 +3,6 @@ import {I18nextProvider, useTranslation} from "react-i18next"; import i18n from "./translation/i18n"; // Importez l'instance déjà initialisée import { SafeAreaProvider } from "react-native-safe-area-context"; import StackNavigator from "./routes/StackNavigator"; -import Question from "./screens/Question/Question"; export default function App() { LogBox.ignoreAllLogs(true); diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index 28b5292..eeae3a4 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -1,9 +1,9 @@ import {createNativeStackNavigator} from "@react-navigation/native-stack"; import {NavigationContainer} from "@react-navigation/native"; import Home from "../screens/Home/Home"; -import Question from "../screens/Question/Question"; -import CreationQuiz from "../screens/CreationQuiz/CreationQuiz"; -import FinQuiz from "../screens/FinQuiz/FinQuiz"; +import GenerateQuiz from "../screens/GenerateQuiz/GenerateQuiz"; +import EndQuiz from "../screens/EndQuiz/EndQuiz"; +import PlayingQuiz from "../screens/PlayingQuiz/PlayingQuiz"; const Stack = createNativeStackNavigator(); @@ -12,9 +12,9 @@ export default function StackNavigator() { <NavigationContainer> <Stack.Navigator initialRouteName="Home"> <Stack.Screen name="Home" component={Home} options={{ headerShown: false }}/> - <Stack.Screen name="CreateQuiz" component={CreationQuiz} options={{ headerShown: false }}/> - <Stack.Screen name="Question" component={Question} options={{ headerShown: false }}/> - <Stack.Screen name="FinQuiz" component={FinQuiz} options={{ headerShown: false }}/> + <Stack.Screen name="CreateQuiz" component={GenerateQuiz} options={{ headerShown: false }}/> + <Stack.Screen name="PlayingQuiz" component={PlayingQuiz} options={{ headerShown: false }}/> + <Stack.Screen name="EndQuiz" component={EndQuiz} options={{ headerShown: false }}/> </Stack.Navigator> </NavigationContainer> ); diff --git a/screens/CreationQuiz/CreationQuiz.tsx b/screens/CreationQuiz/CreationQuiz.tsx deleted file mode 100644 index 46c1061..0000000 --- a/screens/CreationQuiz/CreationQuiz.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import TemplateScreen from "../../templates/TemplateScreen"; -import CreationQuizBody from "./CreationQuizBody"; - -export default function CreationQuiz({navigation}: any) { - return ( - <TemplateScreen childrenBot={<CreationQuizBody navigation={navigation}></CreationQuizBody>}></TemplateScreen> - ) -} - diff --git a/screens/FinQuiz/FinQuiz.tsx b/screens/EndQuiz/EndQuiz.tsx similarity index 95% rename from screens/FinQuiz/FinQuiz.tsx rename to screens/EndQuiz/EndQuiz.tsx index aa5cfc6..ea72db6 100644 --- a/screens/FinQuiz/FinQuiz.tsx +++ b/screens/EndQuiz/EndQuiz.tsx @@ -9,7 +9,7 @@ interface Props { route: any; } -export default function FinQuiz({ navigation, route }: Props) { +export default function EndQuiz({ navigation, route }: Props) { const { score, quiz } = route.params; const {t} = useTranslation(); const handleButtonRePlayPressed = () => { @@ -19,7 +19,7 @@ export default function FinQuiz({ navigation, route }: Props) { index: 0, routes: [ { - name: "Question", + name: "PlayingQuiz", params: {quiz: quiz}, }, ] diff --git a/screens/GenerateQuiz/GenerateQuiz.tsx b/screens/GenerateQuiz/GenerateQuiz.tsx new file mode 100644 index 0000000..014f469 --- /dev/null +++ b/screens/GenerateQuiz/GenerateQuiz.tsx @@ -0,0 +1,9 @@ +import TemplateScreen from "../../templates/TemplateScreen"; +import GenerateQuizBody from "./GenerateQuizBody"; + +export default function GenerateQuiz({navigation}: any) { + return ( + <TemplateScreen childrenBot={<GenerateQuizBody navigation={navigation}></GenerateQuizBody>}></TemplateScreen> + ) +} + diff --git a/screens/CreationQuiz/CreationQuizBody.tsx b/screens/GenerateQuiz/GenerateQuizBody.tsx similarity index 95% rename from screens/CreationQuiz/CreationQuizBody.tsx rename to screens/GenerateQuiz/GenerateQuizBody.tsx index 92d01c0..9330c12 100644 --- a/screens/CreationQuiz/CreationQuizBody.tsx +++ b/screens/GenerateQuiz/GenerateQuizBody.tsx @@ -25,7 +25,7 @@ interface Props { navigation: any; } -export default function CreationQuizBody({navigation}:Props) { +export default function GenerateQuizBody({navigation}:Props) { const [isModalVisible, setIsModalVisible] = useState(false); const [difficulty, setDifficulty] = useState('easy'); @@ -34,11 +34,12 @@ export default function CreationQuizBody({navigation}:Props) { const {t} = useTranslation(); const handleAddQuizPressed = () => { + console.log(difficulty); navigation.reset({ index: 0, routes: [ { - name: "Question", + name: "PlayingQuiz", params: {nbQuestionParam: nbQuestions, difficultyParam: difficulty, themeParam: theme}, }, ] diff --git a/screens/CreationQuiz/CreationQuizHeader.tsx b/screens/GenerateQuiz/GenerateQuizHeader.tsx similarity index 100% rename from screens/CreationQuiz/CreationQuizHeader.tsx rename to screens/GenerateQuiz/GenerateQuizHeader.tsx diff --git a/screens/Question/Question.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx similarity index 87% rename from screens/Question/Question.tsx rename to screens/PlayingQuiz/PlayingQuiz.tsx index 1765397..91dc961 100644 --- a/screens/Question/Question.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -1,6 +1,6 @@ import TemplateScreen from "../../templates/TemplateScreen"; -import QuestionBody from "./QuestionBody"; -import QuestionHeader from "./QuestionHeader"; +import PlayingQuizBody from "./PlayingQuizBody"; +import PlayingQuizHeader from "./PlayingQuizHeader"; import {useEffect, useState} from "react"; import {transformToQuizModel} from "../../helper/QuizHelper"; import QuizModel from "../../model/QuizModel"; @@ -26,7 +26,7 @@ interface Props { navigation: any; } -export default function Question({route, navigation}:Props) { +export default function PlayingQuiz({route, navigation}:Props) { const getIdOfCategory = (category: string) => { for (let i = 0; i < optionsTheme.length; i++) { if (optionsTheme[i] === category) { @@ -102,15 +102,15 @@ export default function Question({route, navigation}:Props) { setIsAlreadyPlayed(false); return { ...prevQuiz, nbActualQuestion: prevQuiz.nbActualQuestion + 1 }; } - navigation.navigate("FinQuiz", { score: prevQuiz?.score, quiz: quiz }); + navigation.navigate("EndQuiz", { score: prevQuiz?.score, quiz: quiz }); return prevQuiz; // Fin du quiz si toutes les questions ont été posées }); }; return ( <TemplateScreen - childrenTop={<QuestionHeader quiz={quiz} ></QuestionHeader>} - childrenBot={<QuestionBody quiz={quiz} onAnswerSelected={handleAnswer} isAlreadyPlayed={isAlreadyPlayed} setIsAlreadyPlayed={setIsAlreadyPlayed}></QuestionBody>} + childrenTop={<PlayingQuizHeader quiz={quiz} ></PlayingQuizHeader>} + childrenBot={<PlayingQuizBody quiz={quiz} onAnswerSelected={handleAnswer} isAlreadyPlayed={isAlreadyPlayed} setIsAlreadyPlayed={setIsAlreadyPlayed}></PlayingQuizBody>} /> ); } \ No newline at end of file diff --git a/screens/Question/QuestionBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx similarity index 96% rename from screens/Question/QuestionBody.tsx rename to screens/PlayingQuiz/PlayingQuizBody.tsx index 26c3c2f..4ea806f 100644 --- a/screens/Question/QuestionBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -13,7 +13,7 @@ interface Props { setIsAlreadyPlayed: (isAlreadyPlayed: boolean) => void; } -export default function QuestionBody({ quiz, onAnswerSelected, isAlreadyPlayed, setIsAlreadyPlayed }: Props) { +export default function PlayingQuizBody({ quiz, onAnswerSelected, isAlreadyPlayed, setIsAlreadyPlayed }: Props) { const [selectedAnswerIndex, setSelectedAnswerIndex] = useState<number | null>(null); const [isCorrect, setIsCorrect] = useState<boolean | null>(null); diff --git a/screens/Question/QuestionHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx similarity index 96% rename from screens/Question/QuestionHeader.tsx rename to screens/PlayingQuiz/PlayingQuizHeader.tsx index 6500cc6..6151f62 100644 --- a/screens/Question/QuestionHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -7,7 +7,7 @@ interface Props { quiz?: QuizModel; } -export default function QuestionHeader({quiz}: Props) { +export default function PlayingQuizHeader({quiz}: Props) { const {t} = useTranslation(); return ( <View style={styles.container}> -- GitLab From 4f9a39809333fbf8c94c1cb7bbc882be6fc16f7f Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 20 Nov 2024 13:37:36 +0100 Subject: [PATCH 010/283] feat: ajout du template Mono --- assets/Name.png | Bin 0 -> 10053 bytes templates/TemplateMono.tsx | 44 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 assets/Name.png create mode 100644 templates/TemplateMono.tsx diff --git a/assets/Name.png b/assets/Name.png new file mode 100644 index 0000000000000000000000000000000000000000..48c1d284cce9aa4a66f1d8cfd47f84b5cfa72d2c GIT binary patch literal 10053 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 literal 0 HcmV?d00001 diff --git a/templates/TemplateMono.tsx b/templates/TemplateMono.tsx new file mode 100644 index 0000000..0452982 --- /dev/null +++ b/templates/TemplateMono.tsx @@ -0,0 +1,44 @@ +import { View, Image, StyleSheet } from "react-native"; + +interface Props{ + children?: React.ReactNode +} + +export default function TemplateMono({children} : Props) { + return( + <View style={styles.containerGlobal}> + <View style={styles.containerHeader}> + <Image source={require('../assets/Name.png')} style={styles.image}/> + </View> + <View style={styles.containerBody}> + {children} + </View> + </View> + ); +} + +const styles = StyleSheet.create({ + containerGlobal: { + display: 'flex', + width: '100%', + height: '100%', + }, + containerHeader: { + height: '12%', + justifyContent: 'flex-end', + alignItems: 'center', + shadowColor: '#171717', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.2, + shadowRadius: 3, + elevation: 5, + backgroundColor: '#fff' + }, + containerBody: { + height: '83%', + }, + image: { + height: '60%', + width: '60%' + } +}); \ No newline at end of file -- GitLab From 85c53dde12acc792452fe8f7d8a71b921db51d80 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 20 Nov 2024 13:55:42 +0100 Subject: [PATCH 011/283] feat: ajout template duo --- assets/FondDuo.png | Bin 0 -> 161291 bytes templates/TemplateDuo.tsx | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 assets/FondDuo.png create mode 100644 templates/TemplateDuo.tsx diff --git a/assets/FondDuo.png b/assets/FondDuo.png new file mode 100644 index 0000000000000000000000000000000000000000..a2abf1e8d90e9daf13cc5217a54b9ae861103cc2 GIT binary patch literal 161291 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%0A&5GPFW#`1N%J9}m8ljWF9 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 literal 0 HcmV?d00001 diff --git a/templates/TemplateDuo.tsx b/templates/TemplateDuo.tsx new file mode 100644 index 0000000..786c5ec --- /dev/null +++ b/templates/TemplateDuo.tsx @@ -0,0 +1,45 @@ +import { ImageBackground, Text, View, StyleSheet } from "react-native"; + +interface Props{ + childrenHeader?: React.ReactNode + childrenBody?: React.ReactNode +} + +export default function TemplateDuo({childrenHeader, childrenBody}: Props) { + return ( + <View style={styles.container}> + <ImageBackground source={require('../assets/FondDuo.png')} style={styles.image}> + <View style={styles.containerHeader}> + {childrenHeader} + </View> + <View style={styles.containerBody}> + {childrenBody} + </View> + </ImageBackground> + </View> + ); +} + + +const styles = StyleSheet.create({ + container: { + display: 'flex', + width: '100%', + height: '100%', + }, + containerHeader: { + width: '100%', + height: '30%', + }, + containerBody: { + width: '100%', + height: '70%', + backgroundColor: '#FFFFFF', + borderTopLeftRadius: 46, + borderTopRightRadius: 46 + }, + image: { + height: '100%', + width: '100%', + } +}); \ No newline at end of file -- GitLab From 362122edee20d1fbd8bc5f5e0b882ef9869db30d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 20 Nov 2024 15:28:50 +0100 Subject: [PATCH 012/283] feat: creation du service --- helper/QuizHelper.ts | 93 +++++++++++++---------- models/EDifficulty.ts | 5 ++ {model => models}/QuestionModel.ts | 1 + {model => models}/QuizModel.ts | 3 +- screens/GenerateQuiz/GenerateQuizBody.tsx | 14 ++-- screens/PlayingQuiz/PlayingQuiz.tsx | 58 ++------------ screens/PlayingQuiz/PlayingQuizBody.tsx | 6 +- screens/PlayingQuiz/PlayingQuizHeader.tsx | 6 +- services/QuizService.ts | 43 +++++++++++ 9 files changed, 127 insertions(+), 102 deletions(-) create mode 100644 models/EDifficulty.ts rename {model => models}/QuestionModel.ts (82%) rename {model => models}/QuizModel.ts (86%) create mode 100644 services/QuizService.ts diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index 090314d..132814e 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -1,50 +1,65 @@ -// src/helpers/QuizHelper.ts - -import QuizModel from "../model/QuizModel"; -import QuestionModel from "../model/QuestionModel"; -import he from "he"; - -interface ApiQuestion { - category: string; - correct_answer: string; - difficulty: string; - incorrect_answers: string[]; - question: string; - type: string; -} -interface ApiResponse { - response_code: number; - results: ApiQuestion[]; -} +// Fonction utilitaire pour mélanger les réponses +import QuizModel from "../models/QuizModel"; +import QuestionModel from "../models/QuestionModel"; + +const optionsTheme = [ + "General Knowledge", + "Entertainment: Books", + "Entertainment: Film", + "Entertainment: Music", + "Entertainment: Musicals & Theatres", + "Entertainment: Television", + "Entertainment: Video Games", + "Entertainment: Board Games", + "Science & Nature", + "Science: Computers", + "Science: Mathematics", + "Mythology", + "Sports", + "Geography"] + +const shuffleAnswers = (answers: string[]): string[] => { + for (let i = answers.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [answers[i], answers[j]] = [answers[j], answers[i]]; + } + return answers; +}; + +export const transformToQuizModel = (data: any): QuizModel => { + // Transformation des questions + // console.log(JSON.stringify(data, null, 2)); -// Fonction pour transformer les données API en QuizModel -export const transformToQuizModel = (data: ApiResponse): QuizModel => { - const questions: QuestionModel[] = data.results.map((item) => { + const questions: QuestionModel[] = data.questions.map((item: any) => { + const answers = shuffleAnswers(item.answers.map((answer: any) => answer.text)); return { - question: he.decode(item.question), - answer: shuffleAnswers([...item.incorrect_answers.map((ans) => he.decode(ans)), he.decode(item.correct_answer)]), - correctAnswer: item.correct_answer, - multiple: item.type === "multiple", + question: item.text, + answer: answers, // Réponses mélangées + correctAnswer: item.answers[0].text, // Hypothèse : première réponse correcte (ajuste si nécessaire) + multiple: item.type === "multiple", // Détermine si c'est une question multiple + nbOfTheQuestion: item.order, // Mapping de order vers nbOfTheQuestion }; }); - return { - id: `quiz-${Date.now()}`, // Génère un ID unique pour chaque quiz - name: `Quiz - ${data.results[0]?.category || "General Knowledge"}`, - category: he.decode(data.results[0]?.category) || "General Knowledge", + // Création de l'objet QuizModel + const quiz: QuizModel = { + code: data.codeQuiz, // Mapping de codeQuiz vers code + category: data.theme.name, // Extraction du nom de la catégorie questions: questions, - nbQuestions: questions.length, - nbActualQuestion: 1, - score: 0, + nbQuestions: questions[questions.length-1].nbOfTheQuestion, // Nombre total de questions + nbActualQuestion: data.questionIndex, // Mapping de questionIndex vers nbActualQuestion + score: data.score, // Mapping direct }; + + return quiz; }; -// Fonction utilitaire pour mélanger les réponses -const shuffleAnswers = (answers: string[]): string[] => { - for (let i = answers.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [answers[i], answers[j]] = [answers[j], answers[i]]; +export const getIdOfCategory = (category: string) => { + for (let i = 0; i < optionsTheme.length; i++) { + if (optionsTheme[i] === category) { + return i; + } } - return answers; -}; + return 0; +} diff --git a/models/EDifficulty.ts b/models/EDifficulty.ts new file mode 100644 index 0000000..640dee4 --- /dev/null +++ b/models/EDifficulty.ts @@ -0,0 +1,5 @@ +export enum EDifficulty { + Easy = "easy", + Medium = "medium", + Hard = "hard", +} \ No newline at end of file diff --git a/model/QuestionModel.ts b/models/QuestionModel.ts similarity index 82% rename from model/QuestionModel.ts rename to models/QuestionModel.ts index 48893ef..fa3cc19 100644 --- a/model/QuestionModel.ts +++ b/models/QuestionModel.ts @@ -3,4 +3,5 @@ export default interface QuestionModel { answer: string[]; correctAnswer: string; multiple: boolean; + nbOfTheQuestion: number; } \ No newline at end of file diff --git a/model/QuizModel.ts b/models/QuizModel.ts similarity index 86% rename from model/QuizModel.ts rename to models/QuizModel.ts index fd2a720..6e1cbb9 100644 --- a/model/QuizModel.ts +++ b/models/QuizModel.ts @@ -1,8 +1,7 @@ import QuestionModel from "./QuestionModel"; export default interface QuizModel { - id: string; - name: string; + code: string; category: string; questions: QuestionModel[]; nbQuestions: number; diff --git a/screens/GenerateQuiz/GenerateQuizBody.tsx b/screens/GenerateQuiz/GenerateQuizBody.tsx index 9330c12..d38e238 100644 --- a/screens/GenerateQuiz/GenerateQuizBody.tsx +++ b/screens/GenerateQuiz/GenerateQuizBody.tsx @@ -1,8 +1,11 @@ import ComboBox from "../../components/comboBox/ComboBox"; import DefaultButton from "../../components/DefaultButton"; -import {StyleSheet, View, Text} from "react-native"; +import {StyleSheet, View} from "react-native"; import {useState} from "react"; import {useTranslation} from "react-i18next"; +import {useQuizService} from "../../services/QuizService"; +import {EDifficulty} from "../../models/EDifficulty"; +import {getIdOfCategory} from "../../helper/QuizHelper"; const optionsDifficulty = [ 'easy', 'medium', 'hard']; const optionsQuestions = ['1', '50']; @@ -30,17 +33,18 @@ export default function GenerateQuizBody({navigation}:Props) { const [isModalVisible, setIsModalVisible] = useState(false); const [difficulty, setDifficulty] = useState('easy'); const [nbQuestions, setNbQuestions] = useState('1'); - const [theme, setTheme] = useState('General Knowledge'); + const [theme, setTheme] = useState<string>('General Knowledge'); const {t} = useTranslation(); + const {createQuiz} = useQuizService(); - const handleAddQuizPressed = () => { - console.log(difficulty); + const handleAddQuizPressed = async () => { + const quizGenerated = await createQuiz(parseInt(nbQuestions), getIdOfCategory(theme), difficulty); navigation.reset({ index: 0, routes: [ { name: "PlayingQuiz", - params: {nbQuestionParam: nbQuestions, difficultyParam: difficulty, themeParam: theme}, + params: {quizGenerated: quizGenerated}, }, ] }); diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 91dc961..e7c3266 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -3,7 +3,7 @@ import PlayingQuizBody from "./PlayingQuizBody"; import PlayingQuizHeader from "./PlayingQuizHeader"; import {useEffect, useState} from "react"; import {transformToQuizModel} from "../../helper/QuizHelper"; -import QuizModel from "../../model/QuizModel"; +import QuizModel from "../../models/QuizModel"; const optionsTheme = [ "General Knowledge", @@ -27,59 +27,15 @@ interface Props { } export default function PlayingQuiz({route, navigation}:Props) { - const getIdOfCategory = (category: string) => { - for (let i = 0; i < optionsTheme.length; i++) { - if (optionsTheme[i] === category) { - return i+9; - } - } - } - + const {quizGenerated} = route.params; const [isAlreadyPlayed, setIsAlreadyPlayed] = useState(false); - const [quiz, setQuiz] = useState<QuizModel>(); - const nbQuestions = "10"; - const difficulty = "easy"; - const theme = "General Knowledge"; - - const { - nbQuestionParam = "10", - difficultyParam = "easy", - themeParam = "General Knowledge", - quiz: quizFromParams, // Quiz passé via la navigation - } = route.params || {}; - const nbQuestionsToDisplay = nbQuestionParam ? nbQuestionParam : nbQuestions; - const difficultyToDisplay = difficultyParam ? difficultyParam : difficulty; - const themeToDisplay = themeParam ? getIdOfCategory(themeParam) : getIdOfCategory(theme); - - - - - const fetchQuestion = async () => { - try { - const response = await fetch(`https://opentdb.com/api.php?amount=${nbQuestionsToDisplay}&difficulty=${difficultyToDisplay}&category=${themeToDisplay}`); - const data = await response.json(); - const quiz = transformToQuizModel(data); - setQuiz(quiz); - } catch (error) { - console.error(error); - } - }; - - useEffect(() => { - if (quizFromParams) { - setTimeout(() => { - setQuiz(quizFromParams); - }, 0); - } else { - fetchQuestion(); - } - }, [quizFromParams]); + const [quiz, setQuiz] = useState<QuizModel>(quizGenerated); const handleAnswer = async (selectedAnswerIndex: number) => { if (!quiz) return; - const currentQuestion = quiz.questions[quiz.nbActualQuestion-1]; + const currentQuestion = quiz.questions[quiz.nbActualQuestion]; const correctAnswer = currentQuestion.correctAnswer; const selectedAnswer = currentQuestion.answer[selectedAnswerIndex]; @@ -99,9 +55,11 @@ export default function PlayingQuiz({route, navigation}:Props) { // Passer à la question suivante setQuiz((prevQuiz) => { if (prevQuiz && prevQuiz.nbActualQuestion < prevQuiz.nbQuestions) { + console.log("new question") setIsAlreadyPlayed(false); return { ...prevQuiz, nbActualQuestion: prevQuiz.nbActualQuestion + 1 }; } + console.log("fin question") navigation.navigate("EndQuiz", { score: prevQuiz?.score, quiz: quiz }); return prevQuiz; // Fin du quiz si toutes les questions ont été posées }); @@ -109,8 +67,8 @@ export default function PlayingQuiz({route, navigation}:Props) { return ( <TemplateScreen - childrenTop={<PlayingQuizHeader quiz={quiz} ></PlayingQuizHeader>} - childrenBot={<PlayingQuizBody quiz={quiz} onAnswerSelected={handleAnswer} isAlreadyPlayed={isAlreadyPlayed} setIsAlreadyPlayed={setIsAlreadyPlayed}></PlayingQuizBody>} + childrenTop={<PlayingQuizHeader quiz={quizGenerated} ></PlayingQuizHeader>} + childrenBot={<PlayingQuizBody quiz={quizGenerated} onAnswerSelected={handleAnswer} isAlreadyPlayed={isAlreadyPlayed} setIsAlreadyPlayed={setIsAlreadyPlayed}></PlayingQuizBody>} /> ); } \ No newline at end of file diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 4ea806f..8926d7d 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -2,12 +2,12 @@ import { View, StyleSheet } from "react-native"; import DefaultButton from "../../components/DefaultButton"; import { ButtonsStyles } from "../../styles/ButtonsStyles"; import { TextsStyles } from "../../styles/TextsStyles"; -import QuizModel from "../../model/QuizModel"; +import QuizModel from "../../models/QuizModel"; import { useState, useEffect } from "react"; import { setEnabled } from "react-native/Libraries/Performance/Systrace"; interface Props { - quiz?: QuizModel; + quiz: QuizModel; onAnswerSelected: (index: number) => void; isAlreadyPlayed: boolean; setIsAlreadyPlayed: (isAlreadyPlayed: boolean) => void; @@ -26,7 +26,7 @@ export default function PlayingQuizBody({ quiz, onAnswerSelected, isAlreadyPlaye if (!quiz) return null; - const currentQuestion = quiz.questions[quiz.nbActualQuestion - 1]; + const currentQuestion = quiz.questions[quiz.nbActualQuestion]; const handleAnswerPress = (index: number) => { if (!isAlreadyPlayed) { diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index 6151f62..bfe216a 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -1,10 +1,10 @@ import { View, StyleSheet, Text } from "react-native"; import { TextsStyles } from "../../styles/TextsStyles"; -import QuizModel from "../../model/QuizModel"; +import QuizModel from "../../models/QuizModel"; import {useTranslation} from "react-i18next"; interface Props { - quiz?: QuizModel; + quiz: QuizModel; } export default function PlayingQuizHeader({quiz}: Props) { @@ -19,7 +19,7 @@ export default function PlayingQuizHeader({quiz}: Props) { <Text style={TextsStyles.titleText}>{t("app.screens.question.question")} {quiz ? quiz.nbActualQuestion : "?"}/{quiz ? quiz.nbQuestions : "?"}</Text> </View> <View style={styles.QuizQuestionContainer}> - <Text style={TextsStyles.subtitleText}>{quiz ? quiz.questions[quiz.nbActualQuestion - 1].question : "Loading..."}</Text> + <Text style={TextsStyles.subtitleText}>{quiz ? quiz.questions[quiz.nbActualQuestion].question : "Loading..."}</Text> </View> </View> ); diff --git a/services/QuizService.ts b/services/QuizService.ts new file mode 100644 index 0000000..d153099 --- /dev/null +++ b/services/QuizService.ts @@ -0,0 +1,43 @@ +import {EDifficulty} from "../models/EDifficulty"; +import {transformToQuizModel} from "../helper/QuizHelper"; + +export const useQuizService = () => { + const createQuiz = async (amount: number, category: number, difficulty: string) => { + const url = "http://klebert-host.com:33036/quiz/create"; // Remplace par l'URL de ton endpoint API + + // Création du corps de la requête avec les variables passées + const requestBody = { + amount, + category, + difficulty: difficulty.toLowerCase(), // Si EDifficulty est en uppercase et l'API attend lowercase + }; + + try { + // Envoi de la requête POST + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", // Type des données envoyées + }, + body: JSON.stringify(requestBody), // Sérialisation de l'objet en JSON + }); + + // Vérification si la requête a réussi + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + // Récupération des données retournées par l'API + const data = await response.json(); + const quiz = transformToQuizModel(data); + return quiz; + } catch (error) { + console.error("Error while creating quiz:", error); + throw error; // Relancer l'erreur pour la gérer au niveau de l'appelant + } + } + + return { + createQuiz: createQuiz, + } +} \ No newline at end of file -- GitLab From c3832c91f058e409099420a82223f1a7f373f738 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 20 Nov 2024 15:39:06 +0100 Subject: [PATCH 013/283] feat: ajout des images --- assets/ImageGenerateQuiz.png | Bin 0 -> 79168 bytes assets/ImageJoinQuiz.png | Bin 0 -> 58320 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/ImageGenerateQuiz.png create mode 100644 assets/ImageJoinQuiz.png diff --git a/assets/ImageGenerateQuiz.png b/assets/ImageGenerateQuiz.png new file mode 100644 index 0000000000000000000000000000000000000000..47a561544bbad44158e1165cf92d583942b2527f GIT binary patch literal 79168 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­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%0DAAR&eoSdC8 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 ChCLSm literal 0 HcmV?d00001 diff --git a/assets/ImageJoinQuiz.png b/assets/ImageJoinQuiz.png new file mode 100644 index 0000000000000000000000000000000000000000..1f83b59b59b5aab112be427fcf1e1f76e914a316 GIT binary patch literal 58320 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 B--G}F literal 0 HcmV?d00001 -- GitLab From 9b472523716fbda7e57bd048f07db8f9fec6f0f1 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 20 Nov 2024 15:39:56 +0100 Subject: [PATCH 014/283] feat: refont page Home --- screens/Home/Home.tsx | 126 +++------------------------------ screens/Home/Home.tsx sample | 133 +++++++++++++++++++++++++++++++++++ screens/Home/HomeChild.tsx | 113 +++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 118 deletions(-) create mode 100644 screens/Home/Home.tsx sample create mode 100644 screens/Home/HomeChild.tsx diff --git a/screens/Home/Home.tsx b/screens/Home/Home.tsx index 5e4d8d6..d0b56d4 100644 --- a/screens/Home/Home.tsx +++ b/screens/Home/Home.tsx @@ -1,133 +1,23 @@ -import {StyleSheet, View, Text, Modal, TextInput, Image} from "react-native"; -import DefaultButton from "../../components/DefaultButton"; -import {TextsStyles} from "../../styles/TextsStyles"; -import {useTranslation} from "react-i18next"; -import { useState } from "react"; -import { ButtonsStyles } from "../../styles/ButtonsStyles"; +import { View, StyleSheet } from "react-native"; +import TemplateMono from "../../templates/TemplateMono"; +import HomeChild from "./HomeChild"; interface Props { navigation: any; } -export default function Home({navigation}: Props) { - const [modalVisible, setModalVisible] = useState(false); - - // const handleButtonPlayPressed = () => { - // setModalVisible(true); - // } - - const {t} = useTranslation(); - const handleButtonCreateQuizPressed = () => { - navigation.navigate("CreateQuiz"); - } - const handleButtonValidatePressed = () => { - setModalVisible(false); - } +export default function Home({navigation}: Props) { return ( - <View> - <Image source={require('../../assets/FondTemplate.png')} style={styles.image}/> - <View style={styles.container}> - <View style={styles.titleContainer}> - <Text style={styles.titleText}>{"SKITSKOLA"}</Text> - </View> - - <View style={styles.buttonContainer}> - <View style={styles.ButtonsStyle}> - {/* <DefaultButton text={t("app.screens.home.play")} handleButtonPressed={handleButtonPlayPressed}></DefaultButton> */} - <DefaultButton text={t("app.screens.home.createQuiz")} handleButtonPressed={handleButtonCreateQuizPressed} buttonStyle={ButtonsStyles.movingButton} buttonText={TextsStyles.defaultText}></DefaultButton> - </View> - </View> - {/* <Modal - animationType="slide" - transparent={true} - visible={modalVisible} - onRequestClose={() => { - setModalVisible(!modalVisible); - }}> - <View style={styles.centeredView}> - <View style={styles.modalView}> - <Text style={TextsStyles.subtitleText}>{t.("app.screens.home.joinQuiz")}</Text> - <TextInput placeholder={t("app.screens.home.codeQuiz")} style={styles.codeTextInput} keyboardType="numeric"/> - <DefaultButton text="V" handleButtonPressed={handleButtonValidatePressed} buttonStyle={styles.button}></DefaultButton> - </View> - </View> - </Modal> */} - </View> + <View style={styles.containerGlobal}> + <TemplateMono children={<HomeChild navigation={navigation}/>}/> </View> - ) + ); } const styles = StyleSheet.create({ - container: { + containerGlobal: { display: 'flex', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - height: '100%', - }, - buttonContainer: { - flex: 1, - alignItems: 'center', - justifyContent: 'flex-start', - width: '100%', - paddingVertical: '5%' - }, - titleContainer: { - display : 'flex', - flexDirection: 'column', - justifyContent: 'flex-end', - alignItems: 'center', - width: '100%', - height: '40%', - }, - ButtonsStyle: { - display : 'flex', - flexDirection: 'column', - justifyContent: 'flex-start', - alignItems: 'center', - gap: 20, - width: '80%', - height: '60%', - marginBottom: "20%", - }, - centeredView: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - modalView: { - height: 250, - width: 320, - margin: 20, - backgroundColor: 'white', - borderRadius: 20, - padding: 35, - alignItems: 'center', - borderWidth: 2, - borderColor: 'black', - borderStyle: 'solid', - }, - codeTextInput: { - borderWidth: 2, - borderColor: 'black', - borderStyle: 'solid', - height: 40, - width: 200, - marginTop: 30, - marginBottom: 20, - padding: 10, - }, - button: { - width: 50, - height: 50, - }, - image: { width: '100%', height: '100%', - position: 'absolute', }, - titleText: { - fontSize: 30, - fontStyle: 'italic' - } }); \ No newline at end of file diff --git a/screens/Home/Home.tsx sample b/screens/Home/Home.tsx sample new file mode 100644 index 0000000..5e4d8d6 --- /dev/null +++ b/screens/Home/Home.tsx sample @@ -0,0 +1,133 @@ +import {StyleSheet, View, Text, Modal, TextInput, Image} from "react-native"; +import DefaultButton from "../../components/DefaultButton"; +import {TextsStyles} from "../../styles/TextsStyles"; +import {useTranslation} from "react-i18next"; +import { useState } from "react"; +import { ButtonsStyles } from "../../styles/ButtonsStyles"; + +interface Props { + navigation: any; +} +export default function Home({navigation}: Props) { + const [modalVisible, setModalVisible] = useState(false); + + // const handleButtonPlayPressed = () => { + // setModalVisible(true); + // } + + const {t} = useTranslation(); + const handleButtonCreateQuizPressed = () => { + navigation.navigate("CreateQuiz"); + } + + const handleButtonValidatePressed = () => { + setModalVisible(false); + } + return ( + <View> + <Image source={require('../../assets/FondTemplate.png')} style={styles.image}/> + <View style={styles.container}> + <View style={styles.titleContainer}> + <Text style={styles.titleText}>{"SKITSKOLA"}</Text> + </View> + + <View style={styles.buttonContainer}> + <View style={styles.ButtonsStyle}> + {/* <DefaultButton text={t("app.screens.home.play")} handleButtonPressed={handleButtonPlayPressed}></DefaultButton> */} + <DefaultButton text={t("app.screens.home.createQuiz")} handleButtonPressed={handleButtonCreateQuizPressed} buttonStyle={ButtonsStyles.movingButton} buttonText={TextsStyles.defaultText}></DefaultButton> + </View> + </View> + {/* <Modal + animationType="slide" + transparent={true} + visible={modalVisible} + onRequestClose={() => { + setModalVisible(!modalVisible); + }}> + <View style={styles.centeredView}> + <View style={styles.modalView}> + <Text style={TextsStyles.subtitleText}>{t.("app.screens.home.joinQuiz")}</Text> + <TextInput placeholder={t("app.screens.home.codeQuiz")} style={styles.codeTextInput} keyboardType="numeric"/> + <DefaultButton text="V" handleButtonPressed={handleButtonValidatePressed} buttonStyle={styles.button}></DefaultButton> + </View> + </View> + </Modal> */} + </View> + </View> + ) +} + +const styles = StyleSheet.create({ + container: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '100%', + }, + buttonContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'flex-start', + width: '100%', + paddingVertical: '5%' + }, + titleContainer: { + display : 'flex', + flexDirection: 'column', + justifyContent: 'flex-end', + alignItems: 'center', + width: '100%', + height: '40%', + }, + ButtonsStyle: { + display : 'flex', + flexDirection: 'column', + justifyContent: 'flex-start', + alignItems: 'center', + gap: 20, + width: '80%', + height: '60%', + marginBottom: "20%", + }, + centeredView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + modalView: { + height: 250, + width: 320, + margin: 20, + backgroundColor: 'white', + borderRadius: 20, + padding: 35, + alignItems: 'center', + borderWidth: 2, + borderColor: 'black', + borderStyle: 'solid', + }, + codeTextInput: { + borderWidth: 2, + borderColor: 'black', + borderStyle: 'solid', + height: 40, + width: 200, + marginTop: 30, + marginBottom: 20, + padding: 10, + }, + button: { + width: 50, + height: 50, + }, + image: { + width: '100%', + height: '100%', + position: 'absolute', + }, + titleText: { + fontSize: 30, + fontStyle: 'italic' + } +}); \ No newline at end of file diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx new file mode 100644 index 0000000..b647a16 --- /dev/null +++ b/screens/Home/HomeChild.tsx @@ -0,0 +1,113 @@ +import { View, StyleSheet, Image, Modal, TextInput } from "react-native"; +import DefaultButton from "../../components/DefaultButton"; +import {useTranslation} from "react-i18next"; +import { useState } from "react"; +import { TextsStyles } from "../../styles/TextsStyles"; + +interface Props { + navigation: any; +} + +export default function HomeChild({navigation}: Props) { + const [modalVisible, setModalVisible] = useState(false); + const {t} = useTranslation(); + + const handleButtonJoinPressed = () => { + setModalVisible(true); + } + + const handleButtonGeneratePressed = () => { + navigation.navigate("CreateQuiz"); + } + + // const handleButtonValidatePressed = () => { + // setModalVisible(false); + // } + return ( + <View style={styles.containerGlobal}> + <View style={styles.containerAction}> + <Image source={require('../../assets/ImageJoinQuiz.png')} style={styles.image}/> + <DefaultButton text={t("app.screens.home.codeQuiz")} handleButtonPressed={handleButtonJoinPressed} buttonStyle={styles.button}/> + </View> + <View style={styles.containerAction}> + <Image source={require('../../assets/ImageGenerateQuiz.png')} style={styles.image}/> + <DefaultButton text={t("app.screens.home.createQuiz")} handleButtonPressed={handleButtonGeneratePressed} buttonStyle={styles.button}/> + </View> + {/* <Modal + animationType="slide" + transparent={true} + visible={modalVisible} + onRequestClose={() => { + setModalVisible(!modalVisible); + }}> + <View style={styles.centeredView}> + <View style={styles.modalView}> + <Text style={TextsStyles.subtitleText}>{t.("app.screens.home.joinQuiz")}</Text> + <TextInput placeholder={t("app.screens.home.codeQuiz")} style={styles.codeTextInput} keyboardType="numeric"/> + <DefaultButton text="V" handleButtonPressed={handleButtonValidatePressed} buttonStyle={styles.button}></DefaultButton> + </View> + </View> + </Modal> */} + </View> + ); +} + +const styles = StyleSheet.create({ + containerGlobal: { + display: 'flex', + width: '100%', + height: '100%', + justifyContent: 'space-around', + alignItems: 'center', + }, + containerAction: { + backgroundColor: '#F3F3F3', + width: '90%', + height: '45%', + padding: '8%', + borderRadius: 40, + shadowColor: '#171717', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.2, + shadowRadius: 3, + elevation: 5, + }, + image: { + height: '60%', + width: '100%', + borderTopLeftRadius: 40, + borderTopRightRadius: 40, + }, + button: { + borderRadius: 40, + height: '35%', + marginTop: '5%' + }, + // centeredView: { + // flex: 1, + // justifyContent: 'center', + // alignItems: 'center', + // }, + // modalView: { + // height: 250, + // width: 320, + // margin: 20, + // backgroundColor: 'white', + // borderRadius: 20, + // padding: 35, + // alignItems: 'center', + // borderWidth: 2, + // borderColor: 'black', + // borderStyle: 'solid', + // }, + // codeTextInput: { + // borderWidth: 2, + // borderColor: 'black', + // borderStyle: 'solid', + // height: 40, + // width: 200, + // marginTop: 30, + // marginBottom: 20, + // padding: 10, + // }, +}); \ No newline at end of file -- GitLab From 150a578a0c4491c07ddf5df57d531db63e71104a Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 20 Nov 2024 15:40:32 +0100 Subject: [PATCH 015/283] feat: modif style de boutton de base --- styles/ButtonsStyles.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/styles/ButtonsStyles.ts b/styles/ButtonsStyles.ts index 769f771..96dd860 100644 --- a/styles/ButtonsStyles.ts +++ b/styles/ButtonsStyles.ts @@ -1,11 +1,10 @@ import { StyleSheet } from "react-native"; export const ButtonsStyles = StyleSheet.create( { defaultButton: { - backgroundColor: "grey", + backgroundColor: "#45128C", padding: 10, alignItems: "center", - borderRadius: 10, - width: "100%", + justifyContent: 'center' }, answerButton: { backgroundColor: "#F3F3F3", -- GitLab From 85f2d72ee1de8932de7d4f569dbea808ee990a6f Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 20 Nov 2024 15:40:42 +0100 Subject: [PATCH 016/283] feat: modif template mono --- templates/TemplateMono.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/templates/TemplateMono.tsx b/templates/TemplateMono.tsx index 0452982..1c781dc 100644 --- a/templates/TemplateMono.tsx +++ b/templates/TemplateMono.tsx @@ -25,20 +25,24 @@ const styles = StyleSheet.create({ }, containerHeader: { height: '12%', + width: '100%', justifyContent: 'flex-end', alignItems: 'center', + backgroundColor: '#fff', shadowColor: '#171717', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.2, shadowRadius: 3, elevation: 5, - backgroundColor: '#fff' + zIndex: 1, }, containerBody: { - height: '83%', + height: '88%', + width: '100%', + backgroundColor: '#FFFFFF' }, image: { height: '60%', - width: '60%' + width: '60%', } }); \ No newline at end of file -- GitLab From 3541f77008348d2187d6093e16b5ec155ece1f82 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 20 Nov 2024 21:45:08 +0100 Subject: [PATCH 017/283] feat: start modal join quiz --- components/ModalJoinQuiz.tsx | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 components/ModalJoinQuiz.tsx diff --git a/components/ModalJoinQuiz.tsx b/components/ModalJoinQuiz.tsx new file mode 100644 index 0000000..2c0927c --- /dev/null +++ b/components/ModalJoinQuiz.tsx @@ -0,0 +1,31 @@ +import { Modal, View, Text, StyleSheet, TextInput } from "react-native"; +import DefaultButton from "./DefaultButton"; + +export default function ModalJoinQuiz() { + const handleButtonClosePressed = () => { + } + + const handleButtonJoinQuizPressed = () => { + } + + return ( + <Modal> + <View> + <Text>PLAY YOUR QUIZ</Text> + <DefaultButton text="X" handleButtonPressed={handleButtonClosePressed}/> + </View> + <View style={styles.separator}/> + <Text>ENTER THE QUIZ ID</Text> + <TextInput placeholder="TYPE A CODE"></TextInput> + <DefaultButton text="PLAY" handleButtonPressed={handleButtonJoinQuizPressed}/> + </Modal> + ); +} + +const styles = StyleSheet.create({ + separator: { + height: 1, + width: '100%', + backgroundColor: '#ccc', + } +}); \ No newline at end of file -- GitLab From 01ac102cc288ba6a25a1c971127796b0f8f05a4e Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 21 Nov 2024 11:01:45 +0100 Subject: [PATCH 018/283] fix: passer a la question suivante --- helper/QuizHelper.ts | 3 ++- screens/PlayingQuiz/PlayingQuiz.tsx | 3 +-- screens/PlayingQuiz/PlayingQuizBody.tsx | 2 +- screens/PlayingQuiz/PlayingQuizHeader.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index 132814e..b8a03ff 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -48,7 +48,8 @@ export const transformToQuizModel = (data: any): QuizModel => { category: data.theme.name, // Extraction du nom de la catégorie questions: questions, nbQuestions: questions[questions.length-1].nbOfTheQuestion, // Nombre total de questions - nbActualQuestion: data.questionIndex, // Mapping de questionIndex vers nbActualQuestion + // nbActualQuestion: data.questionIndex, // Mapping de questionIndex vers nbActualQuestion + nbActualQuestion: 1, // Mapping de questionIndex vers nbActualQuestion score: data.score, // Mapping direct }; diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index e7c3266..64fa416 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -35,7 +35,7 @@ export default function PlayingQuiz({route, navigation}:Props) { const handleAnswer = async (selectedAnswerIndex: number) => { if (!quiz) return; - const currentQuestion = quiz.questions[quiz.nbActualQuestion]; + const currentQuestion = quiz.questions[quiz.nbActualQuestion - 1]; const correctAnswer = currentQuestion.correctAnswer; const selectedAnswer = currentQuestion.answer[selectedAnswerIndex]; @@ -59,7 +59,6 @@ export default function PlayingQuiz({route, navigation}:Props) { setIsAlreadyPlayed(false); return { ...prevQuiz, nbActualQuestion: prevQuiz.nbActualQuestion + 1 }; } - console.log("fin question") navigation.navigate("EndQuiz", { score: prevQuiz?.score, quiz: quiz }); return prevQuiz; // Fin du quiz si toutes les questions ont été posées }); diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 8926d7d..2b82b4d 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -26,7 +26,7 @@ export default function PlayingQuizBody({ quiz, onAnswerSelected, isAlreadyPlaye if (!quiz) return null; - const currentQuestion = quiz.questions[quiz.nbActualQuestion]; + const currentQuestion = quiz.questions[quiz.nbActualQuestion-1]; const handleAnswerPress = (index: number) => { if (!isAlreadyPlayed) { diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index bfe216a..c756900 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -19,7 +19,7 @@ export default function PlayingQuizHeader({quiz}: Props) { <Text style={TextsStyles.titleText}>{t("app.screens.question.question")} {quiz ? quiz.nbActualQuestion : "?"}/{quiz ? quiz.nbQuestions : "?"}</Text> </View> <View style={styles.QuizQuestionContainer}> - <Text style={TextsStyles.subtitleText}>{quiz ? quiz.questions[quiz.nbActualQuestion].question : "Loading..."}</Text> + <Text style={TextsStyles.subtitleText}>{quiz ? quiz.questions[quiz.nbActualQuestion-1].question : "Loading..."}</Text> </View> </View> ); -- GitLab From 11520b96f667c30c6aa82573f95c91c3f79701b5 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 21 Nov 2024 11:24:36 +0100 Subject: [PATCH 019/283] style: commentaire dans le code --- helper/QuizHelper.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index b8a03ff..b3acceb 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -31,14 +31,16 @@ export const transformToQuizModel = (data: any): QuizModel => { // Transformation des questions // console.log(JSON.stringify(data, null, 2)); - const questions: QuestionModel[] = data.questions.map((item: any) => { - const answers = shuffleAnswers(item.answers.map((answer: any) => answer.text)); + const questions: QuestionModel[] = data.questions.map((question: any) => { + const answers = shuffleAnswers(question.answers.map((answer: any) => answer.text)); return { - question: item.text, + question: question.text, answer: answers, // Réponses mélangées - correctAnswer: item.answers[0].text, // Hypothèse : première réponse correcte (ajuste si nécessaire) - multiple: item.type === "multiple", // Détermine si c'est une question multiple - nbOfTheQuestion: item.order, // Mapping de order vers nbOfTheQuestion + // TODO + // Pour l'instant la bonne réponse est la premiere le temps qu'on puisse récupérer la bonne réponse depuis l'API + correctAnswer: question.answers[0].text, // Hypothèse : première réponse correcte (ajuste si nécessaire) + multiple: question.type === "multiple", // Détermine si c'est une question multiple + nbOfTheQuestion: question.order, // Mapping de order vers nbOfTheQuestion }; }); @@ -48,6 +50,8 @@ export const transformToQuizModel = (data: any): QuizModel => { category: data.theme.name, // Extraction du nom de la catégorie questions: questions, nbQuestions: questions[questions.length-1].nbOfTheQuestion, // Nombre total de questions + // TODO + // nbAcutealQuestion devra pas commencer à 0 (Faute de l'API) // nbActualQuestion: data.questionIndex, // Mapping de questionIndex vers nbActualQuestion nbActualQuestion: 1, // Mapping de questionIndex vers nbActualQuestion score: data.score, // Mapping direct -- GitLab From 985e69b4e78c3e1b70fd9642013770a76a640114 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 21 Nov 2024 11:32:50 +0100 Subject: [PATCH 020/283] style: commentaire dans le code --- services/QuizService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index d153099..2680d02 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -2,8 +2,9 @@ import {EDifficulty} from "../models/EDifficulty"; import {transformToQuizModel} from "../helper/QuizHelper"; export const useQuizService = () => { + const url = "http://klebert-host.com:33036/quiz/create"; // Remplace par l'URL de ton endpoint API + const createQuiz = async (amount: number, category: number, difficulty: string) => { - const url = "http://klebert-host.com:33036/quiz/create"; // Remplace par l'URL de ton endpoint API // Création du corps de la requête avec les variables passées const requestBody = { -- GitLab From 5760b190a84ad4f7beada780735814d2e2b0dce7 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 21 Nov 2024 11:41:10 +0100 Subject: [PATCH 021/283] style: createQuiz -> generateQuiz --- screens/GenerateQuiz/GenerateQuizBody.tsx | 4 ++-- services/QuizService.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/screens/GenerateQuiz/GenerateQuizBody.tsx b/screens/GenerateQuiz/GenerateQuizBody.tsx index d38e238..8c8a977 100644 --- a/screens/GenerateQuiz/GenerateQuizBody.tsx +++ b/screens/GenerateQuiz/GenerateQuizBody.tsx @@ -35,10 +35,10 @@ export default function GenerateQuizBody({navigation}:Props) { const [nbQuestions, setNbQuestions] = useState('1'); const [theme, setTheme] = useState<string>('General Knowledge'); const {t} = useTranslation(); - const {createQuiz} = useQuizService(); + const {generateQuiz} = useQuizService(); const handleAddQuizPressed = async () => { - const quizGenerated = await createQuiz(parseInt(nbQuestions), getIdOfCategory(theme), difficulty); + const quizGenerated = await generateQuiz(parseInt(nbQuestions), getIdOfCategory(theme), difficulty); navigation.reset({ index: 0, routes: [ diff --git a/services/QuizService.ts b/services/QuizService.ts index 2680d02..7abd894 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -4,7 +4,7 @@ import {transformToQuizModel} from "../helper/QuizHelper"; export const useQuizService = () => { const url = "http://klebert-host.com:33036/quiz/create"; // Remplace par l'URL de ton endpoint API - const createQuiz = async (amount: number, category: number, difficulty: string) => { + const generateQuiz = async (amount: number, category: number, difficulty: string) => { // Création du corps de la requête avec les variables passées const requestBody = { @@ -39,6 +39,6 @@ export const useQuizService = () => { } return { - createQuiz: createQuiz, + generateQuiz: generateQuiz, } } \ No newline at end of file -- GitLab From 34bedf9bfbbd02808707368a9317236293da0265 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 21 Nov 2024 16:53:14 +0100 Subject: [PATCH 022/283] feat: add modal --- components/ModalJoinQuiz.tsx | 97 +++++++++++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 12 deletions(-) diff --git a/components/ModalJoinQuiz.tsx b/components/ModalJoinQuiz.tsx index 2c0927c..e05bb57 100644 --- a/components/ModalJoinQuiz.tsx +++ b/components/ModalJoinQuiz.tsx @@ -1,31 +1,104 @@ import { Modal, View, Text, StyleSheet, TextInput } from "react-native"; import DefaultButton from "./DefaultButton"; -export default function ModalJoinQuiz() { +interface Props { + navigation: any; + modalVisible: boolean; + setModalVisible: (newModalVisible: boolean) => void; +} + +export default function ModalJoinQuiz({ navigation, modalVisible, setModalVisible }: Props) { const handleButtonClosePressed = () => { + setModalVisible(!modalVisible); } const handleButtonJoinQuizPressed = () => { + setModalVisible(!modalVisible); + navigation.navigate("CreateQuiz");//TODO: changer la direction } return ( - <Modal> - <View> - <Text>PLAY YOUR QUIZ</Text> - <DefaultButton text="X" handleButtonPressed={handleButtonClosePressed}/> + <Modal + animationType="slide" + transparent={true} + visible={modalVisible} + onRequestClose={() => { + setModalVisible(!modalVisible); + }} + > + <View style={styles.centeredView}> + <View style={styles.modalView}> + <View style={styles.containerMenu}> + <Text style={styles.textMenu}>PLAY YOUR QUIZ</Text> + <DefaultButton text="X" handleButtonPressed={handleButtonClosePressed} buttonStyle={styles.buttonMenu} buttonText={styles.buttonTextMenu}/> + </View> + <View style={styles.containerCode}> + <Text style={styles.textCode}>ENTER THE QUIZ ID</Text> + <TextInput placeholder="TYPE A CODE" style={styles.textInputCode}></TextInput> + <DefaultButton text="PLAY" handleButtonPressed={handleButtonJoinQuizPressed} buttonStyle={styles.buttonCode}/> + </View> + </View> </View> - <View style={styles.separator}/> - <Text>ENTER THE QUIZ ID</Text> - <TextInput placeholder="TYPE A CODE"></TextInput> - <DefaultButton text="PLAY" handleButtonPressed={handleButtonJoinQuizPressed}/> </Modal> ); } const styles = StyleSheet.create({ - separator: { - height: 1, + centeredView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + modalView: { + height: '35%', + width: '80%', + margin: '10%', + padding: '10%', + backgroundColor: 'white', + borderRadius: 20, + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.25, + shadowRadius: 4, + elevation: 5, + }, + containerMenu: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + height: '30%', + }, + textMenu: { + flex: 1, + textAlign: 'center', + fontSize: 20, + fontWeight: 'bold', + }, + buttonMenu: { + padding: 10, + backgroundColor: '#FFFFFF', + }, + buttonTextMenu: { + color: '#D5D5D5', + fontSize: 24, + }, + containerCode: { + height: '100%', width: '100%', - backgroundColor: '#ccc', + }, + textCode: { + fontSize: 14 + }, + textInputCode: { + borderColor: '#1FA9FF', + borderRadius: 10, + borderWidth: 2, + }, + buttonCode: { + margin: 20 } }); \ No newline at end of file -- GitLab From 4faf9eed0acf3c2bc4674059183ac36b8f821afb Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 21 Nov 2024 16:54:21 +0100 Subject: [PATCH 023/283] feat: remplacement de la nouvelle modal --- screens/Home/HomeChild.tsx | 47 ++------------------------------------ 1 file changed, 2 insertions(+), 45 deletions(-) diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index b647a16..a03a84a 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -3,6 +3,7 @@ import DefaultButton from "../../components/DefaultButton"; import {useTranslation} from "react-i18next"; import { useState } from "react"; import { TextsStyles } from "../../styles/TextsStyles"; +import ModalJoinQuiz from "../../components/ModalJoinQuiz"; interface Props { navigation: any; @@ -20,9 +21,6 @@ export default function HomeChild({navigation}: Props) { navigation.navigate("CreateQuiz"); } - // const handleButtonValidatePressed = () => { - // setModalVisible(false); - // } return ( <View style={styles.containerGlobal}> <View style={styles.containerAction}> @@ -33,21 +31,7 @@ export default function HomeChild({navigation}: Props) { <Image source={require('../../assets/ImageGenerateQuiz.png')} style={styles.image}/> <DefaultButton text={t("app.screens.home.createQuiz")} handleButtonPressed={handleButtonGeneratePressed} buttonStyle={styles.button}/> </View> - {/* <Modal - animationType="slide" - transparent={true} - visible={modalVisible} - onRequestClose={() => { - setModalVisible(!modalVisible); - }}> - <View style={styles.centeredView}> - <View style={styles.modalView}> - <Text style={TextsStyles.subtitleText}>{t.("app.screens.home.joinQuiz")}</Text> - <TextInput placeholder={t("app.screens.home.codeQuiz")} style={styles.codeTextInput} keyboardType="numeric"/> - <DefaultButton text="V" handleButtonPressed={handleButtonValidatePressed} buttonStyle={styles.button}></DefaultButton> - </View> - </View> - </Modal> */} + <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> </View> ); } @@ -83,31 +67,4 @@ const styles = StyleSheet.create({ height: '35%', marginTop: '5%' }, - // centeredView: { - // flex: 1, - // justifyContent: 'center', - // alignItems: 'center', - // }, - // modalView: { - // height: 250, - // width: 320, - // margin: 20, - // backgroundColor: 'white', - // borderRadius: 20, - // padding: 35, - // alignItems: 'center', - // borderWidth: 2, - // borderColor: 'black', - // borderStyle: 'solid', - // }, - // codeTextInput: { - // borderWidth: 2, - // borderColor: 'black', - // borderStyle: 'solid', - // height: 40, - // width: 200, - // marginTop: 30, - // marginBottom: 20, - // padding: 10, - // }, }); \ No newline at end of file -- GitLab From 519a3b25c2774b4cf7657b62a5162559ac5eb7f5 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 21 Nov 2024 16:55:19 +0100 Subject: [PATCH 024/283] feat: add modal --- screens/PlayingQuiz/PlayingQuiz.tsx | 7 ++++--- screens/PlayingQuiz/PlayingQuizHeader.tsx | 9 ++++++++- styles/ButtonsStyles.ts | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 91dc961..00b21ac 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -4,6 +4,7 @@ import PlayingQuizHeader from "./PlayingQuizHeader"; import {useEffect, useState} from "react"; import {transformToQuizModel} from "../../helper/QuizHelper"; import QuizModel from "../../model/QuizModel"; +import TemplateDuo from "../../templates/TemplateDuo"; const optionsTheme = [ "General Knowledge", @@ -108,9 +109,9 @@ export default function PlayingQuiz({route, navigation}:Props) { }; return ( - <TemplateScreen - childrenTop={<PlayingQuizHeader quiz={quiz} ></PlayingQuizHeader>} - childrenBot={<PlayingQuizBody quiz={quiz} onAnswerSelected={handleAnswer} isAlreadyPlayed={isAlreadyPlayed} setIsAlreadyPlayed={setIsAlreadyPlayed}></PlayingQuizBody>} + <TemplateDuo + childrenHeader={<PlayingQuizHeader quiz={quiz} ></PlayingQuizHeader>} + childrenBody={<PlayingQuizBody quiz={quiz} onAnswerSelected={handleAnswer} isAlreadyPlayed={isAlreadyPlayed} setIsAlreadyPlayed={setIsAlreadyPlayed}></PlayingQuizBody>} /> ); } \ No newline at end of file diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index 6151f62..f2a318b 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -2,6 +2,7 @@ import { View, StyleSheet, Text } from "react-native"; import { TextsStyles } from "../../styles/TextsStyles"; import QuizModel from "../../model/QuizModel"; import {useTranslation} from "react-i18next"; +import DefaultButton from "../../components/DefaultButton"; interface Props { quiz?: QuizModel; @@ -9,11 +10,17 @@ interface Props { export default function PlayingQuizHeader({quiz}: Props) { const {t} = useTranslation(); + const handleButtonBackToHomePressed = () => { + } return ( <View style={styles.container}> + <View> + <DefaultButton text="<" handleButtonPressed={handleButtonBackToHomePressed}/> + <Text>ID QUIZZ : 7</Text> + </View> <View style={styles.infoQuizContainer}> - <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.score")} : {quiz ? quiz.score : 0}</Text> <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.category")} : {quiz ? quiz.category : "?"}</Text> + <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.score")} : {quiz ? quiz.score : 0}</Text> </View> <View style={styles.QuizNumContainer}> <Text style={TextsStyles.titleText}>{t("app.screens.question.question")} {quiz ? quiz.nbActualQuestion : "?"}/{quiz ? quiz.nbQuestions : "?"}</Text> diff --git a/styles/ButtonsStyles.ts b/styles/ButtonsStyles.ts index 96dd860..ebe03d7 100644 --- a/styles/ButtonsStyles.ts +++ b/styles/ButtonsStyles.ts @@ -4,7 +4,8 @@ export const ButtonsStyles = StyleSheet.create( { backgroundColor: "#45128C", padding: 10, alignItems: "center", - justifyContent: 'center' + justifyContent: 'center', + borderRadius: 10, }, answerButton: { backgroundColor: "#F3F3F3", -- GitLab From e7b42052fec4ea48834f09f28cead05d2e84cdda Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 27 Nov 2024 10:01:21 +0100 Subject: [PATCH 025/283] feat: remaining (pas encore fonctionnel) --- services/QuizService.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index 7abd894..b377da5 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -2,7 +2,7 @@ import {EDifficulty} from "../models/EDifficulty"; import {transformToQuizModel} from "../helper/QuizHelper"; export const useQuizService = () => { - const url = "http://klebert-host.com:33036/quiz/create"; // Remplace par l'URL de ton endpoint API + const url = "https://klebert-host.com:33037/quiz/create"; // Remplace par l'URL de ton endpoint API const generateQuiz = async (amount: number, category: number, difficulty: string) => { @@ -38,6 +38,18 @@ export const useQuizService = () => { } } + // const remainingQuiz = async (quizId: number) => { + // try { + // // Envoi de la requête GET + // const response = await fetch(`${url}/${quizId}/remaining`, { + // method: "GET", + // headers: { + // "Content-Type": "application/json", + // }, + // }); + // + // // Vérification si la requête a réussi + // return { generateQuiz: generateQuiz, } -- GitLab From 4d94a7c292394a1326d27c99348c568b84e2439b Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 27 Nov 2024 10:55:22 +0100 Subject: [PATCH 026/283] feat: remaining quiz --- screens/PlayingQuiz/PlayingQuizBody.tsx | 5 ++ services/QuizService.ts | 77 ++++++++++++++++++++----- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 2b82b4d..ab06055 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -5,6 +5,7 @@ import { TextsStyles } from "../../styles/TextsStyles"; import QuizModel from "../../models/QuizModel"; import { useState, useEffect } from "react"; import { setEnabled } from "react-native/Libraries/Performance/Systrace"; +import {useQuizService} from "../../services/QuizService"; interface Props { quiz: QuizModel; @@ -17,6 +18,8 @@ export default function PlayingQuizBody({ quiz, onAnswerSelected, isAlreadyPlaye const [selectedAnswerIndex, setSelectedAnswerIndex] = useState<number | null>(null); const [isCorrect, setIsCorrect] = useState<boolean | null>(null); + const {getAnswerQuiz} = useQuizService(); + useEffect(() => { if (quiz) { setSelectedAnswerIndex(null); @@ -30,6 +33,8 @@ export default function PlayingQuizBody({ quiz, onAnswerSelected, isAlreadyPlaye const handleAnswerPress = (index: number) => { if (!isAlreadyPlayed) { + getAnswerQuiz(quiz.code); + const isAnswerCorrect = currentQuestion.correctAnswer === currentQuestion.answer[index]; setSelectedAnswerIndex(index); setIsCorrect(isAnswerCorrect); diff --git a/services/QuizService.ts b/services/QuizService.ts index b377da5..d262bab 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -14,13 +14,12 @@ export const useQuizService = () => { }; try { - // Envoi de la requête POST - const response = await fetch(url, { + const response = await fetch(url + "/quiz/create", { method: "POST", headers: { - "Content-Type": "application/json", // Type des données envoyées + "Content-Type": "application/json", }, - body: JSON.stringify(requestBody), // Sérialisation de l'objet en JSON + body: JSON.stringify(requestBody), }); // Vérification si la requête a réussi @@ -38,19 +37,65 @@ export const useQuizService = () => { } } - // const remainingQuiz = async (quizId: number) => { - // try { - // // Envoi de la requête GET - // const response = await fetch(`${url}/${quizId}/remaining`, { - // method: "GET", - // headers: { - // "Content-Type": "application/json", - // }, - // }); - // - // // Vérification si la requête a réussi - // + const remainingQuiz = async (codeQuiz: string) => { + + // Création du corps de la requête avec les variables passées + const requestBody = { + codeQuiz + }; + + try { + const response = await fetch(url + "/quiz/remaining", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(requestBody), + }); + + // Vérification si la requête a réussi + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + // Récupération des données retournées par l'API + const data = await response.json(); + const quiz = transformToQuizModel(data); + return quiz; + } catch (error) { + console.error("Error while creating quiz:", error); + throw error; // Relancer l'erreur pour la gérer au niveau de l'appelant + } + } + + const getAnswerQuiz = async (codeQuiz: string, questionID: number, answerID: number) => { + try { + const requestBody = { + codeQuiz, + questionID, + answerID + }; + const response = await fetch(url + "/quiz/answer", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(requestBody), + }); + const data = await response.json(); + const answer = transformToQuizModel(data); + return answer; + } catch (error) { + console.error("Error while fetching remaining quiz:", error); + throw error; + } + + // Vérification si la requête a réussi + + + } return { generateQuiz: generateQuiz, + getAnswerQuiz: getAnswerQuiz, } } \ No newline at end of file -- GitLab From 28095c99154006164dab76c6c874b7faace65caa Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 27 Nov 2024 21:37:04 +0100 Subject: [PATCH 027/283] feat: answer + HttpError --- App.tsx | 2 +- helper/QuizHelper.ts | 49 ++++++++---- models/AnswerModel.ts | 6 ++ models/QuestionModel.ts | 7 +- screens/EndQuiz/EndQuiz.tsx | 18 +++-- screens/PlayingQuiz/PlayingQuiz.tsx | 43 ++-------- screens/PlayingQuiz/PlayingQuizBody.tsx | 95 ++++++++++++++++------- screens/PlayingQuiz/PlayingQuizHeader.tsx | 2 +- services/HttpError.ts | 14 ++++ services/QuizService.ts | 63 ++++++++++----- 10 files changed, 188 insertions(+), 111 deletions(-) create mode 100644 models/AnswerModel.ts create mode 100644 services/HttpError.ts diff --git a/App.tsx b/App.tsx index 96cb4a2..8d4b928 100644 --- a/App.tsx +++ b/App.tsx @@ -4,7 +4,7 @@ import i18n from "./translation/i18n"; // Importez l'instance déjà initialisé import { SafeAreaProvider } from "react-native-safe-area-context"; import StackNavigator from "./routes/StackNavigator"; export default function App() { - LogBox.ignoreAllLogs(true); + // LogBox.ignoreAllLogs(true); const { t } = useTranslation(); return ( diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index b3acceb..c42135d 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -2,6 +2,7 @@ // Fonction utilitaire pour mélanger les réponses import QuizModel from "../models/QuizModel"; import QuestionModel from "../models/QuestionModel"; +import AnswerModel from "../models/AnswerModel"; const optionsTheme = [ "General Knowledge", @@ -27,20 +28,38 @@ const shuffleAnswers = (answers: string[]): string[] => { return answers; }; +// Cette méthode est utilisée quand on récupère les réponses d'une question +export const transformToAnswerModel = (data: any): AnswerModel[] => { + console.log(JSON.stringify(data, null, 2)); + const answers: AnswerModel[] = data.answers.map((answer: any) => { + return { + id: answer.id, + text: answer.text, + isCorrect: answer.isCorrect, + questionId: answer.questionId, + }; + }); + return answers; +}; + export const transformToQuizModel = (data: any): QuizModel => { // Transformation des questions - // console.log(JSON.stringify(data, null, 2)); - const questions: QuestionModel[] = data.questions.map((question: any) => { - const answers = shuffleAnswers(question.answers.map((answer: any) => answer.text)); + // Transformation des réponses pour cette question + const answers: AnswerModel[] = question.answers.map((answer: any) => { + return { + id: answer.id, + text: answer.text, + }; + }); + return { - question: question.text, - answer: answers, // Réponses mélangées - // TODO - // Pour l'instant la bonne réponse est la premiere le temps qu'on puisse récupérer la bonne réponse depuis l'API - correctAnswer: question.answers[0].text, // Hypothèse : première réponse correcte (ajuste si nécessaire) + id: question.id, + question: question.text, // Texte de la question + category: question.theme.name, + answers: answers, // Réponses associées multiple: question.type === "multiple", // Détermine si c'est une question multiple - nbOfTheQuestion: question.order, // Mapping de order vers nbOfTheQuestion + nbOfTheQuestion: question.order, // Numéro de la question }; }); @@ -48,18 +67,16 @@ export const transformToQuizModel = (data: any): QuizModel => { const quiz: QuizModel = { code: data.codeQuiz, // Mapping de codeQuiz vers code category: data.theme.name, // Extraction du nom de la catégorie - questions: questions, - nbQuestions: questions[questions.length-1].nbOfTheQuestion, // Nombre total de questions - // TODO - // nbAcutealQuestion devra pas commencer à 0 (Faute de l'API) - // nbActualQuestion: data.questionIndex, // Mapping de questionIndex vers nbActualQuestion - nbActualQuestion: 1, // Mapping de questionIndex vers nbActualQuestion - score: data.score, // Mapping direct + questions: questions, // Questions transformées + nbQuestions: questions.length, // Nombre total de questions + nbActualQuestion: data.questionIndex, // Index actuel de la question + score: data.score, // Score actuel }; return quiz; }; + export const getIdOfCategory = (category: string) => { for (let i = 0; i < optionsTheme.length; i++) { if (optionsTheme[i] === category) { diff --git a/models/AnswerModel.ts b/models/AnswerModel.ts new file mode 100644 index 0000000..4e66a15 --- /dev/null +++ b/models/AnswerModel.ts @@ -0,0 +1,6 @@ +export default interface AnswerModel { + id: number; + text: string; + isCorrect?: boolean; + questionId?: number; +} \ No newline at end of file diff --git a/models/QuestionModel.ts b/models/QuestionModel.ts index fa3cc19..527e1fc 100644 --- a/models/QuestionModel.ts +++ b/models/QuestionModel.ts @@ -1,7 +1,10 @@ +import AnswerModel from "./AnswerModel"; + export default interface QuestionModel { + id: number; question: string; - answer: string[]; - correctAnswer: string; + category: string; + answers: AnswerModel[]; multiple: boolean; nbOfTheQuestion: number; } \ No newline at end of file diff --git a/screens/EndQuiz/EndQuiz.tsx b/screens/EndQuiz/EndQuiz.tsx index ea72db6..12db032 100644 --- a/screens/EndQuiz/EndQuiz.tsx +++ b/screens/EndQuiz/EndQuiz.tsx @@ -3,14 +3,22 @@ import DefaultButton from "../../components/DefaultButton"; import { TextsStyles } from "../../styles/TextsStyles"; import { ButtonsStyles } from "../../styles/ButtonsStyles"; import {useTranslation} from "react-i18next"; +import QuizModel from "../../models/QuizModel"; +import {NavigationProp, RouteProp} from "@react-navigation/native"; + +type RoutePropsType = { + EndQuiz: { + quiz: QuizModel; + }; +}; interface Props { - navigation: any; - route: any; + navigation: NavigationProp<any>; + route: RouteProp<RoutePropsType, "EndQuiz">; } export default function EndQuiz({ navigation, route }: Props) { - const { score, quiz } = route.params; + const { quiz } = route.params; const {t} = useTranslation(); const handleButtonRePlayPressed = () => { quiz.nbActualQuestion = 1; @@ -33,8 +41,8 @@ export default function EndQuiz({ navigation, route }: Props) { return ( <View style={styles.container}> <Image source={require('../../assets/FondTemplate.png')} style={styles.image}/> - <Text style={TextsStyles.titleText}>{t("app.screens.endQuiz.score")} : {score}</Text> - <Text style={TextsStyles.subtitleText}>{((score/quiz.nbQuestions)*100 > 50 ) ? t("app.screens.endQuiz.lose") : t("app.screens.endQuiz.win")}</Text> + <Text style={TextsStyles.titleText}>{t("app.screens.endQuiz.score")} : {quiz.score}</Text> + <Text style={TextsStyles.subtitleText}>{((quiz.score/quiz.nbQuestions)*100 > 50 ) ? t("app.screens.endQuiz.lose") : t("app.screens.endQuiz.win")}</Text> <View style={styles.buttonContainer}> <DefaultButton text={t("app.screens.endQuiz.replay")} handleButtonPressed={handleButtonRePlayPressed} buttonStyle={ButtonsStyles.defaultButton}></DefaultButton> <DefaultButton text={t("app.screens.endQuiz.goHome")} handleButtonPressed={handleButtonHomePressed}></DefaultButton> diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 559abdc..dc838f5 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -1,10 +1,10 @@ -import TemplateScreen from "../../templates/TemplateScreen"; import PlayingQuizBody from "./PlayingQuizBody"; import PlayingQuizHeader from "./PlayingQuizHeader"; import {useEffect, useState} from "react"; -import {transformToQuizModel} from "../../helper/QuizHelper"; -import QuizModel from "../../model/QuizModel"; import TemplateDuo from "../../templates/TemplateDuo"; +import QuestionModel from "../../models/QuestionModel"; +import QuizModel from "../../models/QuizModel"; +import {NavigationProp} from "@react-navigation/native"; const optionsTheme = [ "General Knowledge", @@ -24,7 +24,7 @@ const optionsTheme = [ interface Props { route: any; - navigation: any; + navigation: NavigationProp<any>; } export default function PlayingQuiz({route, navigation}:Props) { @@ -32,43 +32,10 @@ export default function PlayingQuiz({route, navigation}:Props) { const [isAlreadyPlayed, setIsAlreadyPlayed] = useState(false); const [quiz, setQuiz] = useState<QuizModel>(quizGenerated); - - const handleAnswer = async (selectedAnswerIndex: number) => { - if (!quiz) return; - - const currentQuestion = quiz.questions[quiz.nbActualQuestion - 1]; - const correctAnswer = currentQuestion.correctAnswer; - const selectedAnswer = currentQuestion.answer[selectedAnswerIndex]; - - // Vérifier si la réponse est correcte - if (selectedAnswer === correctAnswer) { - setQuiz((prevQuiz) => { - if (prevQuiz) { - return { ...prevQuiz, score: prevQuiz.score + 1 }; - } - return prevQuiz; - }); - } - - // Attendre 2 secondes avant de passer à la question suivante - await new Promise((resolve) => setTimeout(resolve, 2000)); - - // Passer à la question suivante - setQuiz((prevQuiz) => { - if (prevQuiz && prevQuiz.nbActualQuestion < prevQuiz.nbQuestions) { - console.log("new question") - setIsAlreadyPlayed(false); - return { ...prevQuiz, nbActualQuestion: prevQuiz.nbActualQuestion + 1 }; - } - navigation.navigate("EndQuiz", { score: prevQuiz?.score, quiz: quiz }); - return prevQuiz; // Fin du quiz si toutes les questions ont été posées - }); - }; - return ( <TemplateDuo childrenHeader={<PlayingQuizHeader quiz={quiz} ></PlayingQuizHeader>} - childrenBody={<PlayingQuizBody quiz={quiz} onAnswerSelected={handleAnswer} isAlreadyPlayed={isAlreadyPlayed} setIsAlreadyPlayed={setIsAlreadyPlayed}></PlayingQuizBody>} + childrenBody={<PlayingQuizBody quiz={quiz} setQuiz={setQuiz} isAlreadyPlayed={isAlreadyPlayed} setIsAlreadyPlayed={setIsAlreadyPlayed} navigation={navigation}></PlayingQuizBody>} /> ); } \ No newline at end of file diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index ab06055..44afa35 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -1,48 +1,80 @@ -import { View, StyleSheet } from "react-native"; +import { View, StyleSheet, Text } from "react-native"; import DefaultButton from "../../components/DefaultButton"; import { ButtonsStyles } from "../../styles/ButtonsStyles"; import { TextsStyles } from "../../styles/TextsStyles"; import QuizModel from "../../models/QuizModel"; -import { useState, useEffect } from "react"; -import { setEnabled } from "react-native/Libraries/Performance/Systrace"; -import {useQuizService} from "../../services/QuizService"; +import React, { useState, useEffect, SetStateAction, Dispatch } from "react"; +import { useQuizService } from "../../services/QuizService"; +import QuestionModel from "../../models/QuestionModel"; +import AnswerModel from "../../models/AnswerModel"; +import {NavigationProp} from "@react-navigation/native"; +import HttpError from "../../services/HttpError"; interface Props { quiz: QuizModel; - onAnswerSelected: (index: number) => void; + setQuiz: Dispatch<SetStateAction<QuizModel>>; isAlreadyPlayed: boolean; setIsAlreadyPlayed: (isAlreadyPlayed: boolean) => void; + navigation: NavigationProp<any>; } -export default function PlayingQuizBody({ quiz, onAnswerSelected, isAlreadyPlayed, setIsAlreadyPlayed }: Props) { +export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsAlreadyPlayed, navigation }: Props) { const [selectedAnswerIndex, setSelectedAnswerIndex] = useState<number | null>(null); const [isCorrect, setIsCorrect] = useState<boolean | null>(null); + const [correctAnswer, setCorrectAnswer] = useState<AnswerModel | null>(null); - const {getAnswerQuiz} = useQuizService(); + const { getAnswerQuiz } = useQuizService(); useEffect(() => { if (quiz) { setSelectedAnswerIndex(null); setIsCorrect(null); + setCorrectAnswer(null); } }, [quiz?.nbActualQuestion]); if (!quiz) return null; - const currentQuestion = quiz.questions[quiz.nbActualQuestion-1]; + const currentQuestion: QuestionModel = quiz.questions[quiz.nbActualQuestion - 1]; - const handleAnswerPress = (index: number) => { - if (!isAlreadyPlayed) { - getAnswerQuiz(quiz.code); + const handleAnswerPress = async (index: number) => { + if (isAlreadyPlayed) return; - const isAnswerCorrect = currentQuestion.correctAnswer === currentQuestion.answer[index]; - setSelectedAnswerIndex(index); - setIsCorrect(isAnswerCorrect); + const answers = await getAnswerQuiz(quiz.code, currentQuestion.id, currentQuestion.answers[index].id); - onAnswerSelected(index); + if(HttpError.isHttpError(answers)) + { + navigation.navigate("Home"); + return; + } + + const correctAnswer = getCorrectAnswer(answers); + const isAnswerCorrect = currentQuestion.answers[index].id === correctAnswer.id; + + setSelectedAnswerIndex(index); + setIsCorrect(isAnswerCorrect); + setCorrectAnswer(correctAnswer); + setIsAlreadyPlayed(true); - setIsAlreadyPlayed(true); + await new Promise((resolve) => setTimeout(resolve, 2000)); + + if(quiz.nbActualQuestion === quiz.questions.length) { + navigation.navigate("EndQuiz", { quiz: quiz }); } + + setQuiz((prevQuiz) => ({ + ...prevQuiz, + nbActualQuestion: prevQuiz.nbActualQuestion + 1, + score: isAnswerCorrect ? prevQuiz.score + 1 : prevQuiz.score + })); + setIsAlreadyPlayed(false); + + }; + + const getCorrectAnswer = (answers: AnswerModel[]): AnswerModel => { + const correctAnswer = answers.find((answer) => answer.isCorrect); + if (!correctAnswer) throw new Error("No correct answer found"); + return correctAnswer; }; const getButtonStyle = (index: number) => { @@ -51,7 +83,7 @@ export default function PlayingQuizBody({ quiz, onAnswerSelected, isAlreadyPlaye if (index === selectedAnswerIndex) { return isCorrect ? styles.correctAnswer : styles.incorrectAnswer; } - if (currentQuestion.correctAnswer === currentQuestion.answer[index]) { + if (currentQuestion.answers[index].id === correctAnswer?.id) { return styles.correctAnswer; } return ButtonsStyles.answerButton; @@ -63,7 +95,7 @@ export default function PlayingQuizBody({ quiz, onAnswerSelected, isAlreadyPlaye if (index === selectedAnswerIndex) { return isCorrect ? styles.correctText : styles.incorrectText; } - if (currentQuestion.correctAnswer === currentQuestion.answer[index]) { + if (currentQuestion.answers[index].id === correctAnswer?.id) { return styles.correctText; } return TextsStyles.defaultText; @@ -71,15 +103,19 @@ export default function PlayingQuizBody({ quiz, onAnswerSelected, isAlreadyPlaye return ( <View style={styles.buttonContainer}> - {currentQuestion.answer.map((answer, index) => ( - <DefaultButton - key={index} - text={answer} - handleButtonPressed={() => handleAnswerPress(index)} - buttonStyle={getButtonStyle(index)} - buttonText={getTextStyle(index)} // Applique le style de texte dynamique - /> - ))} + {currentQuestion ? ( + currentQuestion.answers.map((answer, index) => ( + <DefaultButton + key={index} + text={answer.text} + handleButtonPressed={() => handleAnswerPress(index)} + buttonStyle={getButtonStyle(index)} + buttonText={getTextStyle(index)} + /> + )) + ) : ( + <Text style={styles.loadingText}>Loading...</Text> + )} </View> ); } @@ -111,4 +147,9 @@ const styles = StyleSheet.create({ fontWeight: 'bold', color: 'red', }, + loadingText: { + fontSize: 18, + color: 'gray', + textAlign: 'center', + }, }); diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index 76eb64a..8014894 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -19,7 +19,7 @@ export default function PlayingQuizHeader({quiz}: Props) { <Text>ID QUIZZ : 7</Text> </View> <View style={styles.infoQuizContainer}> - <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.category")} : {quiz ? quiz.category : "?"}</Text> + <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.category")} : {quiz ? quiz.questions[quiz.nbActualQuestion-1].category : "Loading..."}</Text> <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.score")} : {quiz ? quiz.score : 0}</Text> </View> <View style={styles.QuizNumContainer}> diff --git a/services/HttpError.ts b/services/HttpError.ts new file mode 100644 index 0000000..82fd80f --- /dev/null +++ b/services/HttpError.ts @@ -0,0 +1,14 @@ +export default class HttpError extends Error { + statusCode: number; + + constructor(statusCode: number, message: string) { + super(message); // Appelle le constructeur de Error + this.statusCode = statusCode; + this.name = "HttpError"; // Nom de l'erreur + } + + // Méthode pour vérifier si une erreur est un HttpError + static isHttpError(error: unknown): error is HttpError { + return error instanceof HttpError; + } +} diff --git a/services/QuizService.ts b/services/QuizService.ts index d262bab..831fe57 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -1,8 +1,10 @@ import {EDifficulty} from "../models/EDifficulty"; -import {transformToQuizModel} from "../helper/QuizHelper"; +import {transformToAnswerModel, transformToQuizModel} from "../helper/QuizHelper"; +import AnswerModel from "../models/AnswerModel"; +import HttpError from "./HttpError"; export const useQuizService = () => { - const url = "https://klebert-host.com:33037/quiz/create"; // Remplace par l'URL de ton endpoint API + const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API const generateQuiz = async (amount: number, category: number, difficulty: string) => { @@ -22,18 +24,23 @@ export const useQuizService = () => { body: JSON.stringify(requestBody), }); - // Vérification si la requête a réussi if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + throw new HttpError(response.status, "HTTP error! status: " + response.status); } // Récupération des données retournées par l'API const data = await response.json(); + console.log(JSON.stringify(data, null, 2)); const quiz = transformToQuizModel(data); return quiz; } catch (error) { - console.error("Error while creating quiz:", error); - throw error; // Relancer l'erreur pour la gérer au niveau de l'appelant + console.error("Error while generating quiz:", error); + + if (HttpError.isHttpError(error)) { + return error; + } else { + return new HttpError(500, "Unexpected error: " + error); + } } } @@ -53,9 +60,8 @@ export const useQuizService = () => { body: JSON.stringify(requestBody), }); - // Vérification si la requête a réussi if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + throw new HttpError(response.status, "HTTP error! status: " + response.status); } // Récupération des données retournées par l'API @@ -64,17 +70,26 @@ export const useQuizService = () => { return quiz; } catch (error) { console.error("Error while creating quiz:", error); - throw error; // Relancer l'erreur pour la gérer au niveau de l'appelant + + if (HttpError.isHttpError(error)) { + return error; + } else { + return new HttpError(500, "Unexpected error: " + error); + } } } - const getAnswerQuiz = async (codeQuiz: string, questionID: number, answerID: number) => { + const getAnswerQuiz = async (codeQuiz: string, questionID: number, answerID: number) : Promise<AnswerModel[] | HttpError> => { + console.log("codeQuiz : " + codeQuiz); + console.log("questionID : " + questionID); + console.log("answerID : " + answerID); try { const requestBody = { - codeQuiz, - questionID, - answerID + codeQuiz: codeQuiz, + questionID: questionID, + answerID: answerID }; + const response = await fetch(url + "/quiz/answer", { method: "POST", headers: { @@ -82,17 +97,23 @@ export const useQuizService = () => { }, body: JSON.stringify(requestBody), }); - const data = await response.json(); - const answer = transformToQuizModel(data); - return answer; - } catch (error) { - console.error("Error while fetching remaining quiz:", error); - throw error; - } - // Vérification si la requête a réussi + if (!response.ok) { + throw new HttpError(response.status, "HTTP error! status: " + response.status); + } + const data = await response.json(); + const answers = transformToAnswerModel(data); + return answers; + } catch (error) { + console.log("Error while fetching answers:", error); + if (HttpError.isHttpError(error)) { + return error; + } else { + return new HttpError(500, "Unexpected error: " + error); + } + } } return { generateQuiz: generateQuiz, -- GitLab From 3fef0b4eaf9912e1a3dc1050b2c4892c7cec6ef4 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 28 Nov 2024 14:39:40 +0100 Subject: [PATCH 028/283] feat: lancer le quizz avec le code depuis la modal dans le home --- components/ModalJoinQuiz.tsx | 43 ++++++++++++++++++++--- helper/QuizHelper.ts | 2 +- screens/GenerateQuiz/GenerateQuizBody.tsx | 2 +- screens/PlayingQuiz/PlayingQuiz.tsx | 14 +++++--- screens/PlayingQuiz/PlayingQuizHeader.tsx | 1 + services/QuizService.ts | 7 ++-- 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/components/ModalJoinQuiz.tsx b/components/ModalJoinQuiz.tsx index e05bb57..d2ce415 100644 --- a/components/ModalJoinQuiz.tsx +++ b/components/ModalJoinQuiz.tsx @@ -1,5 +1,8 @@ import { Modal, View, Text, StyleSheet, TextInput } from "react-native"; import DefaultButton from "./DefaultButton"; +import {useState} from "react"; +import {useQuizService} from "../services/QuizService"; +import HttpError from "../services/HttpError"; interface Props { navigation: any; @@ -8,13 +11,40 @@ interface Props { } export default function ModalJoinQuiz({ navigation, modalVisible, setModalVisible }: Props) { + + const [isError, setIsError] = useState<boolean>(false); + const [errorMessage, setErrorMessage] = useState<string>(""); + const [codeQuiz, setCodeQuiz] = useState<string>(""); + const {remainingQuiz} = useQuizService(); const handleButtonClosePressed = () => { setModalVisible(!modalVisible); + setIsError(false); + setErrorMessage(""); } - const handleButtonJoinQuizPressed = () => { - setModalVisible(!modalVisible); - navigation.navigate("CreateQuiz");//TODO: changer la direction + const handleButtonJoinQuizPressed = async () => { + const quiz = await remainingQuiz(codeQuiz); + if(HttpError.isHttpError(quiz)){ + setIsError(true); + setErrorMessage("Code quiz invalide"); + return; + } + + if(!quiz){ + console.log("vvvvvvvvvvv"); + } + + navigation.reset({ + index: 0, + routes: [ + { + name: "PlayingQuiz", + params: {quizRecovered: quiz}, + }, + ] + }); + + } return ( @@ -34,7 +64,8 @@ export default function ModalJoinQuiz({ navigation, modalVisible, setModalVisibl </View> <View style={styles.containerCode}> <Text style={styles.textCode}>ENTER THE QUIZ ID</Text> - <TextInput placeholder="TYPE A CODE" style={styles.textInputCode}></TextInput> + {isError && <Text style={styles.textError}>{errorMessage}</Text>} + <TextInput placeholder="TYPE A CODE" style={styles.textInputCode} onChangeText={(text) => setCodeQuiz(text)}></TextInput> <DefaultButton text="PLAY" handleButtonPressed={handleButtonJoinQuizPressed} buttonStyle={styles.buttonCode}/> </View> </View> @@ -93,6 +124,10 @@ const styles = StyleSheet.create({ textCode: { fontSize: 14 }, + textError: { + fontSize: 14, + color: "red" + }, textInputCode: { borderColor: '#1FA9FF', borderRadius: 10, diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index c42135d..1eb2b4f 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -68,7 +68,7 @@ export const transformToQuizModel = (data: any): QuizModel => { code: data.codeQuiz, // Mapping de codeQuiz vers code category: data.theme.name, // Extraction du nom de la catégorie questions: questions, // Questions transformées - nbQuestions: questions.length, // Nombre total de questions + nbQuestions: data.questionCount, // Nombre total de questions nbActualQuestion: data.questionIndex, // Index actuel de la question score: data.score, // Score actuel }; diff --git a/screens/GenerateQuiz/GenerateQuizBody.tsx b/screens/GenerateQuiz/GenerateQuizBody.tsx index 8c8a977..198f438 100644 --- a/screens/GenerateQuiz/GenerateQuizBody.tsx +++ b/screens/GenerateQuiz/GenerateQuizBody.tsx @@ -44,7 +44,7 @@ export default function GenerateQuizBody({navigation}:Props) { routes: [ { name: "PlayingQuiz", - params: {quizGenerated: quizGenerated}, + params: {quizRecovered: quizGenerated}, }, ] }); diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index dc838f5..9ea3d2e 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -4,7 +4,7 @@ import {useEffect, useState} from "react"; import TemplateDuo from "../../templates/TemplateDuo"; import QuestionModel from "../../models/QuestionModel"; import QuizModel from "../../models/QuizModel"; -import {NavigationProp} from "@react-navigation/native"; +import {NavigationProp, RouteProp} from "@react-navigation/native"; const optionsTheme = [ "General Knowledge", @@ -22,15 +22,21 @@ const optionsTheme = [ "Sports", "Geography"] +type RoutePropsType = { + PlayingQuiz: { + quizRecovered: QuizModel; + }; +}; + interface Props { - route: any; + route: RouteProp<RoutePropsType, "PlayingQuiz">; navigation: NavigationProp<any>; } export default function PlayingQuiz({route, navigation}:Props) { - const {quizGenerated} = route.params; + const {quizRecovered} = route.params; const [isAlreadyPlayed, setIsAlreadyPlayed] = useState(false); - const [quiz, setQuiz] = useState<QuizModel>(quizGenerated); + const [quiz, setQuiz] = useState<QuizModel>(quizRecovered); return ( <TemplateDuo diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index 8014894..56ad134 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -10,6 +10,7 @@ interface Props { export default function PlayingQuizHeader({quiz}: Props) { const {t} = useTranslation(); + const handleButtonBackToHomePressed = () => { } return ( diff --git a/services/QuizService.ts b/services/QuizService.ts index 831fe57..a3e9b37 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -2,11 +2,12 @@ import {EDifficulty} from "../models/EDifficulty"; import {transformToAnswerModel, transformToQuizModel} from "../helper/QuizHelper"; import AnswerModel from "../models/AnswerModel"; import HttpError from "./HttpError"; +import QuizModel from "../models/QuizModel"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API - const generateQuiz = async (amount: number, category: number, difficulty: string) => { + const generateQuiz = async (amount: number, category: number, difficulty: string) : Promise<QuizModel | HttpError> => { // Création du corps de la requête avec les variables passées const requestBody = { @@ -44,7 +45,7 @@ export const useQuizService = () => { } } - const remainingQuiz = async (codeQuiz: string) => { + const remainingQuiz = async (codeQuiz: string) : Promise<QuizModel | HttpError> => { // Création du corps de la requête avec les variables passées const requestBody = { @@ -66,6 +67,7 @@ export const useQuizService = () => { // Récupération des données retournées par l'API const data = await response.json(); + console.log(JSON.stringify(data, null, 2)); const quiz = transformToQuizModel(data); return quiz; } catch (error) { @@ -118,5 +120,6 @@ export const useQuizService = () => { return { generateQuiz: generateQuiz, getAnswerQuiz: getAnswerQuiz, + remainingQuiz: remainingQuiz } } \ No newline at end of file -- GitLab From 88143df93175e6991a23f68e9b7cec49eafd58a3 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 29 Nov 2024 09:42:30 +0100 Subject: [PATCH 029/283] =?UTF-8?q?fix:=20crash=20sur=20la=20derni=C3=A8re?= =?UTF-8?q?=20question=20du=20quizz?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ModalJoinQuiz.tsx | 4 ---- helper/QuizHelper.ts | 1 - screens/PlayingQuiz/PlayingQuizHeader.tsx | 6 ++++-- services/QuizService.ts | 5 ----- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/components/ModalJoinQuiz.tsx b/components/ModalJoinQuiz.tsx index d2ce415..77320ad 100644 --- a/components/ModalJoinQuiz.tsx +++ b/components/ModalJoinQuiz.tsx @@ -30,10 +30,6 @@ export default function ModalJoinQuiz({ navigation, modalVisible, setModalVisibl return; } - if(!quiz){ - console.log("vvvvvvvvvvv"); - } - navigation.reset({ index: 0, routes: [ diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index 1eb2b4f..3ff16ac 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -30,7 +30,6 @@ const shuffleAnswers = (answers: string[]): string[] => { // Cette méthode est utilisée quand on récupère les réponses d'une question export const transformToAnswerModel = (data: any): AnswerModel[] => { - console.log(JSON.stringify(data, null, 2)); const answers: AnswerModel[] = data.answers.map((answer: any) => { return { id: answer.id, diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index 56ad134..0b0b803 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -11,6 +11,8 @@ interface Props { export default function PlayingQuizHeader({quiz}: Props) { const {t} = useTranslation(); + if (quiz.nbQuestions <= quiz.nbActualQuestion - 1) return; + const handleButtonBackToHomePressed = () => { } return ( @@ -20,14 +22,14 @@ export default function PlayingQuizHeader({quiz}: Props) { <Text>ID QUIZZ : 7</Text> </View> <View style={styles.infoQuizContainer}> - <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.category")} : {quiz ? quiz.questions[quiz.nbActualQuestion-1].category : "Loading..."}</Text> + <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.category")} : {quiz.questions[quiz.nbActualQuestion-1].category ? quiz.questions[quiz.nbActualQuestion-1].category : "Loading..."}</Text> <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.score")} : {quiz ? quiz.score : 0}</Text> </View> <View style={styles.QuizNumContainer}> <Text style={TextsStyles.titleText}>{t("app.screens.question.question")} {quiz ? quiz.nbActualQuestion : "?"}/{quiz ? quiz.nbQuestions : "?"}</Text> </View> <View style={styles.QuizQuestionContainer}> - <Text style={TextsStyles.subtitleText}>{quiz ? quiz.questions[quiz.nbActualQuestion-1].question : "Loading..."}</Text> + <Text style={TextsStyles.subtitleText}>{quiz.questions[quiz.nbActualQuestion-1].question ? quiz.questions[quiz.nbActualQuestion-1].question : "Loading..."}</Text> </View> </View> ); diff --git a/services/QuizService.ts b/services/QuizService.ts index a3e9b37..690a15a 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -31,7 +31,6 @@ export const useQuizService = () => { // Récupération des données retournées par l'API const data = await response.json(); - console.log(JSON.stringify(data, null, 2)); const quiz = transformToQuizModel(data); return quiz; } catch (error) { @@ -67,7 +66,6 @@ export const useQuizService = () => { // Récupération des données retournées par l'API const data = await response.json(); - console.log(JSON.stringify(data, null, 2)); const quiz = transformToQuizModel(data); return quiz; } catch (error) { @@ -82,9 +80,6 @@ export const useQuizService = () => { } const getAnswerQuiz = async (codeQuiz: string, questionID: number, answerID: number) : Promise<AnswerModel[] | HttpError> => { - console.log("codeQuiz : " + codeQuiz); - console.log("questionID : " + questionID); - console.log("answerID : " + answerID); try { const requestBody = { codeQuiz: codeQuiz, -- GitLab From e7b9ce08ad90392a7e96e31e12d0635c1bea6b23 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 29 Nov 2024 12:06:40 +0100 Subject: [PATCH 030/283] feat: refonte du screen Home --- components/buttons/MenuButton.tsx | 32 +++++++ screens/Home/HeaderNavigation.tsx | 41 +++++++++ screens/Home/Home.tsx | 14 +++- screens/Home/Home.tsx sample | 133 ------------------------------ screens/Home/HomeChild.tsx | 51 ++++++------ templates/TemplateMenu.tsx | 45 ++++++++++ 6 files changed, 155 insertions(+), 161 deletions(-) create mode 100644 components/buttons/MenuButton.tsx create mode 100644 screens/Home/HeaderNavigation.tsx delete mode 100644 screens/Home/Home.tsx sample create mode 100644 templates/TemplateMenu.tsx diff --git a/components/buttons/MenuButton.tsx b/components/buttons/MenuButton.tsx new file mode 100644 index 0000000..a7d38f7 --- /dev/null +++ b/components/buttons/MenuButton.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import {TouchableOpacity, StyleSheet, Text} from "react-native"; + +interface Props{ + text: string + handleButtonPressed:() => void; +} +/** + * MenuButton : Bouton du menu principal + * @param text - Texte du bouton + * @param handleButtonPressed - Fonction qui sera exécutée lorsque le bouton sera pressé + */ +export default function MenuButton({text, handleButtonPressed}: Props) { + return ( + <TouchableOpacity onPress={handleButtonPressed}> + <Text style={styles.textStyle}>{text}</Text> + </TouchableOpacity> + ); +} + +const styles = StyleSheet.create({ + container: { + display: 'flex', + width: '100%', + height: '100%', + }, + textStyle: { + color: '#2ec7ff', + fontSize: 50, + fontWeight: 'bold', + } +}); \ No newline at end of file diff --git a/screens/Home/HeaderNavigation.tsx b/screens/Home/HeaderNavigation.tsx new file mode 100644 index 0000000..5b71d9c --- /dev/null +++ b/screens/Home/HeaderNavigation.tsx @@ -0,0 +1,41 @@ +import { View, StyleSheet, Image, TouchableOpacity } from "react-native"; +import { NavigationProp } from "@react-navigation/native"; + +interface Props { + navigation: NavigationProp<any>; +} + +/** + * HeaderNavigation : Header qui permet de remonter à l'écran précédent + * @param navigation - navigation + */ +export default function HeaderNavigation({ navigation }: Props) { + return ( + <View style={styles.container}> + <TouchableOpacity onPress={() => navigation.goBack()} style={styles.goBackContainer}> + <Image source={require('../../assets/GoBackNavigation.png')}/> + </TouchableOpacity> + <Image source={require('../../assets/TitleApp.png')} style={styles.titleImage} /> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', // Alignement en ligne + alignItems: 'center', // Centrer les éléments verticalement + justifyContent: 'center', // Centrer les éléments horizontalement + width: '100%', + height: 60, // Ajustez en fonction de votre design + position: 'relative', // Pour positionner l'élément de gauche + }, + goBackContainer: { + position: 'absolute', // Place l'élément indépendamment + left: 10, // Décalé à gauche + }, + titleImage: { + width: 170, // Ajustez en fonction de la taille de l'image + height: 80, + resizeMode: 'contain', // Pour conserver les proportions + }, +}); diff --git a/screens/Home/Home.tsx b/screens/Home/Home.tsx index d0b56d4..53bb379 100644 --- a/screens/Home/Home.tsx +++ b/screens/Home/Home.tsx @@ -1,15 +1,22 @@ -import { View, StyleSheet } from "react-native"; +import { View, StyleSheet, Text } from "react-native"; import TemplateMono from "../../templates/TemplateMono"; import HomeChild from "./HomeChild"; +import TemplateMenu from "../../templates/TemplateMenu"; +import {NavigationProp} from "@react-navigation/native"; +import HeaderNavigation from "./HeaderNavigation"; interface Props { - navigation: any; + navigation: NavigationProp<any>; } export default function Home({navigation}: Props) { return ( <View style={styles.containerGlobal}> - <TemplateMono children={<HomeChild navigation={navigation}/>}/> + <TemplateMenu navigation={navigation} headerNavigation={false}> + <View> + <HomeChild navigation={navigation}/> + </View> + </TemplateMenu> </View> ); } @@ -19,5 +26,6 @@ const styles = StyleSheet.create({ display: 'flex', width: '100%', height: '100%', + flexDirection: 'column', }, }); \ No newline at end of file diff --git a/screens/Home/Home.tsx sample b/screens/Home/Home.tsx sample deleted file mode 100644 index 5e4d8d6..0000000 --- a/screens/Home/Home.tsx sample +++ /dev/null @@ -1,133 +0,0 @@ -import {StyleSheet, View, Text, Modal, TextInput, Image} from "react-native"; -import DefaultButton from "../../components/DefaultButton"; -import {TextsStyles} from "../../styles/TextsStyles"; -import {useTranslation} from "react-i18next"; -import { useState } from "react"; -import { ButtonsStyles } from "../../styles/ButtonsStyles"; - -interface Props { - navigation: any; -} -export default function Home({navigation}: Props) { - const [modalVisible, setModalVisible] = useState(false); - - // const handleButtonPlayPressed = () => { - // setModalVisible(true); - // } - - const {t} = useTranslation(); - const handleButtonCreateQuizPressed = () => { - navigation.navigate("CreateQuiz"); - } - - const handleButtonValidatePressed = () => { - setModalVisible(false); - } - return ( - <View> - <Image source={require('../../assets/FondTemplate.png')} style={styles.image}/> - <View style={styles.container}> - <View style={styles.titleContainer}> - <Text style={styles.titleText}>{"SKITSKOLA"}</Text> - </View> - - <View style={styles.buttonContainer}> - <View style={styles.ButtonsStyle}> - {/* <DefaultButton text={t("app.screens.home.play")} handleButtonPressed={handleButtonPlayPressed}></DefaultButton> */} - <DefaultButton text={t("app.screens.home.createQuiz")} handleButtonPressed={handleButtonCreateQuizPressed} buttonStyle={ButtonsStyles.movingButton} buttonText={TextsStyles.defaultText}></DefaultButton> - </View> - </View> - {/* <Modal - animationType="slide" - transparent={true} - visible={modalVisible} - onRequestClose={() => { - setModalVisible(!modalVisible); - }}> - <View style={styles.centeredView}> - <View style={styles.modalView}> - <Text style={TextsStyles.subtitleText}>{t.("app.screens.home.joinQuiz")}</Text> - <TextInput placeholder={t("app.screens.home.codeQuiz")} style={styles.codeTextInput} keyboardType="numeric"/> - <DefaultButton text="V" handleButtonPressed={handleButtonValidatePressed} buttonStyle={styles.button}></DefaultButton> - </View> - </View> - </Modal> */} - </View> - </View> - ) -} - -const styles = StyleSheet.create({ - container: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - height: '100%', - }, - buttonContainer: { - flex: 1, - alignItems: 'center', - justifyContent: 'flex-start', - width: '100%', - paddingVertical: '5%' - }, - titleContainer: { - display : 'flex', - flexDirection: 'column', - justifyContent: 'flex-end', - alignItems: 'center', - width: '100%', - height: '40%', - }, - ButtonsStyle: { - display : 'flex', - flexDirection: 'column', - justifyContent: 'flex-start', - alignItems: 'center', - gap: 20, - width: '80%', - height: '60%', - marginBottom: "20%", - }, - centeredView: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - modalView: { - height: 250, - width: 320, - margin: 20, - backgroundColor: 'white', - borderRadius: 20, - padding: 35, - alignItems: 'center', - borderWidth: 2, - borderColor: 'black', - borderStyle: 'solid', - }, - codeTextInput: { - borderWidth: 2, - borderColor: 'black', - borderStyle: 'solid', - height: 40, - width: 200, - marginTop: 30, - marginBottom: 20, - padding: 10, - }, - button: { - width: 50, - height: 50, - }, - image: { - width: '100%', - height: '100%', - position: 'absolute', - }, - titleText: { - fontSize: 30, - fontStyle: 'italic' - } -}); \ No newline at end of file diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index a03a84a..67ae101 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -4,11 +4,17 @@ import {useTranslation} from "react-i18next"; import { useState } from "react"; import { TextsStyles } from "../../styles/TextsStyles"; import ModalJoinQuiz from "../../components/ModalJoinQuiz"; +import MenuButton from "../../components/buttons/MenuButton"; +import {NavigationProp} from "@react-navigation/native"; interface Props { - navigation: any; + navigation: NavigationProp<any>; } +/** + * HomeChild : Enfant de l'écran Home, il contient le menu principal + * @param navigation - navigation + */ export default function HomeChild({navigation}: Props) { const [modalVisible, setModalVisible] = useState(false); const {t} = useTranslation(); @@ -23,15 +29,15 @@ export default function HomeChild({navigation}: Props) { return ( <View style={styles.containerGlobal}> - <View style={styles.containerAction}> - <Image source={require('../../assets/ImageJoinQuiz.png')} style={styles.image}/> - <DefaultButton text={t("app.screens.home.codeQuiz")} handleButtonPressed={handleButtonJoinPressed} buttonStyle={styles.button}/> + <View style={styles.imageContainer}> + <Image source={require('../../assets/TitleApp.png')} style={styles.image} /> </View> - <View style={styles.containerAction}> - <Image source={require('../../assets/ImageGenerateQuiz.png')} style={styles.image}/> - <DefaultButton text={t("app.screens.home.createQuiz")} handleButtonPressed={handleButtonGeneratePressed} buttonStyle={styles.button}/> + <View style={styles.buttonContainer}> + <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonJoinPressed}/> + <MenuButton text={"PLAY A GAME"} handleButtonPressed={handleButtonGeneratePressed}/> + <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonJoinPressed}/> </View> - <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> + <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> </View> ); } @@ -41,30 +47,25 @@ const styles = StyleSheet.create({ display: 'flex', width: '100%', height: '100%', - justifyContent: 'space-around', + justifyContent: 'flex-start', alignItems: 'center', }, - containerAction: { - backgroundColor: '#F3F3F3', - width: '90%', - height: '45%', - padding: '8%', - borderRadius: 40, - shadowColor: '#171717', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.2, - shadowRadius: 3, - elevation: 5, - }, image: { - height: '60%', - width: '100%', - borderTopLeftRadius: 40, - borderTopRightRadius: 40, }, button: { borderRadius: 40, height: '35%', marginTop: '5%' }, + imageContainer: { + display: 'flex', + alignItems: 'center', + marginTop: '15%', + marginBottom: '35%', + }, + buttonContainer: { + display: 'flex', + flexDirection: 'column', + gap: '7%', + } }); \ No newline at end of file diff --git a/templates/TemplateMenu.tsx b/templates/TemplateMenu.tsx new file mode 100644 index 0000000..ee54727 --- /dev/null +++ b/templates/TemplateMenu.tsx @@ -0,0 +1,45 @@ +import {ImageBackground, Text, View, StyleSheet, SafeAreaView} from "react-native"; +import HeaderNavigation from "../screens/Home/HeaderNavigation"; +import {NavigationProp} from "@react-navigation/native"; + +interface Props{ + children: React.ReactNode + navigation: NavigationProp<any>; + headerNavigation: boolean; +} + +/** + * TemplateMenu : Template du menu principal + * @param children - Contenu du menu + * @param navigation - Navigation + * @param headerNavigation - Si le menu est en haut de page + */ +export default function TemplateMenu({children, navigation, headerNavigation}: Props) { + return ( + <View style={styles.container}> + + <ImageBackground source={require('../assets/MenuBackground.png')} style={styles.image}> + <SafeAreaView> + {headerNavigation && <HeaderNavigation navigation={navigation}/>} + <View style={styles.container}> + {children} + </View> + </SafeAreaView> + </ImageBackground> + + </View> + ); +} + + +const styles = StyleSheet.create({ + container: { + display: 'flex', + width: '100%', + height: '100%', + }, + image: { + height: '100%', + width: '100%', + } +}); \ No newline at end of file -- GitLab From 8f3dab7a83af8a3604b04eaca38e4e72049c684c Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 29 Nov 2024 14:23:12 +0100 Subject: [PATCH 031/283] fix: push des images --- assets/GoBackNavigation.png | Bin 0 -> 390 bytes assets/MenuBackground.png | Bin 0 -> 9992 bytes assets/TitleApp.png | Bin 0 -> 18261 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/GoBackNavigation.png create mode 100644 assets/MenuBackground.png create mode 100644 assets/TitleApp.png diff --git a/assets/GoBackNavigation.png b/assets/GoBackNavigation.png new file mode 100644 index 0000000000000000000000000000000000000000..6ce5f9c1812181299641b7ecf871b5f404a0d05f GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^B0#Ln!3HF!eAAKuQk(@Ik;M!Q+`=Ht$S`XTNRY8O z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBIcZN9$B+ufwbu@E9x@PNd+^^?<Vv^lj>f(P z%wHa`?P`=>0pv()STsmFn!n(X6XEYN%oWu2y}U=oe#wPpH3Cj?a*8U)Z+qC6SM_|I zAh6c*pt<ET6^G{i>o4(}Bv<}ez<Sy8vx9XRtCev11xDvfJAp<=e7nSBv>|Om=S@fc zHF95$oJn8^V`biGB*FN*apDH1o{m((6R#s>-f;Kz8gsGhePdl(*k*A2<31+onyq3A zUS}_7optSwy=UK+vEM%+E8_EQDbIT5bOWU*J59k0uOj(e&R$hxSi8P`-ip0R)eFv+ za|xI3{wlHJ_ue-hpRC&5LUexa?d1*iVBImZG}z|om52b2aJvlGmk;h2q-jpuf64o* g=zNt)_r5X5em~KovSe}}FxVJ8UHx3vIVCg!0H<q}O#lD@ literal 0 HcmV?d00001 diff --git a/assets/MenuBackground.png b/assets/MenuBackground.png new file mode 100644 index 0000000000000000000000000000000000000000..7e9ece3d28dbf3b8b4e92329d3e6486d13561184 GIT binary patch literal 9992 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 literal 0 HcmV?d00001 diff --git a/assets/TitleApp.png b/assets/TitleApp.png new file mode 100644 index 0000000000000000000000000000000000000000..40dba7c972a9121502a2e3fa6bac3af9072cea51 GIT binary patch literal 18261 zcmbqZV|OJ?vyE-rwvCBx+nU(6ZF^$dc5<SL?TO73=RWUmxF4!_cYo=swQAL>uDzp` z6{Qg2@Zdl|KoDi5#Z~{q_y0@-2I{}nxcu#Z0P7^J;|c--kN!Uc1<A?7`R@|cRaHt9 zq<)Uz{J#T)m571}2uM>R{HF;d2#Dl`jJSxp7wEM=bl#3e`cEHlZP%CmR)P6E42g`S z4??sOjf4ZeI_M8fhP%7LT62xF=YL?JEDi}Nl9WOtEsAI%DosN2hoo|uB1tCu`((wa z3-~?H3j|W7n(OKx2?u`PzT#%j_zrkX)9yVAo=#|MRI8>}tC!JfRLTB7w}JKtS3fBX z%euX7cCO8Gkp)Zo)K~hB7{@-LBZdO4y-uWBwf{W>FjDv~$N#FvZe3a`%%|$R8wD-M zQagO%(2BLB%)-8BnQyzOTUr=*qwQ(dEX}C%eNVj1V9rBt`k$9S^l!O#ZmqlX!dy;8 zF53+mjHk6BC<TWh&h^=gF|-b>QwN$HW>}y6Rel+!&68QZH7baHuJ>0vtBr(dt~YvW zx22;*^$2<;ZijVW1z8W4D{AY!Yy%}VT+JUq6cL`CV+F?w_Unc{Hyq@RA7~fR31e6I z>FdkQ@OP2p{_cOTxVF4ZR|fJP?~=k_#oWE>d~e~1f8LB_XC4{qbd04CxVzDH<VUis z`=9Nd>W*@k7>cJa_5N%h%UN{@^A5>9EEVbsPju+^U($bpLh9vG50mIH1)fBs>pGb7 z%P0m)`)YPCj|0MY&{e7gn>j1#Z}X|<bx$2H&UAZ?gUxS6c!9}lNEb@;pCU3CR^gi_ z@fLC$Y@Z!ciZaaRw<bEBPQ_mLX@S~*$*%&{lGoLAK_5Hkyo=p8&?nYDbFNLB)wth3 zB^o-v|3wgcu-*dl)aJv&W)XdP!s3~-$LwB#+MYqgU1*Gc9>%IoA7Q>io%3=>29KZV z_@^GxqkjbC)@W2Q^LUN*@!EbSCK9H46R#n7Zx1?<Zk5`ZXL_c1Wv5HWM3oKWXuZbO z)i3Je*U2pPi~?U@-MD>`ry!Nwh!)Q`kpFo<QsgiyZ=#|zP|Aj%Cd#ff0~`JB&Tnd5 z+C4`|T<z-xy?$re8<@J!TzWldpNhA~MjP|XwVnjXePHjk<JHe}AvRv{wm6Z(te<a& zh#VYM<F;Oc`cYf0tN^Jt9eGVmA_Z50%mF?-H~3yV^D&K6VvYaySy+}S<u@3VHADV$ z+IDPwV?+JROwiEOL8h^^730x<$`5qSoas`@Jcn;*hBGwdYZ{%eb#|BBMP<Tv)<Drm zWnhOR`bZycPV{NcIH1kvR^U|H<)nI63Yjl{?Kvi`E?h#^VDQJrp@l8h1HkvO2T<HP zd(`anx1Li7%&(%5g9@P}+rI2vAmxI7noZ8Ty4DUUjA%3`d$nAZC0O)0B(jQoF&G8q z+Ir^B$X)}KjuAALSE}7nkcPo(mKItCT4WDcC$Bhi|8!2&*PsV6^2iP!W2Ej6yC^d; z(cn|VS*L+p5*syeUAg3ZGovXVhSolBfjgKY=Sc)>$1cim=9QTbUX=Mo_envDwdTq0 zs;3ELgUFfEZlAH7vLKPtcoJ4&Z!ppDD?D2291WO<TlhAD5WK=NZZ~4t3+QFP@;47I zq;egZWqA~vq{e_iEn48a?fAVeeUba$0;joWE|ppf;`XHI6IQp2|Na_s=d{Hd3cYGz zm_>vp_~wdzv0xdB5)?05;KX4EosV1>+Zfsh{jM{?70-L7#7m4A$%yw6UH|o{^`fNK zwJ0)HdQ0iwbg_$k)1G{7>hfJ(Ahy@WP8<)SCvqEJfLgF2d5kFAJHCkA2&{zQyc9C{ zmx>HB068o>N9Lw%Qb3t$tj}W$3w>kNQ*?z9`<)%Rn{^SJ;|(gn1Li2fZE6wQ>-jh6 z1UeA;{^Dr#?A2i0{|@3VGv`R^eOJ^e+CSqhBP?mKW2jjhZI1Z>M;#-^qIIu^8#7?+ z8=%_0O%6646ivXvze^^Rf~c7fugn~Ts9kh0coIy{JlN9Mr=kRv`}wr@<X<b-!)&j} z$>>eJ((gsG()wOa*}&OiAkDFD)lvObe#@huGyz76`#Gs?Fya)3XacOe5~O+5^r-M} zIFKub)Z9jEY+o=v*bZe4jhqh~BEA*2c{sAePRnmz@LhU#6_ZbAg(!v4;Tz*MKoEh4 z?7=?5<8Q|OV#~39bRH$V1LG0ZPnCM##9Qz-4DR`mi9%-;7_`XzWe-0=w!AYlOw4S^ zQaAbyKsyuP99aje)rP_64Y`CyVa6~}_-Uuy=H5<(1d<uEb|w&D#_^v9b6X*Y?IM5F z<-CKPfs>*$OrS5dAZE9$Gf|u}==qB4+^OMON)GzE9BkOj#+v4pYU_T)25|Y#dv5qD z|0)h_E<&3vT_;DNAJT)Er9yFyp|D*k``N@)v!&s;C;>Ju4|R5m+zy#?pLHwL6OOuo z9l6QeaCSd)(xuQIW24h4XK5~tI3RF$iH8>nfY0bORzI!gfG!a@_8=PJ+)c_^ok43C zVK{Z79(Z;#mHGHISn-R17~Zz1d<{PC&upPkbXM+=Iy90$Zlii(gQ&3Rr9Bl44VsbX zLyL5antcEt1#Db#3PURP^<nHJ?q=qg(lah+TLY3;n@>u6W0B@4X;@*D1TX=3$zRMb zL}_oat+~h117am5CMtYqjMeLngWWYNc8s=m-Kp1qn$@_v;dZhu*8b#I&?q6*Gcc!G zjQ9gYz-38G5Qz2Jbg-E=ViEH=Wf1*Jndp8%8Htey*2XV-7VW;st~S%Kc-!O0{f<U$ z6Xs|yoq~YUxYnXugs+Sckfb^}Zyh`CFE_axib>Lu$gV^F%V$#{h3v)__}u*}$^={k z;W*#|YNE%5op0+eT9SN9{;l^{pbi6t$;@D}l3-+@&hhY4YuJC%?XZ($i9e6xQY;D$ zTH_8SEX-@7^w(hip7QDVT=gH7_frS+_yYDr@?NN7dG!8;x=gquWJI!5VxfKl55bK= zul^S$pQk(igC^u?9Z{`U>ml5>XX~LgC_2C@mFy>iH*vMl_qO)k*Fm`bYw9D>_QPAR z9r%QR`~U}~Vk5tn#k1S-D|9#l`sya!zM)MfT8SMLUr)+d1xL;#FRqlJGi7AB(ZYDi zb3-vj?{r3gYxu1rJSAW#{7#(u7b`E7A&7sNbIrH}3Sz$i<uWnxIYAcN%<8g#8O(eT zQ3B+RBgnjwEK11F;=H0wVD2@UYOT$8SlagRzBFt}E8}D}3yc~=m3s~SZt5xlajOxO z7P+t5=s{_XQ_7IN7KFOp@3ZN>VGolHT6{u+xxvOAp5l2Jb(0%?a36HIIK7c?L7!%$ zjf|A;*IJu=)kkPjP^nlUNaw^^XI2UG4M9?3-0!bgkV?8v_tQgnQ5W!1=Qk5H2oNgH z{=hv!SD;VqwYZLpn~O;<z-XBOlRxKkf2Uh@eF+D0qr7riqXH248$oVwvjv#1c2mG^ zb1gklp(tn`FyMjgOhT7@2C8FCYaBNm#o06r++ZTp>bna`ZBr$d@PxF|RMEDup2o>p z{{41nAhoKL*7y38rr%Zj<F8X7psDlv?`+H(ONzNP%hvtrEB8c!w%I`2L)J&3dQn%p znIIaESl=ze0}ht0Gs$@gddB;AYcMUXZ(l29=OD~cc$(vy=3t5_2K83gR%BqufCa#! zzg-X(yg@J$QU&JJb7&BT3akp~_WJY5sJTl%!MP3n({<}Qe^xrtT@lu-p4nD6E6nR6 zOC7?l6z8&yoA^TbcN*;{)TTNuPJ5~yWhKIC56n~OVx#7=t!KkX<ge{yp5L#<t)Qrl zheadNiEHMzd|u-&d$0rb<tkAXuAvMR)8P2Rs}s+h)2Bc^uAX-@34R${+_en&PBrU| z)&)Yw$0aV$pQ!7@|I*bbzRr+PnKVTSS68o96^p?TmiE7RcWfQMDkrL~Es?nBT@FxF z;20PJnslfp4LmPd>vHM#MmXka>HkcJ$7}v28;+Qj57>pe)Siy5ap0@J;eFvwvrRmR zx*E`V11d?q?ZOh;qD42JjNX{fCdO>rOR>n&j^HYz?Cp==m-+2)LQZ@(ut7fZ8&+A^ zT|oPW>o#Vu$Xi{LJ&kVUKV}R(s{5;JKRu=bFZ0j3eB5R+(J(97)U_apOtRULhvpk! zH9ODmegCVdFGf!>_&mF1pegkrP86SzC$hf@Vqu(0&f4|n^LYn7oc~MPK2kb?P206E z+d)nh@W{V1@^vPQ3QedsAzDHY4o7<Wg#c5@-2Eb6Q15zX==oKT^l$%HPvQHgT;jXu zWpLEDvDO@tcESi4kc1$2`NwT-tcXI_z1xeJRG@t$;MgbpgZ}&(J7o99DskhUV_PQi zr)tVwZ>w|}@ILZrOZ45-_1(Ugy|=JJBymtcrov~=ZY$6?UnQ7M4~l|Sw2HD?WI8fY z&H(Pk){ibPc>MMqny(5+P#n@;)(}GSvpV7me4utw!e;ihZJ2eKR&rhB-92P_JoqFv zr-DOS{dfHi4+wtlip{7W_^fX!7vkUT5_<R>%NW?6T0wpm5#oBjpErDK@mINyYk;UX zOeMI|8~DRZDDP;C25utAV=JJn7$~>zxfh`88~lX{p_4o>)ZM?k7IgbiNp-zKF;qMU zJ<LP_wTr9m^3u=yf(;n5cekpy@Id#`wVq1$3(rGAju=i2;E;LWGYc%v>ndd*``rtL z3ig-OTd>rE^p|@}ss``CM2qlEaNMHwN`~L=g`aI2yl(#A<TksdccYmdTc=*z>$G?x z2cLtEDOxMwTOQ{{))^g0@CzBpl(CIe)mdY5LMmA&nN`y!8-=ByO%jl{tRU0Y_c+A< zaX+8T^}HzbG{Osuld)RY-fN<ruwKrs9w#~9-BQYmy#>AS!s;R~#1fiy`UoIWP{*_a z*i1hac)97g;rG5!L!XLd(yttwc7<ZTa#<h1DD7x?;;{|bBmIFT_P2QjPJVdt{pb$? z<P*g~VBBLcO`1x<0JFnjreVw^TAL*Js53pjE{@hebKvxJjG0|rpH3fmudfqFJ8O`H zwh5b)msrm_RW@j_;sB@b`(y%~ALViqX6+U&TDA8T4LL*pyK?W3r#Sb1eajQdBz9*M z3$Ak&3bwKOnmRbO<#*KJ%D2SlmGs}+^xforByhdtWi<?>N!sM0&Ey5bms7i!Xo5<g z@62^q{BKPi7X;M)GfPUy`l^*xKS9m;edM&)WCG+5x1iTF=NY)nbN<~wtUVZC=2KOS z`0Gdjs^oqSWSzV)u3rS*x9yabFytknQnz_K@8b!1$ZNxIN=WiR^p`UHv@Eqtp2+aA zM#W$25$=Rem$mGsx8{j`7sc$EexJfbS=`6@fF`ZJBDLGJZsFCs(M<Jb4>YPw4qIK= z^9|nV(>vA=uB+CIwp2~bhD)<ut9vwWY-QPBV``vwE+8DD(`&wI@0Q!bz5vb97$t$G z^V&@Gs<F`aQ%(qzq!iPQg7@phAE!Cc+=#%$7(GQkw~=vi`&ew#CV7Z+r0LPeAaIWA z2ks1Fy%wh}Uq0Iv{z&BN2!5!mf4f<2M<Ha2R5aKTSQ;&E7-_%j?wTVL6@FZ1pu=Eu zJF0<djVS$E%?!cIN|4UBeHVx?p1XB@=Zzx~7fq+kK2&dW%Rubu?hFYev@hdR?>K1= zSu;zpBoAzaHhGZopd7enq#%o5qu}`HY2=wtf?DF-y<TY_=S>cmX%-E{`igDB+swbW z2*V+{;$RrFz=q?anP^lwS(k^BeghZBnRkS(#sd?c^IEjhw17Qxs8KtIukTzHPs9ZS zlyuw8b{;2y>)WG5U!As|ot?Tqu}P7mh{3Sx4aiFxlJGO9s)`8k!U3zff0ye>FcCrt z2A&RcCq_wZ-Z1D)Vj)-|p)q$N_B;f4VwYh0SK~AT#p9WUx<0B)U+9!=3|HD}12eg; zU3(APx}6{(V+>9dWln;fn!es6=g70!FO%Z8AEgt}xvkiKa~-`($KK6Ex2lKlq3%)b zd9G&LcHMy#<Wp^_BR9yA49~J6VT{FQMuiqtOC)uWz&|)x%B0f%e+TuX_V_%1R?G~1 zD=^#SnLPr9%QVbw81%zH!MOA2){KkK27?7xU#IL7yY5S0%G5007HLf!bYd^w&RC~Y ztsHx%RLb+3f1>wmT8)e2B(Aup1z_^}HNzLa7shit1W&}Kxe3;6I0d$kmapQQSVCkv zNh<dtT|cC!A^$oKwxou}eBGt+?@q=&`&kJr2-hT1)~5r8R+J84RYj-i&LhHQu15xX za7?dDQ}SOSrNvkgBT0aSW;p6?Ib1DftxOt$*&zgRE9XkaqRA$w;y&`&Pm7p4sZh2R zOiWEV9{QYU2@?WBYasx4SmpyL%8ApC13t)_GMidg!+U*T0p+wHm>AR8^uKJAceD<; z&W3J@llGRAN8+1p>fF31uR^RyaSUBpvNfC(2GLqD)pDe1J=p$T``7s;VjnAHE)m|_ zp$;pUinjuyikmsI2EkGULjzc>UW}6~5Bap=V)k=k$*uCeIr%xCXJB3rU6$~IZCx8a zGt|te1m!vTCEG6JnE;>efd8l(H*@Y;*PPe>tXMj=<`d7e5n(IZ^C+`PWzf)i5m2^= z01;EQj1i`dhaS$iL$Tu7xf7TG%Kg|QqTz~HG{<nuVw!pQ>D)`brkLpYmEXP)=mCi9 zunp4<dvj%xuBGBY#xmJNWe_cVY8dZy-#Wq~-g}?6XvtCPaeQ$Yz^NmbH!?S3$j7j^ zL%?@NB@K+D(wdwI=tP8c4VdWh_0Wm#p^)S7R$Zb(+vIX?`up#sxJ-0SSd_2kxHU{| z+q*<?E1TJ^+;6_0m)TBl%tm~(G0RG#|JUul2X6Sd)BdHgr(f&`0=eMg_99nrVC?}C zL^w&7Y+7X)3tDh$#z8R!O+*;N`adV96>Ff|*!(I5*W;#yck(*>Y4UX$q$0d**q!|1 zU(SONN(7{R-rPS!1?@#vtoUNxe*UoC-yy0pOzEFw_plSY=tG4!S?q7w2YUmRW{q7P z<<2%kRoRAMa(b{k5xL3=@S8lTl$JdKJFT(n5dXvyJ&2Yxwl)`Ty&lQDsKO7xK{~KT z=WxDxBpnyK%I;U<d`jYw<IA>cKJl5Zv|F~s>(?}puu1B4R3m~_d~jGvKtQx5!#m>9 z#2l#~3vS+x0}U>l$vj_xWESr%f8UqP(>3%hWq#1h|78l0ygCjw_X%J_XJ+e-gqsPF zO<7+Tfq9bGT?hTx^!l!QKcA7<Yn~U{dZ~|lZBb2|;)#Sc?l%|cAvaxcPf+zZGHWq< zlWMf9o@Uyl;=x-$7!bjd=s!Ik>`k0t3r^+e`R>eMw8ll$S)Ikw>{Ft5{xKm>vI@%8 z;HzB%Tybs+PMSU}^(mS%GsvZl+!&g5%fueqpWbYBT(bh_Hm*;qzV{<@zk+=QJ3Y)F zJO4_h9+P%6P}k!sl#^4sZw?{SgXr5Og=6a(8UfFuxFM*|a+BX=b@*CX(m*?7u@G0G zqrALWF|mwu*ju%IQ82dL@8`rlj#vkHyx$!qeHG)V;SDd)>}gF0Ox~DxlS7FR3WOg5 zS+@}-XG}w92lmXUUk)2l;vdXP;OfI+YPC=R=XM0y9nFtvbLwB<ywsfRr52ZXKSA1Q zaZcb2EW^aLY3jo?*sSs+Va9yE=+P&$4v*LqJ-gxbJ=CXYZ7tv?rmmD^hm74(a+5T_ z*UI!ZApXrz`zl^&RrNscxS65$;+UhDW?3aasF#XU2!@e~g<md2<KQyo#ta}75<#P3 zGNnw}(IFghOt_o8g%{54*nW8d?=v0U;#9OeDoz{L*ImY!F7aA-17sbAn*$VD=)MPs zyub{&Ka0Ia|EhEAMNbRAdY!7+L616aKh7|;{8rJLAgrh?0-HxpmTSz-H)8wz2)9|P zpQ|2i(&moy=`x2k-8%TEb04I!t+S266LnTjx&4j``e(lOX7*|8277v2z0Ah2Npsx8 zo6X0g73Z!WT^C!a%DF#=sOMh<$%sTW)e>~z(gNtd_rbVicA#1F)sH<sy}(j6lvdkR za1Uwzb8)Va^Lmv3`kc-Vdk=l~tIHH3Zir*pZV9F+w<g{o+8eyKrVjba`;Oap?}Swu zv-f`O*91fglg`^JDfs3FIthnv+A=3Jlp;vw?@Q&5bl;8#sBb^N`D|}`Z&WsF9?<IX zSz<XSM0k3<mVs?=!rX+-(Tf{gw-iL43w0!XJSSAdBGHD480!|2Y{*&yRGocE8ThQY zBC0Yn5#G~u-;Ga+#FcpW*<M*gJ{a3H-2xf4_qZ*{^BJzpX-DDudqS>+powm)j?8Ys z##DE*7;R(MVQF;>0!(V)RsuxV-K)Ol75QK7BZzUJ(ryilKE^xY1X4&5m1cjk!Y>aT z_rjn5rob$+M5%fUEhH5w(9d2T_^E8%juODoM{LIT_ja3%s?eRERjqj@0_E(g^i?OK zGFl3HmuZ)s2;%(jB%z0cE<aJ5lK4ek5<!t10(cn?`GP16d-hBTrERx$EV4OH<XT%V z9UmhJnkA@O5;Tu1jiA*Dz6-C%k`HkOh(;O6-`cOehLGeAA)YP=>Bny6u-F(H%dqHQ ze^#4JOxEt5&b5Ji>1vtcL37mL=$STAU9F+$0@^t$bU$jgAv=#pW?Mb$5XUhetIGpb zwhlzCVb^q#Xjw@A76@F{;w^shv5wbAop9@h)87b`pGQaM^-TQk3{xA=L+`^8WSt^; z+L!rN{G97q&^_@NC;$7em|>y;(B3VZRUk>btpeg15~qJ9S|0lc)C!E1z~*so;pX5e z;aLqj?`bDknp0Ut{VJI>DGE|8PS>9%#{S+XVdEC%rpQ7IL-`1ym^p+f7n{s=souo? zV?H7@GtN(AcKY`n*tWKQE*P!+9vxj4oc5-rXOlC8FD265a=t}D9)%MGq|YxXvB4q& zZoU_$LH985FinY3PBaLb6w{T~d)imh+7Y_|y{!DbN+|xVUMJCT4u126dA~o8Ne&Bs z!lms*^Re$W%?^i?y(m3ze9zyY8Skpq-i1f)f+s$c_nebgT8)nQ=~d%ZM9e3lnPL7( zsk@(v=w07dL_Xx;H}m=~ec91{${oe*!4_3AghHI3AGfQ^enh6Znl7kELt~eVrSK(; z3R`?pyJ8}`#L16wfRE?UzOAmQsF%~enTo1VL<8LkgY&<L=uThF00g)AnAH+nJ(u3; z1psBM^9HZwqWh$-#I5IzVAa<<;l{r+iD$0W0RH*Bon*7b3{d}7h;FZau52irxQoa8 zl2W>6zJ;&c=sf(MQ)?YpQr-NR$Hd$j(L{N4R8x8qfFiNC_iFRY$Yqhl*e$kwiF8N* zsmZ{Rv*?uOitu5C*k;|WxdVP{Ev`Bc89uL<I~Xn-t^`3ql(mbD%K|j0dT_|Ds)$LL zfl2gWGs%BjIz?-P9xuVNWRg0gZV!F9S*(?dNJe~2BcxX{v8uA)_6v<GLAz~g-s<|_ z0oW~q%{B^vM2NzK_%9FIG42gIMv7xNgG*ESNGa2~mqi)}*aSqt_)&NxJ5mGqX&rpa znjyc)?!+h;A44U3A;6%KcBQ#isi&d{J}p#chCifxeDs$>M{B-tf?H>c^Lsun>L(KK z8IOt(o#}Sb-H-RQDfs5sgGJ&%8_=txiFL&*K;@1?$HdJ)i*%CYDat@!^(~&BZZH35 zSkE2YaF&`f*uIk|pT|-iij>;c_>dWSMsdo*9Mpu&y^qfC_5@jRi)qe5(D^6ThtbND zfRhnZhaS+D^ivNKzB>Q&S?e#^rr40T9cX9Rxuh)eU;Aak@cXTG)ug$_DpJlyczMB{ zE=*Vp_ChIg>bg?DM0QX9K{QixpP{{spb$3z`YG@2c=M6k#i7T0t~TT(6$PbvDxX3P zVvx>|+B};EPZftc$Oga;Bz{PUa|SgTY;-ntgCNcg`5ptttFEl&Ze6B&Ckr&qi08#C zx9(2*rt&;XS3p5Rxh`CthF}Tnfmq<HyoLw^X1Hmc+};*yTI2yu#(F*R9!B{2GnAD4 zB0b<{rr_)2K!y>k8@xzQrzx?^3XiRGX6S8@v7GGM&{Cf8R7$DvxVj0pWBuj9VN<pz za%=%ny=?6rlvz5B4Yr$%nTYKVjM(=VqHT_Y`f>Elv&X{o!mU#ZM`qiKXeL{5O}L;$ zDeN(|*MQKY`GKR?FDX0URxi*@cl|(c19a6!kdaKgBdi0eS)J!9#G5qRrgp#DkM)@= zNLlsGMeEHYGN$WMe_P)e98vAPeEQW-wF2+>w8fg`$`QwPOo+8Q{1WSbo5Q@|b`e9v zBvr?Pvk%V=iEjQN*+O_IO`vt~sy@7B+-Y33!dr)CE=xW3w=rZoWTD{3Fu~sL(k_C| zd)`utSqU*AOuno2UnbVR`jJKEBwq68hum7kT95NfU_X1b+_%yX%C(&z3NRi#HJ3vC zhN%i^h@9@&7<W31OB~>JC_<Fs`~-ET!K(geWfX;TfxUYtzWnCKnD#EQ!;lg&%T8w) z_^JYNKEUZX1|cEAy|1a{`k$ti+%2xHs)l6ZsCcPg?0h(Ta9BNyjYH((-z9dT!d^Q= zT!n#24D<Q=bB3E2z=r1124}-EldHwD`|imj7Q%72E$KB%kVOw>gic;fSl7LORfCf9 zUAvuic`a?bYi+d(W~c~wxDt#V@=MCxYzN^J7EKb+o{E_dhl1gp9h8gMoDyXpf|!3| zWvi@fH{%Y+Q)#g7Ne4@R1CI9I^INPGvhKNW!U`Y%;`=_adb>815Qf6I=(42LRxV_@ z|M8plpI8>=T=jk@e!C8xqm}B_)2y;#Di~p#tgA_4_@kXTfBOBl%sdmE&TjhYrSTu@ z$n1SHrMe}ChYU(RgMJhi#*&4vcHP0~#n<DOpSstf#4#J$3Jz*n3Rf9`6X+Thj2u8q z<4;UrNDmX@)VN^Q5N-M$aa8E=voaj++Af#-`SoAn{i)xdiygG`B!!HCz~a9_jMQB- zOU^s|0LHz1W-)p(HlN60Cuu3CAie&^(b^2E@6aAfwS$JhKFX$V-Xn0j-cgy-xwCT* z_G`91>e(Bb2jV_C8t(lIWLG!DJ5pzEo&q0+%y7>OEuYU8N@O%Iaan5OTw^0-+;=A0 z{8t%|#FcKFSNFL6^LnTD4XtU-OX*0>^u#C;geG;TWKH&gLkuD^uDsRQ1X>JF+LtWn z(pArN^)a<<`gXcj=)E7cF0W)A$ZHiX`FjQ82Z=Dme6qmIlr&hFZ+*S;;dEEH;;rkz zdG%9bo*}yYW@({hhdg_qRhZb!HS_g6>*6^Lr%7ThlD`{irt92ax-J=PVwTE!cI84O zW6qwag?P6tv6toCUQ7RCqRL1VhNA+iN+JqBUBlGI5_wgsY&wHzuTdkAS2+jhk(XnH zP~u6{zaF-4!ElX)dyPFx<J7OlmcqD5oJq>7KfZm|S#%<_^7uv2U?b+1R7=dBFLAic z+9g(Z-0xgdd$%G>o!|R*6GZvFz_QL$ChFvD{MTfI>k<KeFDUXFhc<BcxK$tK(QK<} zEXq3du{euZO)A%Gr&t1u2=CW7mPRp^E1HU*#xlr?H)xH?u|o9Zw+-2lQDMq7)}ril zDE)f{F%fTS6mQ9OXF-aqNbr4_T=)x=nUuwdh=!L_PlEdV4qt`)lJ$IHgDro@jQXYu zRiqLrWH}zGWX=7Th8^_3ko1kz?(2V?-s%}n^`&t>c+ejW`u+PLV4~P_xvB!+UB=Nd zS91C`wP;M5$hJG6BDc+W1KrA(M$G@nA2D{*{HgUs?9s=MVw~V5<vYc<RkUI#K&p0k z9pT>QDfK|+h7Y?~yf4|hulD(@hChRh+d8P)0&LjUy2N?@qh)?h3ug-u&WmzX4qg)l zTAPj$4+8Rns0FCQ^`OKSmKUH;w4h8viF)t8OY}PE^+iJ#y=%DlP$oZ<EhbxRlUdGL z?Y40<Gb1!P{)C)+zDnSwH4=9448LVKYmPVP?eZ&a+$wNIxK+G#me2G;n2fE<(2hg> zC2yovOmyoh?6JI$X58XT9(51AcgF7rPL%Q>H$MmQ(~@E9G>$t7lo2+96cmT27xubp z?jWf+PHdl@de{k=yH-0mz)U)!;Y%w6u?SP;AQ+b^th2=f_GhE@?Vw@}FDDY}`9w0v z2Kz6g&O+Q77Cy4jCi(+(Z=;Rv+ie@UD|e8qIzyyYE40iw<r}y{gUFqSr9fcx8DcSD zi#||tPxa9oD8Uwlr)UX3_M4_UHjres&hiO6ncAEr<3gl~ss4rJ@eG&0JTxWTHrX;i zb;%9FF>S(3J5}{*4(bo|W+*7nUE$d6R;mf7Zyp+A3`!!DjH~`s+zOgKbRX>1WUgKi ztYz{v4nMS)YZ;ip!HLv@YPe3;(0?d~H^fWtT~)~#C0VdmN#KVw9F`X>9|E8+!HTsO zniV$FNIQQY$_)Q%>{6x(&GvYU>q)HijkqpyTe+|CJ1PHrEg=BCB}1MO3^#EAlP4L3 z3ISxw-sv`Nb>*;XX>gQYQN?c_WCI{M2{+d-m#SA^r?EvOD0NlA`?H`S;jqM%>Esod zouN_`n4A5zYU;FnV=qgTz8NcM*dTYo@wyuB9i$@0r1ga69=IxH6YtQ-kU>7oTaZ9q z9%(<U?TxxFio->>%73Sa_gF+wj4Gv3z|Wsx%%wGghws>4=O`|JR;eWa9ctI~O7Qy( z6u~9O_?8ZMVV~IZlzby&aYM-5C4o|a1tnqe@130t;Pi`~)dN)(*N2`7$qBTRrAYu3 zr@!-Zi2TCSD>d+fcSqfAiCSugVm5hOG)?D;!YNe@V1eA_y!;s8W&!*M22?~2TTiJU zV#O?vt)$F@?OTCpinL1!Bp$57%~F#1eL|tx{dSPL)d7#Y`Ptu=z%*f;1?na$%d9&J zxNuAP;Xwl)RL{++BQqC99$+vzwOU924U<W$KU@b9M}LhzP$r9G$CX7D4qo?kGCEgw z+x8dVLUMH3X7HZwQoWQF8u>RE%@`$%DbPY;x87bUV5wWBoob_ITx2f)G3GE>dKDEA z8`#pZb=EM~eKOg6J2$vY5bQK6KYAsShZUQ*Hqm_|H@zoRJ{np@#vzOLc>C|!xy8yU zyh5Di#2-5ckJ{E|OhG02aY%=$*vxO#q>VJpht12a^+@<J>4nM1>ubSEc$3%ubx&&N ztP@6bnzvA!FAaJ@OrEK^fOUaFe6Bg4H%sAH_jXP;m_r879xG3X12-MKydVj4j_v8w z{4Yh1S7(143X>qi3a{-yvJG(TFh`vHl6^KdPN}BVPKBu`Sj%B?4}-c<3|k^Y&e|mp zfkr`TU^<TUmS$sxB`1o%d}f-Kqv}a?qZK?Zgku2BEdQcEw9tHHK|#XAL;1YHa)Z{Z zf^D1g4AjN1Eg>1ohF2Vv4j#$<wOHM%%`#EBpiJp_0?q*NSL*aD(U}67g63OCt9>V* zf&|I;=F&~1Ld-hsCIRxnF>lPI0M@}6d{lf`lM3Qm;r-%mQl}=ed0|c`nF0&T2sdDk z;ipKIHtP7AxE+KtSpo55I01f_YYLbx#xU6UNxpcZhNe6tBy!}3p1oTw!j^LGH?ewz zbc_f<AzaaRv4TYl>L0O6C1U&9`&3>!nJK*GPV_DytIABOtO}fqcBY?Y)!)j(hX}Kd z<Uq!Bm)NGBYC0%l;aGOp<@4Wj$u>mb*6v;WbJe&NRH{330dW1Dlx?#T{|kQJF3&Io zqe9N!)x1`PRe{{p7H~vRXB=AWNrW%O0;zU5g<QO)X9FUWX5?0--RpJdr&`)Q)-Q!1 z+-|I6mHZ*r>)|u8s7y~RuS4UrlPWjp@T<iPJyTERKjBR0BWS4_$oY$`6%iadfQm-i zr_)*jDD|H93<);3jwKp;kb?gQJoL`E?)O$=Jj-1}{?pWSPg$q@1hcnt`cWJ1H(3$V z?J{2>pNYy^r6yte81KAf1$M}jCqp_J{lRa3J9@kCJMa>>3vVqB0(nQI16F(090S>e z3Xa?+H9lg(fSC;?Ac2nC<v=y~%pz6vwrac1%*>x-#ZPCl1d`+k`1d@9E|ykBz~f=K zzCS=BD6a(;&WGNr_KY7qZm5m&Un9F%Om%B*jMq#yH3)G4eu?waCuUcMa#tqlFX?N! zzcfkk%uxV6d`z7hJe-(o;a-`wUM!4yXoAy9Ht$QU<9&NFkznemU4~?rhZ3@9D*?Uk zzgAVTEF<<exLYF7<+Yh{CFd5Cq1$J^H<SiPJ=wO6({?ehq?Vz7v?SC>0LFIduaggL zyV5|X3)dmYowjV|CrZNA)6UsG!|Sf}d8s~YF6GFho<-{<i5cgNVK)~GfqHSVfKK!N zqnW~i+Q1j<(_swxk=U+z%rqZj9ZrD?dkzL9if*ld8QK@ZT#LSLG5U#sx2|UemCA(2 zWHW((bGYoGHYMWbc$hAjm3SIgU}cx7ltpvYJcpbo9|*kHb00g6cQvks7aO-Tl;n<H z48a195+;kdiJtRKY}c_|ccZhA$<orl|DdL%!(Ml`*k7H8ZCirZcRqYp?C#VYB9RNe z)?#)gIv7t8gF0C#J6dEFq#0qQsHRTp(m!m1Z>V41EETTzLer2{xG+yH2|qUVpEDPD z%?{rv?`olFf=$Gt{-X57&w*Zze?rLoZ?cEsHH&EyZV6gJVL|t#+$|YQeZuYt+6~BZ z$OWpe-S05CJ1Rz{h4IqhK1G3$BA~LQ#~9ZZugm5mh3=NpWs2vCj%@S#1V*KjfFq(j zW*BCB-PIo75(qgS7cP-oEZ@J)`MarIZYKnfphe#lBEj7Dqq*kTnNyp3>dczCAb9&= z+@hr--3aRC@>!0)>6M^+Uw>APqCKN&up?E(Jgz$#P9&wP=TjC8bqPIY5BYP~!Uu3p zZYKh8Qvb3lm583RpYyh|l|epgdEB&=9e18k3-<Th>@I~C?v(E6$!*3Z%k9$oWR$}| zZsdB!fW)aNb$>-A-{1?toyLC;-I`}-CEU{>iyrD21CLTl%o5>PsdItrsr7gTWP;b( zp7=@zI<GxKQx1%9%LgfCq0+?KtH>JtBO5eqff~1`YmK9U*w<;GrgW=C&C0Zy^8Sj^ zF<BWtuBkUaA3^+yLOeANAmHfcT5s{Lxzh=~K7%d&y8HEJBO}nPGKBzn6A;#l1U!_> zTvj8!iWTkx-c7xzweHXJg6ZA=+C4-*rqXDf711Ss6u~4l3w;o}21dF7M=E;#o(!D{ zOt&vai)oKEt|no_IqJ13YtL9Ii@USJHiLW1l*fQllo9(<@Vf`nOD<T4gCw@Kx-2=w z<WoX2V*G~Fk*{opOIfihaj=L;Oh@C1MZSv^x_h3hm=yF%;M=AU-EGd)L=rV<Q*UBa z6W6F}EyPk|HVdM3tt34F%y{`g*TK_TY>^?2QL)r&<oP0|9rQ<egRF}hGecWW{XNHN z3`itY9sLS!u9JbiDNFl(^ruiDmk0drb21A)Z!#^$)XI>+wGoH&ZVzkXCsBmfa`;z4 zrUtDzJnYpI&7V>0QBH4thw2(p_?`I&W~<j#VNd?CzIX301IR%f76uae6n5~U;>Xyg z?lAqN*@DomK*I6vi4F;v_YSPQecdgsS<x`!AZ?$`BE6tD1yoe{bFQcso?;eEud{JF zH7$}#<B9Rr#R0jGFDpT(#nw4MOW{DTqpTL)S7EocQJk4LX#&^nTMI2WVKP2~89oK% zK<bAIe@7_#FiSSgR$jdvtN>e}NiE4yITb=WfnAZZgGbk+@g~;Jn{s<Zty^mX+6R)) zYrn8W7!nvFC1pTXf=#FM5OK{~xVu8Aple1!`&LfzM?h;uUtUcAs>O<dK}Z~w=1Ex; za35z*h)Xb@fXuCUN>*||kFV@w)BGl1sKAD)=|ROJd1IiqDPt&XP=W4a)NFz?PvAyU z_~Gr&HW*_sD&knlTVhJ%Dcgi}^cVOE6=^;CDls5s;EGREhAtbb!C15C6@yRdszl3J z&dnl;kg};Y6^f079y&TR(8#~_k@+=WrzQkHM%Pc3Jcp>qB|R6b2|x2`_*reOziHx- zr_XKp;;dg3BmW5A@$8$fRj0gT9jh9N-49Rfd_O{M#w;_sSKm<OKVgt#PxOYg+60V) z{2ne{bS*+!!p`h^{o<jPSlHsLWM@KEucY_GD^Vnr!DHv(P~IwD4!A<qY#P&7`8XOg zm-WIDa(1rBw4lXcdqv6Vv5brkteDY`iz@o3`uN_m3%+SGPL45=D@A3sY|b2al;N5< z<j>$-TdJd>69_A`k}s970r#U2eh8R2I=V>(0p0h$*t`KQve4B+L<8-xPac_TD4wht zZUv&9OH$E2F`g=4a5i~sa!`D2_6z`-Rt^Cn!c=$kO08wsX3bgKCP+{42@I7|K1+m0 z?W(w_Y1j!x5J1sHh}j2=z2i<Bp#%|o+6-2k&8wARlSpN1t60S|9kjyKH#<fkaT(hl z6c#69A_jhnTtVLEom!cTru*=(H>%ut2^H3uJP}@pK|`g{jz&)zw?(#%$_MfFPvW1E zqs%g687~>)KV0)&-=o<|8sk38Pk1D0d7bMc>O<{wYj?n6RUlfUX7J+eX~=-^AU2CJ zB~H5G_+CS&=tx3w@R}VA!%(je+e2h^w(*Q&d9<2X>v7!;ba0t;b9%Z$g6c#GrZ?4G z)s^yWBx7@3K_s$Zs-<3__JOm;FI%CjeSpX5$W6sC46$ezJs`~ft-*4{h<YkgvEM@f zO%o`s(zNU2s?GuWJxi^{<n5&s5qYX?GVGR-<vo>;f!S+%`LX5lbMl7Sau|*gsDzwL zY(HGv$q*F7WH)3~W#^K{l_<QTg;AK^I!#XZw@QEY3HG1ZeD{j$=pO5Q4M<-H%{cPW zn!Cx5u%O*=<T#CC(wQ7qr=V;8Ng){>!-`X`W$BY<Q76u5Vysx>8>C?*oXr^rPJfn` z4eAa@0dZ@;X)f>9@3W(~Y5{n>asW`H7yep%>S(K5UfV3KtMrMZ=uGo>bJ|ck?`r_+ z-gqb}tZIi_oF41Q`Cba^hR=2GKrR%~*&ED-Dh8|C;E47(QbBjXYKDSCzhDf71xxWC zA0fMnrBKs&9#yEe;*3QC*Y@<`N|-T5QG{T%%ArPxW)Z0;OH8|R6q`1!F*XNJfi6k6 zl)BwfRin9ny8o20E~7^ga^ZQ=rY`ySdv8wD!Vk{;dUW`~T3GPMZf1PHXWT^!e(fqY z3UN<8BuL4NAVeogU_RUz8fkGk>#rHGR$2z<w3=HyqMJa9wjNA$AUyS2+p0-eW-l^e zNm-VQ+Q#u0ouW&^9{66+)$?j7OZ)2A3-};`Hb~Th_Mals%EVkUwp2BC5MsLNmXC)B zK*(a^_)(x>A~Tt$cf-hO^wJBRtfLw!XSa%tk^f6)H7j?Dxrn3N&0^DrvP^hLYQJf- zJukrM0~rCyFaOrWB?!NCK5>-8Lovz_6@Z>JUfcTaX#YB?#P2gaf$Ab^+1rL$i}&@2 z281Sh2GIr$TA-ozV=aI=AiW!oDfQu2`K#yc;TZ0A9<w6OZ?}zV7J8K}5;I6!7$>f< zI%YN%57l;xn^Lr}$6Q^_CS;C3W#};E(Q2Nrin<4t;@rbl4I{i=%`m7|Jd`kU?P5%J zax^!0(T16hoF*U_PySPJ6f6I@iDus$s>S1!9^CbaL*{%oK4Mq`a3a!9fYnB*e9pGv zT>P}}WFhcV62&rg;=@!M+?kK1v~*5@P;>Le#j5J#%s_k&#fs0E8LLyt!o}O`z@H%Y zpC~Yx$<{_YCx}i9Bu5PisCsYOxj~>;;YL^>4ZE?t(28@B`n2Q$@)eE{6iFK1`0=e? z4vWb}?vPc|q0SpRFDu)xckj6`Bpv^L>ssmivMi3xhN=qrZd+Jll`05!;UF@d?b0;v zmFt^fghN-G+yu2<pLaHq^l$py?|5y-DKK?Bq>Y=QCz;f*XJ~I>RF9hUczah81q#p^ zE$|Duk;k;QbC9qwOS0CpFhP--v7iU?HffAQVy?3a8zc%sV@haw`^4CEMN`Ie9<v^2 z+uMXS&c+N;;n!2{mGbz~6s+nwBd+6k2GzAxHqTLom$TM|mlFN+NHm{~^o?lEwjT-Q zPB+Qn%EZ)D%8e3;P(L<)0ohY?)PR%R*Qrb5gnZLqv<O@+)mUz9lvJ`tx@VCuM154u zeI_%@#25MXPbaIjk!q7`C7YavAhO26uOV%#<JdAWHsw6F(H(~~homl3H4w(orLMej zaZU1l_rHWvI5isekURo#1u*UfLn`74dLX)g8rv8cx?kq;TD%hsiM99n3hKwLZofCQ zzGit51@||#vRUbEyA$d${_&@ifT2TEILxe2pX*t<FJT<TVScBNq8i6-{`(7;4wb$Y zp#Aqxb5fkhC$p}+^^k1Ffn^P>9cL@pN!vvEM`hPVqYD1!KZbF-_Q2nAnFY;c@-LfW z_!cxLgpZqX*0kbu!8k4Pm<8hPY-26!{V|EB?6~6u0o;Gq{B2T~5^vW&vmVv#A;)Le zfy7`1<t%G^CqQAo9ol@N_lvc1B({WDGqtMmY;k*0pK50%IgRmn#a--J?kX<Bh^mX4 zyj2=|${9WDDpVxDtR&Lh+LoV!L#}LB^u<Q!@kC^2Wfu2bxDjXv{BSIR3(238E8xH- ztnG+Vv*D3d_TTb`&Ty`>e!EqalgP8YWTtZ5HF5_UK?Dg8GbyXi<umJa6RkK#Os&2{ zD3u1znR_YH$1rVh+G8PAsI4$0v#|uR%g~?OUSENDbVvl3{`@wsKs^3+WlF<1fT}z` zIA;0bwl=a{;)p#F-vAYTm@MP<g(qQk_^RG8P8+49Rr-L$*8FcX?N@)st`agTNDYCo z`@U{Ci8h_nX5ZJhnv}f0>Naoh;<9lqjB*uIeWrM03?!j1JmH`S#6bX`S!B(H_x-M= zVYfp{Vc&^ypz$jDM4SH|wp2%5$hLfrD3U!_5ijsXfw2QyG+P@!mW#9M%me<=c8@DK zU12bnEv$aMj(a5t>wqQ(t|e|FLd8Q3qjxjoXGDonSk@S5#D;}_S6Q{hXhGsuHHC-M zhP|703F^`vOH)xrpICeJZaR{Tcg{seq6k^F(j-_e6{}PdiHH$QBrZ9mh0tQxGjM>N zu0&1*=Q<aoSK1L%L!Z06`t7kiD$l!RG8W|wiM9y{jcO2f6K<N8IF4?dRMpK`s`cY! z$e+zQYx`Xp44ybJ0GYklOhGpvMfocmq9=W9jpJr(wNv8wmg+)Nk4h|8ogl%#?jg3S z=qWj(vUNS$KunB~tBQHaa<lW^i%I`gn!283e&kfzkzMLZ6#CUxHX}1c@{4s1Gfc3Y zW}*mXY}h&0>Q}?#R)TvAov>+SGMB+q8fe=gw&8dSK}Qf9dAmdx-Z;L`^)uk-YCgd> zaI5`~ky#1r?_V}Kt+~FIC9#-l%{mB$I%nl_GDNtCclILZnTN$I)U`Z({@<w+%K;AO zc^UW(!rtS`scBIX>ato>fBq`vb?kZ6)3MvDwyfJxJgA9$S7XV25wS7~GPH#eIyJ^J zf2&8yX1$EQ`oWx$PZL7AM{m|Fq=qZtXSKR>aQOJG<EW3<H^1Ed2n?1ChJH1SwUqaA zZQ{gtEdWt311_pvqM}L<lQ;C8<!4)os>g_!VjREBhQ%dXwdfuC%(>Ki_mc){Ocfh7 zUU#2e6Y+fd;_RbQunB3wBx8ca$5#=kv3FeOHMaUvEIn-jOEQd5m9OSB5vF3N#@luj zYC7ho{H}xy3d_}$1fLIPQY%cZvfsC~;S=<n*Ae~nhf^GHPD^#1y9Z82q%|GKgdf1u zZ(2~6HnB9LB?!^6QS@0i{Tc#2&G&?I?i^=_7#ap#arfjO<Y~(lq1E<pCC^U%XSMgp zv~&({Z-==OZ^-0C9d$oB+40WO^18+oDhgIokx{%X-DXcVUlX$~t3D3B6pdCR7}i=M zmkpb7ZQcV1VEr4u;-<er{PDma(=rFc<gr8Btfq{nn%^+#C*HW>1oGUMhtG1B3f+kF zoQ%02GAZ=L0?;uPHU+JCo7R|)B`Luyby+KayQ2=7AJ#*-!UU(cLW2{{vN`Z2R6Th1 zhx3vbNz00lILi^L3DvaiaT6`<#_l-b6jc3y!RWdfeBl0x8+V{w8G~li!!!m`F#r%e z^@1pHsGlLu_}frxmCpfe;xVnmzE`fTpq)ep)ND;NzHxZ%vXH+s?l^_eZ?`rEp9=Js z?_Bs|{}DsnGd_DYoWosGL2{-dpO~r+%DD-+!+2n!lIZUU=aKs<W>p3#o|nr_?Naga z4d}pwN50wJ^_q{By4}nZnp1m3xN@2#^c%KPw?FjRNs<T3xt3XRFD~9aFADIdca@r% zx8)H&tbJJA6yuHkQ&3}wuqYr1zaBHShoxL{R%L2FmyGgxqIauSsE!}G+8EK?3PKpq z2(WbFZMy5FP2eP>hT(HdG@rh225;O8yKEH8sY|9C!W^O@9YAcD6+jSG7!Ug86vg$O z2@q#aBQl)1$y3Xd-5K0>z+E>hhXxhkk*2v?wui$?Gdf=XU~3MqGoZEpqK~Ed@8c=b zcItA5Ie;02T38%6`y)|$(fay4R5cXaRBXwY5;z)x*O_DsE)pX;tXeK)nGrLcYT}Ij z9A%%Ts^Z1}XL7xHI)y4cY+gJDQ^%EML3>H@9qjG#6@JFvHOpYLG2r?yeXWCV*?m5q zVK*vuWgPQq=}cnztDfdCb-h`Vva(!bR4i#b*>71p?(imOwMo7g;BR5KC0gG&otf6# z!F}D*chjM`;edNh^KQU+49*8)hQU(J90$Z0ksqa7W~K+F(HB&~(W+WSUvl*4TGHsD z%~A@kEmIOo9&8#d)o^Fw?x@%jG7z!AjN@*9ChY4O&}R<Tg(pVyBt0VpYaE}||7lGF zNHFmx3*@iz&%oN~-wF5KICS=zh{p#gFj)<#;7f$^1j$@&K!PW^B36l1-XGn;!O2R3 zsez!BP*jPKw-1ylVlm9B_ba9`cUk1F@;qnHxM=w{j*DKr%h1AMmE^EUpZ(!uON)Oi zI3_pf>B{Tls*5Qmlo0E(mcq4{=he7!t!LnKoJ^755CCWoF~(o+y~&qZxb{2*u4pIh zU#{D){sB(lGEUo&(xaC2G(kdOf3&@`cz-|N$KEc^bgzy9A#D<|EGVU7J=RBrRO1Fo z$05au??U4-Q<EQZ-JR^0BF6OkMilJqdWvOE0;3(T7*#Y!$j0RCw@a%eicml>*tu8P zEJL!#5tjwXetG0FE!PZp=&`*(?g>uzo;kUC?oGGXLA4Z&-R*d)n^c<qVq{X00|Z^G zo#CNg$uw-;rH(f)D_!Sz*^EX%gN7C~nWrmq3cZ|!p2|l_SAh3FvDiMAjZ7)hOz@2{ zuT@>K7r8B*sC*5?s03~Yp^h!Yi~}&FiZPj&T+QY`DLRK|{q7RJ;06RwBgom0G#6;E z_Zmn+>x=_12Rqe=$`$zLQp#)BP6deMB;kCU3w_=8=H{jY_^(#<+G_6zpA?e;qD|gc z&KLwoh6f>Zl*%^0F4o`%w_x~@;vb0n&sl`r5@bwn$>fXASR_aYGl|^E|6oUrf9)I0 z5QVKXOJQHC-%2^~Q9YjC7dUUn2TbIxa$lAlrYGF_*IrXC3Td~f$4y?c-lHi%!1S^* z|6eQj{?CNMz;S#BQA0}Xd3qSN3TbYqlex`08j@#AhB?HX6OBi6nQ`WlB$wRnpv+~? zdR&r>b#k{7uenTfS#G(FSaRRoZ7#?83(ha+&-lJxpYK~%$OXI{>1Kn>>3j{sJ2B<L zLhT}2jI(_k5NqciJ8Y$Bd#&#!_nLNP+S8CH`iQ8uvyv*c`2_?bd|{DxDLT_h3%E6- z@ym#$`+|U;>J#&L*6J&-Sy)rMnRHtLM;`9=bHinE_}Q7vrYos1<)Evi^i;l|td0}m zViAFpgT|RPMWX~ePZpHl3zt3!K*Kktfnu%mVa{KI4};Ts|J7<ka%By$uI@js(4^(; zbP#ZsPg&cgieIaIe|}Oom@wMGX*E0vCo3$PRhz_<%EdsI96G;8g>?Lif+OcqSff5o z^zC(fmA+++oO{p1>s}3PcSm}X1RP-<)XAe$uM|`6TkUfKdQsru|MSHZC53^CJuLLj z08jT5+$Rb(zUI`D=6FB&vBC^nYx%Gx$r!rR^Lm+2YCL6;dR7`PuN5CHH`I3y<t$Vl z$0W6@QN73rl{HhJH+gP9+soME$qj$>uS$048L+#S77FV2(NW@$HD@oaN(CmhAYCte zH#n>3Q6kmz+Di*;4@j!x%)sc0*nH4~<;y~bAEHXWt)n^~#sUH1YEdV2h|ARP1VPKC zkn++v*7iz&P4q!%NNu!WExW92vIzJB-(a`{0zGQ*Jzjr%5lr>{#`SSE!eT$-nBGI4 zGhxLgk@wDIV`vk?9Q`5jwd*c$HCf9R9>U+>inCP=B1Ea&hf1-t2dZOEN49)28`ju3 zoZy*q>(av}u3!ndOs!jbi?_VD+PN`CB&zujU8w#kHGoWE#CCz5zpeQ}NXjJYz|V2v z8OlX`<F_&6Qi{VmX40wNRo(Gs(0&w$g8M1LY2vBDE6MoPRW1HNSCd(I+gYS#az#N< zKRr<z__EF}zK%OSvR^fZw&dWkhxFw^-vvEsNQm+;+Q;m9{)rRy04YB$#uwJ68)Ebo zP=2)l9+rO&8fUWF^-f6@D#|GkEP2!-fm!ze#7x15&8dD1hch;nFJ9gz_h|mlc}0%e zvr;nV)<<VX&*rq(VX~foB~SE+1Fl0m95N_ABc4DQtazuHomfWGTzrR)sFg@eUDid{ zwc#Xzk-`V5?_o$EgKqt892K%(wf`k<DDkt&8aw9^8WG6dlS3q1o+g&565C_{<KLcU zZx|=v#hN>>GmT0IPu4~{>`j9^>K=HT>x?`I@xaW0!^hw?Ucnvm@Ydtir%R6kyP4#R zOT5uhohD&!N8mgEx5GvrA}_3@k+i>TZJvvnwM*I4Csa$jTQ}O2r&ZoLcyEo}R<g-Y zv=_0JT0NUTSHhpN<BWi6|MiH-RhhQuzS;SV>j}sUx|eYxkd%#ihBwe1y~Z~<-hVgd z+(o;s`|4X%<6_wXLg!7MkzxO-U4O4oPf_2}Y_f=vVxSb2rENJsvNb=oVyxmivbyk% zKD5-5meQLyWI8Ew<vlms%-m(A;=bGrRH_b-9cxU<5uC|tMS*`Ud@}|#?ol&VVu_C7 z*A66YUx5a?Mog@isf&$@xpRvKO<8rNDP-%d5p3=R7#wD{dvG<P&6l=3sHuU36aw{P zG&%l&^W+CMwfN7=c(leg*saay074iX+WvSHH?&zztFz<aCE2r7?=FMkI5hwFLh|K1 zP*$_~BJSZ#`6sP`drPmZ0|zVfhlrs+EreJIYGl(ncJMxCQB`2GUr%<h2NIcyj(D-I z@Ql%9p*g>A!LTT?R?-<Ma}O1DCCnyhC%pFygB*1an;2L<HS3{Yk(M65_xtCHQpTQ; zK6uaz=TL?l{Exczq(yBZO~{m)tM@6oJg1_()d1;KcOZsVP7zO8WbyI2(X)VtmB;ZR zCp$BKrS&$866%66Yq9zPxZlHxx7F5tbjcM%ziq=dHsK~N_QK?-1<@wz(JR89AyP62 z3Jp&#VVx_l<PQV47UL%CNg^3Z1J*my*o?DVu`eC+<3`mnIvVN*NmB&2n<;8?y7M;v z-9luYnCB}R_0+9chKf(VuWI~@N=ffN8Mu*rw;2+N)XrciSaZ7oia*>?$&ua2sxS#t zRf)7pZI|aI6+n@xg-7pl#&Ag!=*D>OU!=<(ch+F?n=17%IgDL8`GnY&_fW2wfldD; zvpW^hd1(%yJ0HN5!0@>d;Tg90qp{(iS@BOvFn?>wT;LpjN4cf<x|LGwdZge0?5xp9 IhLu<BpSU?IEdT%j literal 0 HcmV?d00001 -- GitLab From fc3522e5ed4748076f3d487d562e02ae0eff6bf5 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sat, 30 Nov 2024 14:45:33 +0100 Subject: [PATCH 032/283] style: style home --- screens/Home/HomeChild.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 67ae101..3718b10 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -34,7 +34,7 @@ export default function HomeChild({navigation}: Props) { </View> <View style={styles.buttonContainer}> <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonJoinPressed}/> - <MenuButton text={"PLAY A GAME"} handleButtonPressed={handleButtonGeneratePressed}/> + <MenuButton text={"PLAY A QUIZ"} handleButtonPressed={handleButtonGeneratePressed}/> <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonJoinPressed}/> </View> <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> @@ -64,8 +64,10 @@ const styles = StyleSheet.create({ marginBottom: '35%', }, buttonContainer: { + width: '100%', display: 'flex', flexDirection: 'column', - gap: '7%', + paddingLeft: '8%', + gap: '9%', } }); \ No newline at end of file -- GitLab From 78783a3e8963ae6d7d656b86c015fa41ac117e7d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sat, 30 Nov 2024 15:49:03 +0100 Subject: [PATCH 033/283] feat: SelectMode box --- components/PlayQuiz/PlayQuizMod.tsx | 9 ++++ components/PlayQuiz/SelectMode.tsx | 74 +++++++++++++++++++++++++++++ models/SelectModeType.ts | 1 + routes/StackNavigator.tsx | 2 + screens/Home/HomeChild.tsx | 2 +- screens/PlayQuiz/PlayQuiz.tsx | 31 ++++++++++++ screens/PlayQuiz/PlayQuizChild.tsx | 36 ++++++++++++++ 7 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 components/PlayQuiz/PlayQuizMod.tsx create mode 100644 components/PlayQuiz/SelectMode.tsx create mode 100644 models/SelectModeType.ts create mode 100644 screens/PlayQuiz/PlayQuiz.tsx create mode 100644 screens/PlayQuiz/PlayQuizChild.tsx diff --git a/components/PlayQuiz/PlayQuizMod.tsx b/components/PlayQuiz/PlayQuizMod.tsx new file mode 100644 index 0000000..803b117 --- /dev/null +++ b/components/PlayQuiz/PlayQuizMod.tsx @@ -0,0 +1,9 @@ +import {View, Text} from "react-native"; + +export default function PlayQuizMod() { + return ( + <View> + <Text>PlayQuizMod</Text> + </View> + ) +} \ No newline at end of file diff --git a/components/PlayQuiz/SelectMode.tsx b/components/PlayQuiz/SelectMode.tsx new file mode 100644 index 0000000..17a6535 --- /dev/null +++ b/components/PlayQuiz/SelectMode.tsx @@ -0,0 +1,74 @@ +import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import { SelectModeType } from "../../models/SelectModeType"; + +interface Props { + mode: SelectModeType; + setMode: (mode: SelectModeType) => void; +} + +export default function SelectMode({ mode, setMode }: Props) { + return ( + <View style={styles.container}> + <View style={styles.buttonContainer}> + <TouchableOpacity onPress={() => setMode("play")} style={{marginLeft: '10%'}}> + <Text style={[styles.text, mode === "play" && styles.activeText]}>Play a quiz</Text> + </TouchableOpacity> + <View style={[styles.bar, mode === "play" ? styles.activeBar : styles.inactiveBar]} /> + </View> + + <View style={styles.buttonContainer}> + <TouchableOpacity onPress={() => setMode("join")} style={{marginRight: '10%'}}> + <Text style={[styles.text, mode === "join" && styles.activeText]}>Join a quiz</Text> + </TouchableOpacity> + <View style={[styles.bar, mode === "join" ? styles.activeBar : styles.inactiveBar]} /> + </View> + + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + display: 'flex', + width: '100%', + height: '10%', + flexDirection: 'row', + backgroundColor: '#f3f3f3', + borderRadius: 10, + justifyContent: 'space-around', + alignItems: 'center', + // Ombre pour Android + elevation: 5, + // Ombre pour iOS + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, // Décalage de l'ombre en bas + shadowOpacity: 0.25, // Opacité de l'ombre + shadowRadius: 3.84, // Flou de l'ombre + }, + buttonContainer: { + display: 'flex', + flexDirection: 'column', + width: '40%', + alignItems: 'center', + gap: "5%", + }, + text: { + fontSize: 25, + color: '#bebebe', + }, + activeText: { + color: '#2ec7ff', + }, + bar: { + width: '100%', + height: 4, + marginTop: 4, + borderRadius: 10, + }, + activeBar: { + backgroundColor: '#2ec7ff', // Couleur pour le bouton actif + }, + inactiveBar: { + backgroundColor: '#bebebe', // Couleur grise pour les boutons inactifs + }, +}); diff --git a/models/SelectModeType.ts b/models/SelectModeType.ts new file mode 100644 index 0000000..b664180 --- /dev/null +++ b/models/SelectModeType.ts @@ -0,0 +1 @@ +export type SelectModeType = "play" | "join"; \ No newline at end of file diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index eeae3a4..0283a97 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -4,6 +4,7 @@ import Home from "../screens/Home/Home"; import GenerateQuiz from "../screens/GenerateQuiz/GenerateQuiz"; import EndQuiz from "../screens/EndQuiz/EndQuiz"; import PlayingQuiz from "../screens/PlayingQuiz/PlayingQuiz"; +import PlayQuiz from "../screens/PlayQuiz/PlayQuiz"; const Stack = createNativeStackNavigator(); @@ -14,6 +15,7 @@ export default function StackNavigator() { <Stack.Screen name="Home" component={Home} options={{ headerShown: false }}/> <Stack.Screen name="CreateQuiz" component={GenerateQuiz} options={{ headerShown: false }}/> <Stack.Screen name="PlayingQuiz" component={PlayingQuiz} options={{ headerShown: false }}/> + <Stack.Screen name="PlayQuiz" component={PlayQuiz} options={{ headerShown: false }}/> <Stack.Screen name="EndQuiz" component={EndQuiz} options={{ headerShown: false }}/> </Stack.Navigator> </NavigationContainer> diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 3718b10..a750275 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -24,7 +24,7 @@ export default function HomeChild({navigation}: Props) { } const handleButtonGeneratePressed = () => { - navigation.navigate("CreateQuiz"); + navigation.navigate("PlayQuiz"); } return ( diff --git a/screens/PlayQuiz/PlayQuiz.tsx b/screens/PlayQuiz/PlayQuiz.tsx new file mode 100644 index 0000000..25fe9f8 --- /dev/null +++ b/screens/PlayQuiz/PlayQuiz.tsx @@ -0,0 +1,31 @@ +import {NavigationProp} from "@react-navigation/native"; +import TemplateMenu from "../../templates/TemplateMenu"; +import {StyleSheet, View} from "react-native"; +import HomeChild from "../Home/HomeChild"; +import React from "react"; +import PlayQuizChild from "./PlayQuizChild"; + +interface Props{ + navigation: NavigationProp<any> +} +export default function PlayQuiz({navigation}: Props) { + + return ( + <View style={styles.containerGlobal}> + <TemplateMenu navigation={navigation} headerNavigation={true}> + <View> + <PlayQuizChild navigation={navigation}/> + </View> + </TemplateMenu> + </View> + ) +} + +const styles = StyleSheet.create({ + containerGlobal: { + display: 'flex', + width: '100%', + height: '100%', + flexDirection: 'column', + }, +}); \ No newline at end of file diff --git a/screens/PlayQuiz/PlayQuizChild.tsx b/screens/PlayQuiz/PlayQuizChild.tsx new file mode 100644 index 0000000..5227194 --- /dev/null +++ b/screens/PlayQuiz/PlayQuizChild.tsx @@ -0,0 +1,36 @@ +import {Text, View, StyleSheet} from "react-native"; +import {NavigationProp} from "@react-navigation/native"; +import PlayQuizMod from "../../components/PlayQuiz/PlayQuizMod"; +import SelectMode from "../../components/PlayQuiz/SelectMode"; +import {useState} from "react"; +import {SelectModeType} from "../../models/SelectModeType"; + +interface Props { + navigation: NavigationProp<any> +} + +export default function PlayQuizChild({navigation}: Props){ + const [mode, setMode] = useState<SelectModeType>("play"); + + return ( + <View style={styles.containerGlobal}> + <View style={styles.contentContainer}> + <SelectMode mode={mode} setMode={setMode}/> + </View> + </View> + ) +} +const styles = StyleSheet.create({ + containerGlobal: { + width: '100%', + height: '100%', + alignItems: 'center', + justifyContent: 'center', + }, + contentContainer: { + display: 'flex', + width: '90%', + height: '100%', + flexDirection: 'column', + }, +}); \ No newline at end of file -- GitLab From f6000310767ec1362a6a4863c2b41d5b107ec21d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sat, 30 Nov 2024 16:43:19 +0100 Subject: [PATCH 034/283] feat: SelectListBox --- components/PlayQuiz/SelectListBox.tsx | 78 +++++++++++++++++++++++++++ package-lock.json | 7 +++ package.json | 1 + screens/PlayQuiz/PlayQuizChild.tsx | 3 ++ 4 files changed, 89 insertions(+) create mode 100644 components/PlayQuiz/SelectListBox.tsx diff --git a/components/PlayQuiz/SelectListBox.tsx b/components/PlayQuiz/SelectListBox.tsx new file mode 100644 index 0000000..791be54 --- /dev/null +++ b/components/PlayQuiz/SelectListBox.tsx @@ -0,0 +1,78 @@ +import { View, StyleSheet, Text } from "react-native"; +import { SelectList } from "react-native-dropdown-select-list"; +import React from "react"; + +export default function SelectListBox() { + const [selected, setSelected] = React.useState(""); + + const data = [ + { key: "1", value: "Mobiles", disabled: true }, + { key: "2", value: "Appliances" }, + { key: "3", value: "Cameras" }, + { key: "4", value: "Computers", disabled: true }, + { key: "5", value: "Vegetables" }, + { key: "6", value: "Diary Products" }, + { key: "7", value: "Drinks" }, + ]; + + return ( + <View style={styles.container}> + <View style={styles.dropdownContainer}> + <Text style={styles.title}>Choose a Category</Text> {/* Ajout du titre */} + <SelectList + setSelected={(val: any) => setSelected(val)} + data={data} + save="value" + dropdownStyles={styles.dropdownStyles} // Applique un style personnalisé au menu déroulant + boxStyles={styles.boxStyles} // Applique un style personnalisé au champ de saisie + inputStyles={styles.inputStyles} + /> + </View> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + paddingTop: 50, + alignItems: "center", + }, + dropdownContainer: { + width: "90%", + }, + title: { + fontSize: 20, + fontWeight: "bold", // Met le texte en gras + marginBottom: 5, // Ajoute un espace entre le titre et la boîte déroulante + color: "#333", // Couleur du texte + textAlign: "left", // Aligne le texte à gauche + }, + dropdownStyles: { + position: "absolute", // Rend le dropdown flottant + width: "100%", // Ajustez la largeur du dropdown + top: 40, // Ajustez la position verticale + zIndex: 2, // Assure que le menu apparaît au-dessus + backgroundColor: "white", // Ajoute une couleur de fond + borderColor: "black", // Optionnel : bordure de couleur + borderWidth: 1, // Optionnel : épaisseur de la bordure + borderRadius: 8, // Optionnel : coins arrondis + }, + boxStyles: { + backgroundColor: "white", + borderColor: "#bebebe", + borderWidth: 2, + borderRadius: 8, + height: 70, // Hauteur augmentée + alignItems: "center", + elevation: 5, + // Ombre pour iOS + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, // Décalage de l'ombre en bas + shadowOpacity: 0.25, // Opacité de l'ombre + shadowRadius: 3.84, // Flou de l'ombre + }, + inputStyles: { + fontSize: 18, // Taille du texte augmentée + color: "black", // Couleur du texte + }, +}); diff --git a/package-lock.json b/package-lock.json index 393cf26..2160b04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "react": "^18.3.1", "react-i18next": "^15.1.1", "react-native": "^0.76.2", + "react-native-dropdown-select-list": "^2.0.5", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0" }, @@ -9245,6 +9246,12 @@ } } }, + "node_modules/react-native-dropdown-select-list": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/react-native-dropdown-select-list/-/react-native-dropdown-select-list-2.0.5.tgz", + "integrity": "sha512-TepbcagQVUMB6nLuIlVU2ghRpQHAECOeZWe8K04ymW6NqbKbxuczZSDFfdCiABiiQ2dFD+8Dz65y4K7/uUEqGg==", + "license": "MIT" + }, "node_modules/react-native-safe-area-context": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.12.0.tgz", diff --git a/package.json b/package.json index 1bd89c5..a1e4bf5 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "react": "^18.3.1", "react-i18next": "^15.1.1", "react-native": "^0.76.2", + "react-native-dropdown-select-list": "^2.0.5", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0" }, diff --git a/screens/PlayQuiz/PlayQuizChild.tsx b/screens/PlayQuiz/PlayQuizChild.tsx index 5227194..e85a6a0 100644 --- a/screens/PlayQuiz/PlayQuizChild.tsx +++ b/screens/PlayQuiz/PlayQuizChild.tsx @@ -4,6 +4,7 @@ import PlayQuizMod from "../../components/PlayQuiz/PlayQuizMod"; import SelectMode from "../../components/PlayQuiz/SelectMode"; import {useState} from "react"; import {SelectModeType} from "../../models/SelectModeType"; +import SelectListBox from "../../components/PlayQuiz/SelectListBox"; interface Props { navigation: NavigationProp<any> @@ -16,6 +17,8 @@ export default function PlayQuizChild({navigation}: Props){ <View style={styles.containerGlobal}> <View style={styles.contentContainer}> <SelectMode mode={mode} setMode={setMode}/> + <SelectListBox/> + <SelectListBox/> </View> </View> ) -- GitLab From 15aa018067c237f741aa9ae8a3ff1263a9704822 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 1 Dec 2024 15:16:37 +0100 Subject: [PATCH 035/283] feat: input + play button --- components/PlayQuiz/InputPlayQuiz.tsx | 48 +++++++++++++++++++++++++++ components/PlayQuiz/PlayButton.tsx | 32 ++++++++++++++++++ components/PlayQuiz/SelectListBox.tsx | 3 +- screens/PlayQuiz/PlayQuizChild.tsx | 23 ++++++++----- 4 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 components/PlayQuiz/InputPlayQuiz.tsx create mode 100644 components/PlayQuiz/PlayButton.tsx diff --git a/components/PlayQuiz/InputPlayQuiz.tsx b/components/PlayQuiz/InputPlayQuiz.tsx new file mode 100644 index 0000000..a95071f --- /dev/null +++ b/components/PlayQuiz/InputPlayQuiz.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { TextInput, View, StyleSheet, Text } from "react-native"; + +export default function InputPlayQuiz() { + return ( + <View style={styles.container}> + <Text style={styles.title}>Choose a Category</Text> {/* Ajout du titre */} + <TextInput + style={styles.input} + placeholderTextColor="#bebebe" + keyboardType="numeric" + onChangeText={(text) => console.log(text)} + /> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + display: "flex", + flexDirection: "column", + alignItems: "flex-start", // Aligne les éléments à gauche + width: "100%", // S'assure que le conteneur prend toute la largeur + }, + input: { + width: "100%", + height: 70, // Hauteur augmentée comme dans boxStyles + backgroundColor: "white", // Fond blanc comme dans boxStyles + borderColor: "#2ec7ff", + borderWidth: 3, + borderRadius: 8, + paddingLeft: 10, // Espacement à gauche pour le texte + elevation: 5, // Ombre pour Android + // Ombre pour iOS + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, // Décalage de l'ombre en bas + shadowOpacity: 0.25, // Opacité de l'ombre + shadowRadius: 3.84, // Flou de l'ombre + }, + title: { + fontSize: 20, + fontWeight: "bold", // Met le texte en gras + marginBottom: 5, // Ajoute un espace entre le titre et la boîte déroulante + color: "#333", // Couleur du texte + textAlign: "left", // Aligne le texte à gauche + width: "100%", // S'assure que le texte utilise toute la largeur disponible + }, +}); diff --git a/components/PlayQuiz/PlayButton.tsx b/components/PlayQuiz/PlayButton.tsx new file mode 100644 index 0000000..6411702 --- /dev/null +++ b/components/PlayQuiz/PlayButton.tsx @@ -0,0 +1,32 @@ +import {View, Text, StyleSheet, TouchableOpacity} from "react-native"; +import {NavigationProp} from "@react-navigation/native"; + +interface Props { + onPress: () => void + text: string +} +export default function PlayButton({onPress, text} : Props) { + return ( + <TouchableOpacity style={styles.container} onPress={onPress}> + <Text style={styles.text}>{text}</Text> + </TouchableOpacity> + ); +} + +const styles = StyleSheet.create({ + + container: { + width: '100%', + height: '10%', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#2b73fe', + borderRadius: 10, + }, + text: { + color: 'white', + fontSize: 28, + fontWeight: 'bold', + } + +}); \ No newline at end of file diff --git a/components/PlayQuiz/SelectListBox.tsx b/components/PlayQuiz/SelectListBox.tsx index 791be54..a3e9bfe 100644 --- a/components/PlayQuiz/SelectListBox.tsx +++ b/components/PlayQuiz/SelectListBox.tsx @@ -34,11 +34,10 @@ export default function SelectListBox() { const styles = StyleSheet.create({ container: { - paddingTop: 50, alignItems: "center", }, dropdownContainer: { - width: "90%", + width: "100%", }, title: { fontSize: 20, diff --git a/screens/PlayQuiz/PlayQuizChild.tsx b/screens/PlayQuiz/PlayQuizChild.tsx index e85a6a0..ef87046 100644 --- a/screens/PlayQuiz/PlayQuizChild.tsx +++ b/screens/PlayQuiz/PlayQuizChild.tsx @@ -1,10 +1,11 @@ -import {Text, View, StyleSheet} from "react-native"; +import {Text, View, StyleSheet, TouchableWithoutFeedback, Keyboard} from "react-native"; import {NavigationProp} from "@react-navigation/native"; -import PlayQuizMod from "../../components/PlayQuiz/PlayQuizMod"; import SelectMode from "../../components/PlayQuiz/SelectMode"; import {useState} from "react"; import {SelectModeType} from "../../models/SelectModeType"; import SelectListBox from "../../components/PlayQuiz/SelectListBox"; +import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; +import PlayButton from "../../components/PlayQuiz/PlayButton"; interface Props { navigation: NavigationProp<any> @@ -14,13 +15,17 @@ export default function PlayQuizChild({navigation}: Props){ const [mode, setMode] = useState<SelectModeType>("play"); return ( - <View style={styles.containerGlobal}> - <View style={styles.contentContainer}> - <SelectMode mode={mode} setMode={setMode}/> - <SelectListBox/> - <SelectListBox/> + <TouchableWithoutFeedback onPress={Keyboard.dismiss}> + <View style={styles.containerGlobal}> + <View style={styles.contentContainer}> + <SelectMode mode={mode} setMode={setMode}/> + <SelectListBox/> + <SelectListBox/> + <InputPlayQuiz /> + <PlayButton onPress={() => {}} text="PLAY"/> + </View> </View> - </View> + </TouchableWithoutFeedback> ) } const styles = StyleSheet.create({ @@ -32,7 +37,7 @@ const styles = StyleSheet.create({ }, contentContainer: { display: 'flex', - width: '90%', + width: '95%', height: '100%', flexDirection: 'column', }, -- GitLab From 3c5a07dd8424e339991c0ae240bfdc5da049fd3f Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 1 Dec 2024 17:47:12 +0100 Subject: [PATCH 036/283] feat: PlayQuizGenerateQuiz --- components/PlayQuiz/InputPlayQuiz.tsx | 8 ++- components/PlayQuiz/PlayButton.tsx | 2 +- components/PlayQuiz/SelectListBox.tsx | 21 +++---- screens/PlayQuiz/PlayQuizChild.tsx | 6 +- screens/PlayQuiz/PlayQuizGenerateQuiz.tsx | 77 +++++++++++++++++++++++ 5 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 screens/PlayQuiz/PlayQuizGenerateQuiz.tsx diff --git a/components/PlayQuiz/InputPlayQuiz.tsx b/components/PlayQuiz/InputPlayQuiz.tsx index a95071f..318524a 100644 --- a/components/PlayQuiz/InputPlayQuiz.tsx +++ b/components/PlayQuiz/InputPlayQuiz.tsx @@ -1,10 +1,14 @@ import React from "react"; import { TextInput, View, StyleSheet, Text } from "react-native"; -export default function InputPlayQuiz() { +interface Props { + title: string, + setText: (text: string) => void, +} +export default function InputPlayQuiz({title, setText}:Props) { return ( <View style={styles.container}> - <Text style={styles.title}>Choose a Category</Text> {/* Ajout du titre */} + <Text style={styles.title}>{title}</Text> {/* Ajout du titre */} <TextInput style={styles.input} placeholderTextColor="#bebebe" diff --git a/components/PlayQuiz/PlayButton.tsx b/components/PlayQuiz/PlayButton.tsx index 6411702..2a68248 100644 --- a/components/PlayQuiz/PlayButton.tsx +++ b/components/PlayQuiz/PlayButton.tsx @@ -17,7 +17,7 @@ const styles = StyleSheet.create({ container: { width: '100%', - height: '10%', + height: 70, alignItems: 'center', justifyContent: 'center', backgroundColor: '#2b73fe', diff --git a/components/PlayQuiz/SelectListBox.tsx b/components/PlayQuiz/SelectListBox.tsx index a3e9bfe..5ce90d7 100644 --- a/components/PlayQuiz/SelectListBox.tsx +++ b/components/PlayQuiz/SelectListBox.tsx @@ -2,23 +2,19 @@ import { View, StyleSheet, Text } from "react-native"; import { SelectList } from "react-native-dropdown-select-list"; import React from "react"; -export default function SelectListBox() { - const [selected, setSelected] = React.useState(""); +interface Props { + data: any, + setSelected: (val: string) => void, + title: string, - const data = [ - { key: "1", value: "Mobiles", disabled: true }, - { key: "2", value: "Appliances" }, - { key: "3", value: "Cameras" }, - { key: "4", value: "Computers", disabled: true }, - { key: "5", value: "Vegetables" }, - { key: "6", value: "Diary Products" }, - { key: "7", value: "Drinks" }, - ]; +} + +export default function SelectListBox({data, setSelected, title} : Props) { return ( <View style={styles.container}> <View style={styles.dropdownContainer}> - <Text style={styles.title}>Choose a Category</Text> {/* Ajout du titre */} + <Text style={styles.title}>{title}</Text> {/* Ajout du titre */} <SelectList setSelected={(val: any) => setSelected(val)} data={data} @@ -35,6 +31,7 @@ export default function SelectListBox() { const styles = StyleSheet.create({ container: { alignItems: "center", + width: '100%', }, dropdownContainer: { width: "100%", diff --git a/screens/PlayQuiz/PlayQuizChild.tsx b/screens/PlayQuiz/PlayQuizChild.tsx index ef87046..f5151bd 100644 --- a/screens/PlayQuiz/PlayQuizChild.tsx +++ b/screens/PlayQuiz/PlayQuizChild.tsx @@ -6,6 +6,7 @@ import {SelectModeType} from "../../models/SelectModeType"; import SelectListBox from "../../components/PlayQuiz/SelectListBox"; import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; import PlayButton from "../../components/PlayQuiz/PlayButton"; +import PlayQuizGenerateQuiz from "./PlayQuizGenerateQuiz"; interface Props { navigation: NavigationProp<any> @@ -19,10 +20,7 @@ export default function PlayQuizChild({navigation}: Props){ <View style={styles.containerGlobal}> <View style={styles.contentContainer}> <SelectMode mode={mode} setMode={setMode}/> - <SelectListBox/> - <SelectListBox/> - <InputPlayQuiz /> - <PlayButton onPress={() => {}} text="PLAY"/> + <PlayQuizGenerateQuiz /> </View> </View> </TouchableWithoutFeedback> diff --git a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx new file mode 100644 index 0000000..438202c --- /dev/null +++ b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx @@ -0,0 +1,77 @@ +import { Text,View, StyleSheet } from "react-native"; +import SelectListBox from "../../components/PlayQuiz/SelectListBox"; +import React from "react"; +import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; +import PlayButton from "../../components/PlayQuiz/PlayButton"; + +const difficulty = [ + { key: "easy", value: "Easy" }, + { key: "medium", value: "Medium" }, + { key: "hard", value: "Hard" }, +]; + +const categories = [ + { key: "9", value: "General Knowledge"}, + { key: "10", value: "Entertainment: Books" }, + { key: "11", value: "Entertainment: Film" }, + { key: "12", value: "Entertainment: Music"}, + { key: "13", value: "Entertainment: Musicals & Theatres" }, + { key: "14", value: "Entertainment: Television" }, + { key: "15", value: "Entertainment: Video Games" }, + { key: "16", value: "Entertainment: Board Games" }, + { key: "17", value: "Science & Nature" }, + { key: "18", value: "Science: Computers" }, + { key: "19", value: "Science: Mathematics" }, + { key: "20", value: "Mythology" }, + { key: "21", value: "Sports" }, + { key: "22", value: "Geography" }, +]; + +export default function PlayQuizGenerateQuiz() { + const [difficutlyChoose, setDifficutlyChoose] = React.useState(""); + const [categoryChoose, setCategoryChoose] = React.useState(""); + + const handlePlayButtonPress = () => { + console.log("Play button pressed"); + }; + + return ( + <View style={styles.container}> + <View style={styles.contentContainer}> + <View style={styles.fieldContainer}> + <SelectListBox title="CHOOSE A DIFFICULTY" data={difficulty} setSelected={setDifficutlyChoose} /> + <SelectListBox title="CHOOSE A CATEGORY" data={categories} setSelected={setCategoryChoose} /> + <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={() => {}} /> + </View> + <View style={styles.buttonPlayContainer}> + <PlayButton onPress={handlePlayButtonPress} text={"PLAY"} /> + </View> + </View> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + display: "flex", + width: "100%", + height: "75%", + justifyContent: "space-between", + }, + contentContainer: { + flex: 1, // Prend toute la hauteur disponible + flexDirection: "column", + justifyContent: "space-between", + }, + fieldContainer: { + width: "100%", + flexDirection: "column", + justifyContent: "space-between", + marginTop: 20, + gap: 20, + }, + buttonPlayContainer: { + alignItems: "center", + paddingVertical: 20, + }, +}); -- GitLab From a739f04f20bd181e6406270716bb6260d6f8659d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Mon, 2 Dec 2024 21:36:31 +0100 Subject: [PATCH 037/283] feat: PlayQuizJoinQuiz --- components/PlayQuiz/AboutAQuiz.tsx | 55 +++++++++++++++++++++++++++ components/PlayQuiz/InputPlayQuiz.tsx | 14 ++++--- screens/PlayQuiz/PlayQuizChild.tsx | 9 ++++- screens/PlayQuiz/PlayQuizJoinQuiz.tsx | 54 ++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 components/PlayQuiz/AboutAQuiz.tsx create mode 100644 screens/PlayQuiz/PlayQuizJoinQuiz.tsx diff --git a/components/PlayQuiz/AboutAQuiz.tsx b/components/PlayQuiz/AboutAQuiz.tsx new file mode 100644 index 0000000..1e2249a --- /dev/null +++ b/components/PlayQuiz/AboutAQuiz.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import { View, Text, StyleSheet } from "react-native"; +import QuizModel from "../../models/QuizModel"; + +interface Props { + quiz?: QuizModel; +} +export default function AboutAQuiz({quiz} : Props) { + return ( + <View style={styles.container}> + <Text style={styles.title}>About this quiz</Text> + <View style={styles.separator} /> + <View style={styles.infoContainer}> + <Text style={styles.text}>Category : {quiz ? quiz.category : "?"}</Text> + <Text style={styles.text}>Question : {quiz ? quiz.nbActualQuestion : "?"}/{quiz ? quiz.nbQuestions : "?"}</Text> + <Text style={styles.text}>Score : {quiz ? quiz.score : "?"}</Text> + </View> + + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + backgroundColor: "white", // Fond blanc + borderRadius: 10, // Coins arrondis + padding: 20, // Espacement intérieur + shadowColor: "#000", // Couleur de l'ombre + shadowOffset: { width: 0, height: 4 }, // Décalage de l'ombre + shadowOpacity: 0.25, // Opacité de l'ombre + shadowRadius: 3.84, // Flou de l'ombre + elevation: 5, // Ombre pour Android + marginVertical: 10, // Espacement vertical + }, + title: { + fontSize: 18, + fontWeight: "bold", + marginBottom: 10, // Espacement entre le titre et le séparateur + }, + separator: { + height: 3, // Épaisseur de la ligne + backgroundColor: "#ddd", // Couleur de la ligne + marginBottom: 15, // Espacement entre la ligne et le contenu + }, + text: { + fontSize: 16, + fontWeight: "bold", + marginBottom: 10, // Espacement entre la catégorie et les informations + }, + infoContainer: { + flexDirection: "column", // Aligne les éléments horizontalement + justifyContent: "space-between", // Aligne les éléments verticalement + gap: 10, // Espacement entre les informations + }, +}); diff --git a/components/PlayQuiz/InputPlayQuiz.tsx b/components/PlayQuiz/InputPlayQuiz.tsx index 318524a..0f6d7e0 100644 --- a/components/PlayQuiz/InputPlayQuiz.tsx +++ b/components/PlayQuiz/InputPlayQuiz.tsx @@ -2,18 +2,22 @@ import React from "react"; import { TextInput, View, StyleSheet, Text } from "react-native"; interface Props { - title: string, - setText: (text: string) => void, + title?: string; + setText: (text: string) => void; + noTitle?: boolean; + placeholder?: string; } -export default function InputPlayQuiz({title, setText}:Props) { + +export default function InputPlayQuiz({ title, setText, noTitle, placeholder }: Props) { return ( <View style={styles.container}> - <Text style={styles.title}>{title}</Text> {/* Ajout du titre */} + {!noTitle && title && <Text style={styles.title}>{title}</Text>} <TextInput style={styles.input} + placeholder={placeholder || ""} placeholderTextColor="#bebebe" keyboardType="numeric" - onChangeText={(text) => console.log(text)} + onChangeText={(text) => setText(text)} /> </View> ); diff --git a/screens/PlayQuiz/PlayQuizChild.tsx b/screens/PlayQuiz/PlayQuizChild.tsx index f5151bd..ffd4f08 100644 --- a/screens/PlayQuiz/PlayQuizChild.tsx +++ b/screens/PlayQuiz/PlayQuizChild.tsx @@ -1,12 +1,13 @@ import {Text, View, StyleSheet, TouchableWithoutFeedback, Keyboard} from "react-native"; import {NavigationProp} from "@react-navigation/native"; import SelectMode from "../../components/PlayQuiz/SelectMode"; -import {useState} from "react"; +import React, {useState} from "react"; import {SelectModeType} from "../../models/SelectModeType"; import SelectListBox from "../../components/PlayQuiz/SelectListBox"; import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; import PlayButton from "../../components/PlayQuiz/PlayButton"; import PlayQuizGenerateQuiz from "./PlayQuizGenerateQuiz"; +import PlayQuizJoinQuiz from "./PlayQuizJoinQuiz"; interface Props { navigation: NavigationProp<any> @@ -20,7 +21,11 @@ export default function PlayQuizChild({navigation}: Props){ <View style={styles.containerGlobal}> <View style={styles.contentContainer}> <SelectMode mode={mode} setMode={setMode}/> - <PlayQuizGenerateQuiz /> + {mode === "play" ? ( + <PlayQuizGenerateQuiz /> + ) : ( + <PlayQuizJoinQuiz /> + )} </View> </View> </TouchableWithoutFeedback> diff --git a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx new file mode 100644 index 0000000..b5117f2 --- /dev/null +++ b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx @@ -0,0 +1,54 @@ +import {StyleSheet, View} from "react-native"; +import AboutAQuiz from "../../components/PlayQuiz/AboutAQuiz"; +import PlayButton from "../../components/PlayQuiz/PlayButton"; +import React from "react"; +import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; +import QuizModel from "../../models/QuizModel"; + +export default function PlayQuizJoinQuiz() { + const [codeQuiz, setCodeQuiz] = React.useState(""); + const [quiz, setQuiz] = React.useState<QuizModel>(); + const handlePlayButtonPress = () => { + console.log("Play button pressed"); + }; + + return ( + <View style={styles.container}> + <View style={styles.contentContainer}> + <View style={styles.fieldContainer}> + <InputPlayQuiz placeholder={"Enter quiz ID"} setText={setCodeQuiz} noTitle={true}/> + <AboutAQuiz /> + </View> + <View style={styles.buttonPlayContainer}> + <PlayButton onPress={handlePlayButtonPress} text={"PLAY"} /> + </View> + </View> + </View> + ); +} + + +const styles = StyleSheet.create({ + container: { + display: "flex", + width: "100%", + height: "75%", + justifyContent: "space-between", + }, + contentContainer: { + flex: 1, // Prend toute la hauteur disponible + flexDirection: "column", + justifyContent: "space-between", + }, + fieldContainer: { + width: "100%", + flexDirection: "column", + justifyContent: "space-between", + marginTop: 20, + gap: 20, + }, + buttonPlayContainer: { + alignItems: "center", + paddingVertical: 20, + }, +}); \ No newline at end of file -- GitLab From e1bff228ed2a6b8b810c7187876ee69f6339afb7 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Mon, 2 Dec 2024 22:24:36 +0100 Subject: [PATCH 038/283] feat: barre de navigation avec icon --- package-lock.json | 126 +++++++++++++++++++++++++++++++++++++- package.json | 4 +- routes/StackNavigator.tsx | 4 +- routes/TabNavigation.tsx | 40 ++++++++++++ 4 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 routes/TabNavigation.tsx diff --git a/package-lock.json b/package-lock.json index 393cf26..ac741d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@react-native-picker/picker": "^2.9.0", + "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", "expo": "^52.0.8", @@ -20,7 +21,8 @@ "react-i18next": "^15.1.1", "react-native": "^0.76.2", "react-native-safe-area-context": "^4.12.0", - "react-native-screens": "~4.1.0" + "react-native-screens": "~4.1.0", + "react-native-vector-icons": "^10.2.0" }, "devDependencies": { "@babel/core": "^7.20.0", @@ -3836,6 +3838,24 @@ } } }, + "node_modules/@react-navigation/bottom-tabs": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-6.6.1.tgz", + "integrity": "sha512-9oD4cypEBjPuaMiu9tevWGiQ4w/d6l3HNhcJ1IjXZ24xvYDSs0mqjUcdt8SWUolCvRrYc/DmNBLlT83bk0bHTw==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^1.3.31", + "color": "^4.2.3", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "@react-navigation/native": "^6.0.0", + "react": "*", + "react-native": "*", + "react-native-safe-area-context": ">= 3.0.0", + "react-native-screens": ">= 3.0.0" + } + }, "node_modules/@react-navigation/core": { "version": "6.4.17", "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.17.tgz", @@ -5035,6 +5055,19 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5053,6 +5086,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -9269,6 +9312,72 @@ "react-native": "*" } }, + "node_modules/react-native-vector-icons": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.2.0.tgz", + "integrity": "sha512-n5HGcxUuVaTf9QJPs/W22xQpC2Z9u0nb0KgLPnVltP8vdUvOp6+R26gF55kilP/fV4eL4vsAHUqUjewppJMBOQ==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2", + "yargs": "^16.1.1" + }, + "bin": { + "fa-upgrade.sh": "bin/fa-upgrade.sh", + "fa5-upgrade": "bin/fa5-upgrade.sh", + "fa6-upgrade": "bin/fa6-upgrade.sh", + "generate-icon": "bin/generate-icon.js" + } + }, + "node_modules/react-native-vector-icons/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/react-native-vector-icons/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/react-native/node_modules/babel-plugin-syntax-hermes-parser": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.23.1.tgz", @@ -9944,6 +10053,21 @@ "node": ">= 5.10.0" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", diff --git a/package.json b/package.json index 1bd89c5..d6ad328 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@react-native-picker/picker": "^2.9.0", + "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", "expo": "^52.0.8", @@ -21,7 +22,8 @@ "react-i18next": "^15.1.1", "react-native": "^0.76.2", "react-native-safe-area-context": "^4.12.0", - "react-native-screens": "~4.1.0" + "react-native-screens": "~4.1.0", + "react-native-vector-icons": "^10.2.0" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index eeae3a4..799e70c 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -4,13 +4,15 @@ import Home from "../screens/Home/Home"; import GenerateQuiz from "../screens/GenerateQuiz/GenerateQuiz"; import EndQuiz from "../screens/EndQuiz/EndQuiz"; import PlayingQuiz from "../screens/PlayingQuiz/PlayingQuiz"; +import TabNavigator from "./TabNavigation"; const Stack = createNativeStackNavigator(); export default function StackNavigator() { return ( <NavigationContainer> - <Stack.Navigator initialRouteName="Home"> + <Stack.Navigator initialRouteName="TabNavigator"> + <Stack.Screen name="TabNavigator" component={TabNavigator} options={{ headerShown: false }}/> <Stack.Screen name="Home" component={Home} options={{ headerShown: false }}/> <Stack.Screen name="CreateQuiz" component={GenerateQuiz} options={{ headerShown: false }}/> <Stack.Screen name="PlayingQuiz" component={PlayingQuiz} options={{ headerShown: false }}/> diff --git a/routes/TabNavigation.tsx b/routes/TabNavigation.tsx new file mode 100644 index 0000000..719a7d8 --- /dev/null +++ b/routes/TabNavigation.tsx @@ -0,0 +1,40 @@ +import * as React from "react"; +import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; +import Home from "../screens/Home/Home"; +import Icon from "react-native-vector-icons/MaterialIcons"; + +const Tab = createBottomTabNavigator(); + +export default function TabNavigator() { + return ( + <Tab.Navigator + initialRouteName="Home" + screenOptions={({ route }) => ({ + headerShown: false, + tabBarActiveTintColor: "#383b41", + tabBarInactiveTintColor: "#c1c7d0", + tabBarShowLabel: false, + tabBarStyle: { + height: 100, // Augmenter la hauteur de la barre pour plus d'espace + backgroundColor: "#f4f5f7", + }, + tabBarLabelStyle: { + fontSize: 16, // Tu peux aussi ajuster la taille du texte ici + }, + tabBarIcon: ({ color, size }) => { + // Définir des icônes pour chaque route + let iconName; + if (route.name === "Home") { + iconName = "home"; // Icône pour Home + } + return <Icon name={iconName} size={64} color={color} />; + }, + })} + > + <Tab.Screen + name="Home" + component={Home} + /> + </Tab.Navigator> + ); +} -- GitLab From 5d9b7985757d2c47b988a5244516c1c8bf8776f2 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 3 Dec 2024 18:38:54 +0100 Subject: [PATCH 039/283] feat: quiz list --- components/lists/QuizList.tsx | 98 +++++++++++++++++++++++++++++++++ routes/TabNavigation.tsx | 7 +++ screens/MyQuizzes/MyQuizzes.tsx | 49 +++++++++++++++++ templates/TemplateQuizList.tsx | 16 ++++++ 4 files changed, 170 insertions(+) create mode 100644 components/lists/QuizList.tsx create mode 100644 screens/MyQuizzes/MyQuizzes.tsx create mode 100644 templates/TemplateQuizList.tsx diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx new file mode 100644 index 0000000..e5bc4c4 --- /dev/null +++ b/components/lists/QuizList.tsx @@ -0,0 +1,98 @@ +import React from "react"; +import { FlatList, View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import QuizModel from "../../models/QuizModel"; + +const quizzes: QuizModel[] = [ + { + code: "QUIZ123", + category: "General Knowledge", + questions: [], + nbQuestions: 3, + nbActualQuestion: 1, + score: 0, + }, + { + code: "QUIZ456", + category: "Science", + questions: [], + nbQuestions: 5, + nbActualQuestion: 1, + score: 0, + }, + { + code: "QUIZ789", + category: "Entertainment", + questions: [], + nbQuestions: 10, + nbActualQuestion: 1, + score: 0, + }, +]; + +export default function QuizList() { + const renderQuizItem = ({ item }: { item: QuizModel }) => ( + <TouchableOpacity style={styles.quizItem}> + <Text style={styles.quizTitle}>{item.code}</Text> {/* Titre du quiz */} + <Text style={styles.quizDetails}> + {item.nbQuestions} QUESTIONS | {item.category.toUpperCase()} + </Text> + </TouchableOpacity> + ); + + return ( + <View style={styles.container}> + <FlatList + data={quizzes} + renderItem={renderQuizItem} + keyExtractor={(item) => item.code} + showsVerticalScrollIndicator={true} + contentContainerStyle={styles.listContent} + /> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "white", + padding: 10, + borderRadius: 10, + borderColor: "#fafafa", + borderWidth: 3, + elevation: 5, // Ombre pour Android + shadowColor: "#000", // Ombre pour iOS + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + width: "100%", // Assurez-vous que le conteneur occupe toute la largeur + }, + listContent: { + paddingVertical: 10, + }, + quizItem: { + backgroundColor: "#f3f3f3", + padding: 15, + borderRadius: 10, + marginVertical: 5, + elevation: 3, // Ombre pour Android + shadowColor: "#000", // Ombre pour iOS + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 2, + width: "100%", + alignSelf: "center", // Centre les éléments dans leur conteneur + }, + quizTitle: { + fontSize: 22, + fontWeight: "bold", + color: "#000", + marginBottom: 5, + textAlign: "center", + }, + quizDetails: { + fontSize: 18, + color: "#555", + textAlign: "center", + }, +}); diff --git a/routes/TabNavigation.tsx b/routes/TabNavigation.tsx index 719a7d8..ea8a005 100644 --- a/routes/TabNavigation.tsx +++ b/routes/TabNavigation.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import Home from "../screens/Home/Home"; import Icon from "react-native-vector-icons/MaterialIcons"; +import MyQuizzes from "../screens/MyQuizzes/MyQuizzes"; const Tab = createBottomTabNavigator(); @@ -26,6 +27,8 @@ export default function TabNavigator() { let iconName; if (route.name === "Home") { iconName = "home"; // Icône pour Home + } else if (route.name === "MyQuizzes") { + iconName = "person"; // Icône pour Stats } return <Icon name={iconName} size={64} color={color} />; }, @@ -35,6 +38,10 @@ export default function TabNavigator() { name="Home" component={Home} /> + <Tab.Screen + name="MyQuizzes" + component={MyQuizzes} + /> </Tab.Navigator> ); } diff --git a/screens/MyQuizzes/MyQuizzes.tsx b/screens/MyQuizzes/MyQuizzes.tsx new file mode 100644 index 0000000..291acc2 --- /dev/null +++ b/screens/MyQuizzes/MyQuizzes.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import { View, Text, StyleSheet } from "react-native"; +import TemplateQuizList from "../../templates/TemplateQuizList"; +import { NavigationProp } from "@react-navigation/native"; +import QuizList from "../../components/lists/QuizList"; +import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; + +interface Props { + navigation: NavigationProp<any>; +} + +export default function MyQuizzes({ navigation }: Props) { + return ( + <TemplateQuizList navigation={navigation}> + <View style={styles.globalContainer}> + <Text style={styles.title}>COMMUNITY</Text> + <View style={styles.contentContainer}> + <InputPlayQuiz setText={(text) => console.log(text)} noTitle={true} /> + <QuizList /> + </View> + </View> + </TemplateQuizList> + ); +} + +const styles = StyleSheet.create({ + globalContainer: { + display: "flex", + height: "100%", + width: "100%", + paddingHorizontal: "3%", // Ajoute une marge de 5% à gauche et à droite + alignItems: "center", // Centre tous les enfants horizontalement + }, + title: { + fontSize: 40, + fontWeight: "bold", + color: "#00b3f4", + marginBottom: 10, + alignSelf: "center", // Centre uniquement le titre + }, + contentContainer: { + display: "flex", + flexDirection: "column", + width: "100%", // Utilise tout l'espace restant à l'intérieur de `globalContainer` + height: "80%", + alignItems: "center", // Centre tous les enfants horizontalement + gap: "2%", + }, +}); diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx new file mode 100644 index 0000000..5125d01 --- /dev/null +++ b/templates/TemplateQuizList.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import {View, Text} from "react-native"; +import TemplateMenu from "./TemplateMenu"; +import {NavigationProp} from "@react-navigation/native"; + +interface Props { + navigation: NavigationProp<any> + children: React.ReactNode +} +export default function TemplateQuizList({navigation, children}: Props) { + return ( + <TemplateMenu navigation={navigation} headerNavigation={true}> + {children} + </TemplateMenu> + ); +} \ No newline at end of file -- GitLab From 255b9ca6ae20c52364ea146eecb92f568053ee47 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 3 Dec 2024 18:42:50 +0100 Subject: [PATCH 040/283] feat: setup fin --- routes/TabNavigation.tsx | 5 +--- screens/MyQuizzes/MyQuizzes.tsx | 49 --------------------------------- templates/TemplateQuizList.tsx | 46 +++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 58 deletions(-) delete mode 100644 screens/MyQuizzes/MyQuizzes.tsx diff --git a/routes/TabNavigation.tsx b/routes/TabNavigation.tsx index ea8a005..7cfb72c 100644 --- a/routes/TabNavigation.tsx +++ b/routes/TabNavigation.tsx @@ -38,10 +38,7 @@ export default function TabNavigator() { name="Home" component={Home} /> - <Tab.Screen - name="MyQuizzes" - component={MyQuizzes} - /> + </Tab.Navigator> ); } diff --git a/screens/MyQuizzes/MyQuizzes.tsx b/screens/MyQuizzes/MyQuizzes.tsx deleted file mode 100644 index 291acc2..0000000 --- a/screens/MyQuizzes/MyQuizzes.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from "react"; -import { View, Text, StyleSheet } from "react-native"; -import TemplateQuizList from "../../templates/TemplateQuizList"; -import { NavigationProp } from "@react-navigation/native"; -import QuizList from "../../components/lists/QuizList"; -import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; - -interface Props { - navigation: NavigationProp<any>; -} - -export default function MyQuizzes({ navigation }: Props) { - return ( - <TemplateQuizList navigation={navigation}> - <View style={styles.globalContainer}> - <Text style={styles.title}>COMMUNITY</Text> - <View style={styles.contentContainer}> - <InputPlayQuiz setText={(text) => console.log(text)} noTitle={true} /> - <QuizList /> - </View> - </View> - </TemplateQuizList> - ); -} - -const styles = StyleSheet.create({ - globalContainer: { - display: "flex", - height: "100%", - width: "100%", - paddingHorizontal: "3%", // Ajoute une marge de 5% à gauche et à droite - alignItems: "center", // Centre tous les enfants horizontalement - }, - title: { - fontSize: 40, - fontWeight: "bold", - color: "#00b3f4", - marginBottom: 10, - alignSelf: "center", // Centre uniquement le titre - }, - contentContainer: { - display: "flex", - flexDirection: "column", - width: "100%", // Utilise tout l'espace restant à l'intérieur de `globalContainer` - height: "80%", - alignItems: "center", // Centre tous les enfants horizontalement - gap: "2%", - }, -}); diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index 5125d01..e540474 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -1,16 +1,52 @@ import React from "react"; -import {View, Text} from "react-native"; +import {View, Text, StyleSheet} from "react-native"; import TemplateMenu from "./TemplateMenu"; import {NavigationProp} from "@react-navigation/native"; +import InputPlayQuiz from "../components/PlayQuiz/InputPlayQuiz"; +import QuizList from "../components/lists/QuizList"; +import QuizModel from "../models/QuizModel"; interface Props { navigation: NavigationProp<any> - children: React.ReactNode + title: string + setTextInInput: (text: string) => void + quizList: QuizModel[] } -export default function TemplateQuizList({navigation, children}: Props) { +export default function TemplateQuizList({navigation, title, setTextInInput, quizList}: Props) { return ( <TemplateMenu navigation={navigation} headerNavigation={true}> - {children} + <View style={styles.globalContainer}> + <Text style={styles.title}>{title}</Text> + <View style={styles.contentContainer}> + <InputPlayQuiz setText={(text) => console.log(text)} noTitle={true} /> + <QuizList /> + </View> + </View> </TemplateMenu> ); -} \ No newline at end of file +} + +const styles = StyleSheet.create({ + globalContainer: { + display: "flex", + height: "100%", + width: "100%", + paddingHorizontal: "3%", // Ajoute une marge de 5% à gauche et à droite + alignItems: "center", // Centre tous les enfants horizontalement + }, + title: { + fontSize: 40, + fontWeight: "bold", + color: "#00b3f4", + marginBottom: 10, + alignSelf: "center", // Centre uniquement le titre + }, + contentContainer: { + display: "flex", + flexDirection: "column", + width: "100%", // Utilise tout l'espace restant à l'intérieur de `globalContainer` + height: "80%", + alignItems: "center", // Centre tous les enfants horizontalement + gap: "2%", + }, +}); -- GitLab From b553fac3cbbebae7211cad783541fd53e2af1dd9 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 3 Dec 2024 18:47:59 +0100 Subject: [PATCH 041/283] style: commentaires d'explications --- components/lists/QuizList.tsx | 19 +++++++++++++++++++ templates/TemplateQuizList.tsx | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index e5bc4c4..225ac3c 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -29,6 +29,25 @@ const quizzes: QuizModel[] = [ }, ]; +/** + * QuizList : Un composant affichant une liste de quiz avec leur titre, nombre de questions et catégorie. + * + * @component + * + * @example + * // Exemple d'utilisation : + * <QuizList /> + * + * @description + * - Ce composant utilise un `FlatList` pour afficher les quiz. + * - Chaque élément de la liste contient : + * - Le code du quiz comme titre. + * - Le nombre total de questions. + * - La catégorie du quiz, en majuscules. + * - Les quiz sont définis dans une liste statique en haut du fichier pour cet exemple. + * + * @returns Un composant React contenant une liste interactive de quiz. + */ export default function QuizList() { const renderQuizItem = ({ item }: { item: QuizModel }) => ( <TouchableOpacity style={styles.quizItem}> diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index e540474..25e82aa 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -12,6 +12,16 @@ interface Props { setTextInInput: (text: string) => void quizList: QuizModel[] } + +/** + * TemplateQuizList : Un composant principal pour afficher une liste de quiz. + * Il inclut un menu, un titre, un champ d'entrée pour les recherches et une liste de quiz. + * + * @param navigation - L'objet de navigation pour naviguer entre les écrans + * @param title - Le titre affiché en haut de la liste + * @param setTextInInput - Fonction pour gérer les modifications dans le champ d'entrée + * @param quizList - La liste des quiz à afficher + */ export default function TemplateQuizList({navigation, title, setTextInInput, quizList}: Props) { return ( <TemplateMenu navigation={navigation} headerNavigation={true}> -- GitLab From 137e241160ac6d21523c3ffc04c0f8df80080753 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 3 Dec 2024 20:22:12 +0100 Subject: [PATCH 042/283] fix: bug Text rendered --- components/PlayQuiz/SelectListBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/PlayQuiz/SelectListBox.tsx b/components/PlayQuiz/SelectListBox.tsx index 5ce90d7..12612f6 100644 --- a/components/PlayQuiz/SelectListBox.tsx +++ b/components/PlayQuiz/SelectListBox.tsx @@ -14,7 +14,7 @@ export default function SelectListBox({data, setSelected, title} : Props) { return ( <View style={styles.container}> <View style={styles.dropdownContainer}> - <Text style={styles.title}>{title}</Text> {/* Ajout du titre */} + <Text style={styles.title}>{title}</Text> <SelectList setSelected={(val: any) => setSelected(val)} data={data} -- GitLab From edd86fd24b5ba6d73903afa5d97b2bc2e783d97d Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Tue, 3 Dec 2024 20:23:19 +0100 Subject: [PATCH 043/283] =?UTF-8?q?add:=20initialisation=20de=20l'=C3=A9cr?= =?UTF-8?q?an=20profil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- screens/Profil/Profil.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 screens/Profil/Profil.tsx diff --git a/screens/Profil/Profil.tsx b/screens/Profil/Profil.tsx new file mode 100644 index 0000000..2dd614e --- /dev/null +++ b/screens/Profil/Profil.tsx @@ -0,0 +1,9 @@ +import { View } from "react-native"; + +export default function Profil() { + return ( + <View> + + </View> + ); +} \ No newline at end of file -- GitLab From 7140a954374287aea2a442a3b914fb21a0149534 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 3 Dec 2024 20:23:36 +0100 Subject: [PATCH 044/283] feat: screen MyQuizzes --- components/lists/QuizList.tsx | 31 +++++++++++++++++------- routes/TabNavigation.tsx | 4 +++ screens/MyQuizzes/MyQuizzes.tsx | 43 +++++++++++++++++++++++++++++++++ templates/TemplateQuizList.tsx | 6 ++--- 4 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 screens/MyQuizzes/MyQuizzes.tsx diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index 225ac3c..085b34a 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -48,10 +48,13 @@ const quizzes: QuizModel[] = [ * * @returns Un composant React contenant une liste interactive de quiz. */ -export default function QuizList() { +interface Props{ + quizList: QuizModel[] | null +} +export default function QuizList({quizList}: Props) { const renderQuizItem = ({ item }: { item: QuizModel }) => ( <TouchableOpacity style={styles.quizItem}> - <Text style={styles.quizTitle}>{item.code}</Text> {/* Titre du quiz */} + <Text style={styles.quizTitle}>{item.code}</Text> <Text style={styles.quizDetails}> {item.nbQuestions} QUESTIONS | {item.category.toUpperCase()} </Text> @@ -60,13 +63,17 @@ export default function QuizList() { return ( <View style={styles.container}> - <FlatList - data={quizzes} - renderItem={renderQuizItem} - keyExtractor={(item) => item.code} - showsVerticalScrollIndicator={true} - contentContainerStyle={styles.listContent} - /> + {quizList && quizList.length > 0 ? ( + <FlatList + data={quizList} + renderItem={renderQuizItem} + keyExtractor={(item) => item.code} + showsVerticalScrollIndicator={true} + contentContainerStyle={styles.listContent} + /> + ) : ( + <Text style={styles.emptyMessage}>No quizzes available.</Text> // Message si la liste est vide + )} </View> ); } @@ -114,4 +121,10 @@ const styles = StyleSheet.create({ color: "#555", textAlign: "center", }, + emptyMessage: { + fontSize: 18, + color: "#888", + textAlign: "center", + marginTop: 20, + }, }); diff --git a/routes/TabNavigation.tsx b/routes/TabNavigation.tsx index 7cfb72c..305a8cf 100644 --- a/routes/TabNavigation.tsx +++ b/routes/TabNavigation.tsx @@ -38,6 +38,10 @@ export default function TabNavigator() { name="Home" component={Home} /> + <Tab.Screen + name="MyQuizzes" + component={MyQuizzes} + /> </Tab.Navigator> ); diff --git a/screens/MyQuizzes/MyQuizzes.tsx b/screens/MyQuizzes/MyQuizzes.tsx new file mode 100644 index 0000000..7364854 --- /dev/null +++ b/screens/MyQuizzes/MyQuizzes.tsx @@ -0,0 +1,43 @@ +import {View, Text} from "react-native"; +import TemplateQuizList from "../../templates/TemplateQuizList"; +import {NavigationProp} from "@react-navigation/native"; +import {useState} from "react"; +import QuizModel from "../../models/QuizModel"; + +interface Props { + navigation: NavigationProp<any> +} +export default function MyQuizzes({navigation}: Props) { + const [quizList, setQuizList] = useState<QuizModel[] | null>([ + { + code: "QUIZ123", + category: "General Knowledge", + questions: [], + nbQuestions: 3, + nbActualQuestion: 1, + score: 0, + }, + { + code: "QUIZ456", + category: "Science", + questions: [], + nbQuestions: 5, + nbActualQuestion: 1, + score: 0, + }, + { + code: "QUIZ789", + category: "Entertainment", + questions: [], + nbQuestions: 10, + nbActualQuestion: 1, + score: 0, + }, + ]) + const [input, setInput] = useState<string>("") + + + return ( + <TemplateQuizList title={"My quizzes"} setTextInInput={setInput} quizList={quizList} navigation={navigation}/> + ); +} \ No newline at end of file diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index 25e82aa..8d49146 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -10,7 +10,7 @@ interface Props { navigation: NavigationProp<any> title: string setTextInInput: (text: string) => void - quizList: QuizModel[] + quizList: QuizModel[] | null } /** @@ -28,8 +28,8 @@ export default function TemplateQuizList({navigation, title, setTextInInput, qui <View style={styles.globalContainer}> <Text style={styles.title}>{title}</Text> <View style={styles.contentContainer}> - <InputPlayQuiz setText={(text) => console.log(text)} noTitle={true} /> - <QuizList /> + <InputPlayQuiz setText={setTextInInput} noTitle={true} /> + <QuizList quizList={quizList}/> </View> </View> </TemplateMenu> -- GitLab From aeddb1fb6b419392eda2cd6cd54db610e720837b Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Tue, 3 Dec 2024 20:28:00 +0100 Subject: [PATCH 045/283] add: gestion du tab --- routes/TabNavigation.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/routes/TabNavigation.tsx b/routes/TabNavigation.tsx index 7cfb72c..67900be 100644 --- a/routes/TabNavigation.tsx +++ b/routes/TabNavigation.tsx @@ -3,6 +3,7 @@ import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import Home from "../screens/Home/Home"; import Icon from "react-native-vector-icons/MaterialIcons"; import MyQuizzes from "../screens/MyQuizzes/MyQuizzes"; +import Profil from "../screens/Profil/Profil"; const Tab = createBottomTabNavigator(); @@ -29,6 +30,8 @@ export default function TabNavigator() { iconName = "home"; // Icône pour Home } else if (route.name === "MyQuizzes") { iconName = "person"; // Icône pour Stats + } else if (route.name === "Profil") { + iconName = "person"; } return <Icon name={iconName} size={64} color={color} />; }, @@ -39,6 +42,10 @@ export default function TabNavigator() { component={Home} /> + <Tab.Screen + name="Profil" + component={Profil} + /> </Tab.Navigator> ); } -- GitLab From c9ff21d1b06c5a0b20eca7fa6316edfc16061b4d Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Tue, 3 Dec 2024 20:40:34 +0100 Subject: [PATCH 046/283] add: ajout du contenu de la page profil --- assets/ProfilBaseImage.png | Bin 0 -> 8808 bytes screens/Profil/Profil.tsx | 30 +++++++++++++++++++++++++----- screens/Profil/ProfilChild.tsx | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 assets/ProfilBaseImage.png create mode 100644 screens/Profil/ProfilChild.tsx diff --git a/assets/ProfilBaseImage.png b/assets/ProfilBaseImage.png new file mode 100644 index 0000000000000000000000000000000000000000..123c902c001c1d4dd715c364adb55b21d129860d GIT binary patch literal 8808 zcmV-uBA4BXP)<h;3K|Lk000e1NJLTq0077U0077c1^@s6tyr#}00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yP<VFdsHA^k~2K~#7F?Vag! z8^@KvU-tkcMM@NPSfb?Ef)2};EZd}Y?E1)xPQ3fo_FrJRDyho1$RBpgRa+@bl}c@P z_k-<kTbr$l{TIaE?8ZqPS(Id18(kzNOR^=IgieyW2;Rgo)B9fc3@`uzf*@{m|LhS# z90V}auix3P85qjKslK6*6^wUSz`GpIIymr6z@S3_gg}T6x-Fk5<j;;Ae<p&@i>R9{ zEdK=v<7*-gI5!xGu&_lWkeKT4?(U9&q2w?y6a@)Fu-Wbsz(Sny7laU9Kthmv(3cAm zkT4075Y7NNPOlN1;xPhJAZIRK>j8^*G2r`wp)d$347E%cFLS>D=NQ;d|K8`G>IK81 zF#?i@)8Aa}V%+LsjK2X&J5*XTlo~YKP+)}fnQ-jheY>{zgCQqIK(atuf?I(%1QT!8 zfrcoSFcYHJahy+#cA1Y65SOG-fH(m4uO*g>5ta<J7LdNaz7D6Q<LBsD9|V1cA{Su> z?9*6u?xUBwcZ5Mxv=k817Az2WAAO1*Fyx1^UdOgS*|%%k$DkS72#C=ZB_WLF*I_#z z@6|=CG`xTq!6*yTSBb@9hZ|i`8dgAzU{nSal#d%(P#R7^j9^rUMirEX5fCF7H9?p$ z_VMl=n-4>S&=3N0_VV>N0r&?14WHDj7o;=a2vM)P7LZeYS3<#-mfxCiZLK0cjYnhe z*0b4Fw*qqR%Fz49oT?Ad>^fAhW>?(_h)nWX)^EX>Q}sqT9*w?MZ`y=I9dYjR&<7|S zN5Ke3JrW8wHxH5KhdQDzG>G&ynwx)zH@m>l2-J->sd)`@_S>PKqj2<r5spTJCcM5g zm%l#<HBQZH5SiC#?s#8-sQHs<SUL>EhderQxK@`2*Q9_LlS(^k*5s-=6N{o$#-!2? znh=l5_5L+RO$iA38*Y@!<TmspMAyF7qFgm2Ao9dG;zKZK4JLmN)sm1@-3K9O+-_<A z00cV#27?P#2k(A%>pM_oswyB-IGR_INoD*ELpJ)Y==f{ZjBr)W#G*)*5e`Ehx@e5h zSb-{06%FEsaZSmHVX;|w&mF~VV3B2^KRiVBDk4nvVtTu3VBgOEe+X2(NdFf0vO zVL@XFDn!Ku#5m-J5+NP;N(o6t1mx_M?`eUP5e`GC=yICcKY$8Qb^(!DQIiBTlo-L- zfl6fD%j%@N>&6d(!B8dyh<Eqy+*-C0rLqZ#yLS927z|}b*!FAX+ODB2CKiQr-Np(A zL)pQx_PcUr+e<DWZaCNEIt-Phqg>(Nk_!kelLaFjhRW0BG`E-BX)mdO(1ItkhTKps z1h9jpZUj*>C*3V0p{25BHK3uIMB>qCcd5!zN~S?vzrzT}qTowB42!@kX(|aNIqPo1 zCIp5oIPNk3o<a|i@s}Ahn-u4u>)hq<OOk0X`V70K)eso6B5-Ik{Eu@B97ljH)WDvc zo`IQ}8HmMVFgrU7@pwG#b-|`4usDYmD^@^jYbz{Yz8r*bXelbrYG`dU^>;84yYTzx zdAyM+MSdgzMdF%P28pMTmBtYAuwZg(8t&b@55vPFaR1>07@rsi+i~PULO?%hurqJI zV(P^X-cJKO5U_w-7OZSv1sm3{gEgyH!Nv_6pslSHEattZpz@3PA4%AtD{d}sGZaBl z8pM>6kSL^EsWi)TD+rOXM=%t=A&;@~alpIfU>6yN66fj?2%g>AWsc*%kNbT5kGUFT z?dsLAv9lAlKeHWHE^kKxp=U8jlM_c@0U_|5AKv@y)<dvFmMkE~v{IxC2#3MJLHOqK zCAfe80S#lagQSrq+{d{{_emBnAnI|E6cGHoiI~(t^!wViYhc%|T@VU|@NcT;%6D}V zjkC|*(kLpbwd8Ym_VN%$3dEbBCEOk2Olk}o!#8i=qACIy8ymyHi~fdm5-j;R;kaj- z3w1b4JoEHA?o&dHk<ba{>!3Nc3bjgCR~J0__(my2mc^C%CY-E)PY)l3_3qiZ`88Mq zOS(+wzPWw?XFQE-tvmtY5Q&Vzi4$MJLlg`egXA|b)CxA^a`1{Q1G5+b$wUbV{ypxv zf`fs8gahjyTLZ7W@(Qe5yGlJzYXKpgxAyGZ_9+y|k}lKP%Y$TEg+M#hY;4;$#Bsb0 zTo{0XfeT6y$oA(-qoBQ%G?_$>ftg9B!0<(mTgJHW)7dgoCVdds_jrESv#@9PUNpso z``q-;qfwMAY(>Yr3+=I0a2`bGoQEwaOzXhZ+${X%$d_>O!X@Qj$idB|V928YNH9vJ zO)w^gpT&KD3@ekQT)cb*{_y8N!6b%~>GKPH9g<L@slDJbl!8u|u3brl6bxn0-x(f- zqsNXxG^+9zo`Uet<b*fqGkp*bPeb^9lckXPz8RmKY%6*fXlq^uKmKtKtXs1NzXwUB zNPahsh@ckPT!<DaXu8AXm6qBAMFWDtAl$w?41fCb7cesylTjt7h(d%?YL476Hw!Od z788<x`23G>^UiHF5rxz=wtniQqfjlf=rg2kSeFN+&oOl4CLBBdmCA<G-8|?%!FAU& zCgOY=WZv6|?7!!G-NG7#Uhl@+97ks0ElfD>|GQUV%cc+{5(x-e+6hTPT4d2vpsPK! zLda~k4R`O3!13e9Az>##CF1IJP%$YK$B%slw=t`4p;pwe5Tzh3vZyPqoA&TbCx(vh z_*ew~`+xrjVzGq0Us)wQnXS$prAjY5sSO<Ed!SZnZ*7Ht`1{vk)vDF<5nfZDv+OKd zi!4fm=%_tt?wE^4p%)$UM8Zo8HV7DLn~TPv7fnXeC>pY8Es|G2<YIWndO#bzwHFLd zo;(E)9*xP+E|qvVtlrmUa2fuUiA?lKB9U=Sa-INH5rB@nqmMs=X7bM$vxYtI)ouy~ zdqBg;)zFPFgu^$KNhP??S<YPFF@L>Bt)tR5mb^{DhoNw|sEAyhvM4Q*_pD$>SQGO~ zg>5OBHiwD8GiT1pfy5vnBSnk_+%*_@TPhew9XY98!I8osWK2tjdQX0R3iCP%jLr!a zzScyMkQT}JoCO7hYOX*t?LpPGe?4;=Ca0%ypk#^hu=>*&x$u&Nv#~iid-j}M)TJ36 z`L>>3P(Xxby$zZ%Ha-d84~5;;PIP**1wtFc9Fc{NoXJG!ch|1M{f7^wNhw&R5}DP9 zS%C#Dq?}h;I_E<4fM%RMdp0Ao)A*yrGaWBnxB&VgdKPlla|%c>*rZ*5s|ODsz^z-i zWX%<sSCoaeK^;OQWX?kt>pI>~dR2W!w!H6^Y6c<T=FOY%=usq9bSWA`Q|4nKXFc~B z;Oq_12#zyoUthnLO%A+oOy9k~e?YE(*NKIk^{fKoIxZp5i1CRj7#+PYi};+py{H;* zW>vlihw#l?FgZ1&TUmTgXFcncW}Iu~oYP{b@2+2i*=S6!f{P63yC=|YymaYOs>6yV zFpFnhKAxjffG(MHWr5SoIVh8Y`gSu+c1^(q;tfn5+O{K?>FQ&0vaCvB-U!gmyrS~; z(b0RVH4img&@ZE$gu=Qc4wDm;Ffu%<%2M>{-k8<Qnl~DBGp{IfJ~%ieOWRWorM$J< z4(O#xQkrv+%1<1<49;w3*8D54QDLI&TZN~9YLU`sN}W9srA3DC4g)Z~v?=;s^CGut zXW(?-RobdK1PzY2T-JdA#{I`+k6dcMrshs3`K<J#QAueHTE$3<bSZ*0JvR&YA3TuD zM|AN+@LPsXe>0S}U3r>-ux6_VbON=8oGItUH1*aXgkvY*&Ye3_%j)EpG4AyEe@_<> z#<fZg61VQ&b)8Y}tJX~&x<YHj?%lg5wTDit4bhHBlY#W^?m~2dW;}X024MEtNJAQ* zK?_DkM`h?+xX~>=1Vv;wvD^@sHakSo%s4SI0k&hyG82XSQ?pv6Y{-TcwhiNWj;J<G zR{gU1M+1@;?oEvVi)*DlNI*tLMskEQ^%abC7~DrQE0NG_3k9DnBu4_y1^YoGNH8Wa zIY^<*^mPw@ZKAiJka;yW7Lk()B~-P^s*6;r!#4t$RvIK248Yi<M>;I`C<-z|xn{u% zv?gCj-ll*0`i98&2!SSe^|}!%FOl^|{xu)!V2(O;#9Ny$gbk0z9-u3xdTBWfk}D1y z8On#8AH2JXZc7141QOasuV~fa?CcyE7DB)ro+Dc~9|UpcX1eL8(w&_5fM(E|2S=zC zbq+I$!E{s~UxM->lhd=Z%9m>t4#xy(h<-`|VLqEn2k6pMDMietnGsV%S;D!xG%Z*N z%AzMQm_eFWw{?~n`j;}{CJI>yv_S0wOS$2xnOU#`&1hPuF1;rZnfn0h2X|M>(Ejd@ z+|!+)J=*1dBO2s~gqt+?zG(<Ff-d|t&WbcU=8^-WLDYGoW^&3`)9z%1=pXJzu(Zjb zD;UIM7^PytWDlrJyxnMZCtx*6M_ofYD3Zl>j9?K{Q`7D(lL*Pj)YKH{NMa_siZj79 z(;kX=JPwYXieCA(h|wG=Xm_nxECzaFcX1J5A<&R`JO+Va01OL5dov~yanKRQSqH}y zKnOHs8Y43ct+_Tnh=R07G)fx8+c8%k(5MInFyO1DiIp$srF#U+LoFhsTl&owjPaGU zjBXQXh{#xL?bjAYI9$-YpxvmHiKX^i*9jaRA+zuy2PO}qU}_g3&{2cnckT$Fn1n!M zm@t9f1rch$MwL_6&IjqxT7ziRHE_wubEH`(Yd<6)S~}^p2*}c`&40lNF3(dSK;dwf zyiXT|t3k9BkYH0#CJPM<L+2<MYyv4D`e_m!O)M4958|w7@!WQ`$%80pci%uD2mncM zT`M&8K?E#c-X;Y_m}u34aPFQf$hYlvW1^pcFtyU5rKMShFg+z<YO%6Fc@R7=Ez8Kn z63P2?LQ4%oOQu$~FINp3j0RZ{N7%4(<q81bcAq+-pMbbgnD!MbAh19?8{>m0xNu<A z%9WtA4-x?lS-q+QY!k&=5GGW!xc2sTU6ZL1%>+bg42E`9zzRs`lWDl^b3VM?Q!LaT z%bHuDIT%bms8Jq-xdNgcG#gTqaDC@`>5tH6m4^Ayx!JIxQ@hTJel#?(R4-m?=-#=$ zQ~DW};A#Gb@jX&(z)2*)88~>-gl5qUoI1l)?yjE?f&vkb#^K2y{6OxXBtw@hefh1a z=2blT#1rm<DLvunh1~$nAhk*LMax^;VC~wqs-hQFibXpWvh+h&=(~4xbU<rstF|T< z=73OxI6})PmdEBTTW|oR7cCkMqM9}A*s-ILH_FM25Y9Mt-3)`aIPlb#tq>q>A@@o5 zOf4ODx6O{{e>I_o-4qHXm!W$*Oz8xE^)Tm}YmjBjmciC-Tfw04;uD*md=i$mERzkZ zNjP)_!xCX`$DMxAj+9eQiLmY4x8dyQs2yE}f9G(0kXwKVs=X32H?(Kpi?mCKEH#mv zr8~AfKbc1(BWw}jc1uue4MMD5y&85r^NiN<enMJMIOd;u=4lLIv}(WNGLV?+=P&Kp z90ncBP27yI%oyXWx$D_o(Av@@!@g91l&Zzj%NJSE)&{$~U&M$Wlm3UsQ%0n_yE~al z4}<1Vz0u1ry-dRbwCv#F1}FCZ%^qlJY4SawXRrcXneYT;05rp^H}VF6urYD?#720b zYZoMJ9rh+nkJeG%<Avv*hfP0tT=^aP3WfyTE14gZ0)p<AhK_m`P;tD7=Ec+7w}UQP z#0y3|gMo@Ydv*guyV7zu?bLAvVG1P^vU@X2h%hDtKu0n=DpJSy{ja<VcEW+7!9iaV zG5cU~>dH#@eFt~&)BfjAZQBMfz4Q|Oq8xL%5tla}G%ZYXoL>4NuWmeO84Z#_&;I=~ zlia8<w-@!U9XsKNul`WCzSMc42p0WJs*SUk2Zunjt=MuQOHAA-*U*hyaPs6yh$Rwk z>_5Hnb**a<?$a77Z6P3FacFLChQHnSHxSzFWy@3Fgm(SayrMb0XXlp9bi=nn2H9LJ z1co%WZrK9s*RO-GPyH3b;jnDx>__ftS;N+;U!YZ<gTdxd2zKw@jUnCT$>gZ%znTJ1 zNit}a6c9W|$M77z2?jY}ym^<RTWu}P@Z+BS@ZjMt7`QM1H*ekoW(DN_B65Z%A#vB4 z)B@h@FEicr8L5LLezuJpJK?$KpNGdg*UQJ4u$70wQm<1x{Q9+`hK@QDz241~2C<!f zFs(S};f;AJ!FX)#YWUHQdf?HcG58*}$<?b@ArVc?E4iqIpm;)%wOx_xfYU>*nE2bW zbqhT8)K*zBKtk;}cCwV8XTH{;WWgovQlcL+2AZ}a%}>gjCzDG0BY1ms<eoIWhVPC- zWO4#?2$YK#vTViM!HOb7elu(-CYj%f&UC2heQ14)jeZhU1*~lEKrQhYghHF4bK?g0 z)6<(eNnvI@``#!iE#m&|oT1)^&9O(&|5)YylCeo2L&*_6V90{wQz!Jl8gd&S-vFIx zvXCrLAKOSI0>dMtFg7*@Gc(gLJ~07vv$JVJvJ|7m8?c$eL0W+pHmzK_Qr>=S?OIsY zx(qKYlLBlzZqjpsx+=dw7+6|FH%&kUIG-_KJzz+ZIdJ59cDm2I-&WBds&fo92cdQS zTG)sOcr&$<CYYL@mfI=MMrR=!i^KHH3@yl!e}^=H?f7PQ9L&@=SQbw*)rp=~Khoc7 zZE1n!%a_ZQexx<Lf<gy``)*7%qL2s7FF5pmci$`59`VWB-uDJQAQ|xgo-QC!yO%Ya zonM!N8c0rBST#BJaKNot){M@+s&OJgay)?`0YO3WZ^b}^I)UaA35Fc-oFzB<NyHQK zy%;^_N~-*ioNlHzx?#x;Zw!SZ9Nz2yect?G@*(-j-p7{)q*uSBx)A!!p;Cr|GtDfG z_qQoE$^3`M{AtD!pMjwWye-RIXP%XQ0Q>*05suP<8n`#}zIg(IcJZfRSP*ZmNV<Pw zn$OicP9pJ1=6zY`9~161XvjSCipcKCIpPyA7&IcAc|~Lu5MyQ;bR(O2MdWnxjG1N7 zh^*!nkuzA1arKYDV9<ixk=vXC5{U5+!B7JDf`?P&VJMzB%RSa}p5gB9%@MS~dcjaU zWvz2EiIC!8y>9EPtSmSM2|IBZ3`@YLC4~A(duzlKj+*s(026!`B;>xB%zYe^2i?c; zan3CHm}Ld!eYP#+eZgQe1Ok?7EG;`&1?2O+M$?5Hy5l(Zf)U%iXUI6~`S22i+;@+6 zTa}#1viQ+an4X!2>FH^RM8+W+orBrgSy=&0&xyxl$wERV`{Al;;g*&b*+00csVTV~ z0UfllW?Neu3Y1qA>1)&DFHz64Wm==R56weP=iDQ2Wg<E2c){KWLk^f+vFG6IE(;JD zhmm_DaR2@o+#Mc<M`I6A5a#3z??<@XzIk<3x%MKNn3&4?xWM;nTiym8t5(8>&Q4gf zYBh9ru7jY(W#OZpu-&qJLoV=o<+YIVp1;1np$@xQPywUSA{oWsYPl+x8sWx`n=mpm z3d5r#U^_M*Ecp_u#ue`R5f9-mrb_CX90HOQq_oF{+`oe^pOx&st`=#nUA+c2ZrlKy zH;1I~aG!<{+1|skK~utU#$H><{~#7M!JN7D{UM#SNZJ~SEG7yKxp5MTn>Qhhf-x3} z$o^7vh)j>1tKvnG(HJ{(ok^h!mZ(Ht8gRZpE98BaT<d|K23D``fK5+60b4>LSkc}N z%}v4ND#zsCaZNO@TwfP(Jb%1<$L4qPJ`WawZdxR_fN;2W?HXLYehqz!dpIMjC)$F6 zH_W)mXxZsDo-4J0BsB|9eNJ1hdL*mamKutara@E7vKVx(Uk6V={WOG7Sc3j-o#py; z=5B|rkAR@|*ql#$5Q{cZb<iS7!4O>f%a#?8&hzBNG+euW9WH-+MLN|a5T0hpe63gI zTd58wDZxTF9UU6~=a64TxXT}1foW@Ng-x3_!Hyj}(FE&2eUmN}$sSo*nnGlw9-aKf zY7hBbSR^`VkxT)Jj7`9~^XK69?K^TH*g_2mIiz^*4lvabknHc7T*m8a910J6V|sIF z6LdZIEUa0xI{ADZ1Z1(=gDB`SHl`NwhiBF1l{3*<80fz!%_-qEjv_P49X0iu!A@Ha z?Am738*9s!E%4$CUFfH@q0{fS1IpXgL{V=7Qi%4D&xZnNL@gqON|<>$yL0Ey!#9^M zp(*7^6U&heogvK^X@uQxE#G&D#bVI5tQmcj?a=+g^Rg{zYFTeX3CN-%SAGh<)QzG= zJT2ha=r_W*;Q!8?hMBoJDFDLDxrtPGwvo1l_MCeeZkB$N3inb{a^Jo^@YL3Ap!!8F zECBT+Aoy7i?|pXbp+a6$@FjP(NFUU3V+ZN3x+5ft+3cf7kHWnNk072%ppmZJR`0qu zYQLy70Rhkaax;rKJ3PL=6JCAwRcLQtuAVPc<GD=gNI>#t&9kwjIq~d;>u*AU{|@R5 zC(YkrvLoV&6DQ@KAW5O1thhWYo-Llw#Hi2EXlBlkyPe-DFCyCsIjR-WE3do)+qZ9* zzDnBblTotzNHn6U{La2z+dhT@S+db^_VUotdh5cNG^=oQ965SS3J8_@(Uxn>3aZJI zbBzVWUjmcbCVShqZSd+#FGH}&O5W!gp7kLh;?q4lx4s2SV95e<>dH_kD5R-X7Y@6u zo<$RCd~ynUdwb#G*too(d)pa^+Ud?-9nu9oGfvmOybbp6-w*57td&`MI&U(#kw-vk z!9*=2teeS_<CXM=^|b1cX2u(+F@FF1-$P_#Qfd*w%z~rEGBq^=pa1cXvb@G?dFtgL zYZ-FPmns}Y(Q{_4cVZ+Tw7IzjPM<vo7rw#pt*vG}yFs1razQ2+g}X(<fv&DD*th3J zDJ03}*z!s9&CM!nkR>_heu|z#g>5f7=~8Lr1cw*#i=$t{nRDl)a5&09P#bn?ZBi8m zb+ebdRbiB`Z=fHJpZp4DV^OGODCYr1QnY~VeeS7VG(>C4)FJ_(dZ}1KpflbJ-wzF; z6K>&sG{adkJ4~5mW=yO_BXdJ^&hY$MXkvZ)?KL=d?1c2~Y?1Qg@-5J+6vdiW9wnPY z(bOV`y$h-ilOfy~ngXAH{s(l%$1uNPt3^!|=9ks3zLE>mF|;7$q=F~Si6^9qwXt&@ zy#D&@&>RdX6HM;HkUFVVGO>y_tvpI5?x@Zvj23|^;%z>Vz`VwhFOSGb6|LiDoP@$# zCvGSj!DSSSj?w!M;V)nIqWOyF-CbXw)Fjo>CXrH@Rvsn$1~RJUh{I4Nyaogb)Es{~ zas+PQxjS!hkfC&V=bKhfj*N`Jkt1Iwv+~tMKxs@XkCKY`-d$TiteQW<DbEpyV<%3a za14WIYSI1P#UyplsQA!<<Y6cb3YXL2_2uF+)yiRbco>c!KMn~LnCiJ9;-kGgx0bY@ zTgktQ73YWWM?X{;vVG5;JCE7%8?rly$#GN;wNsXm+hd<TeHtvD%D7jMuoabKucVZG z6j8&u%K9T*I^O+#190KuWvMkpa#tB6B;`hi%Yhd8Von4F;_{_$p|9^EWcCFsGqQ|b zH9IUVW%=aNAF=Hv)xH#hm*)uIz6s|sHqYkm^KNXYiokr~P>VzVg#pwiH|5%QPk_n^ z<>E@|clarrXj6U!o%VO33?zHgVho;YtB#*IsWRit?MR5uhUNAgG*kv92weIjRP%NM z6N&d9K1kKNl?l1VgK}|YqO7`)oOb#GP&OF+>5DI5dM=h+5y+B{7%#jUc-0AW@fc2A zCouf$q}^Xima+h<P|9u`(igy($0txSyp@0qPM-W4B9RGz<hIADuOl+Lj2J2mUVsM= zAHkV3XH{V%N{9_2S>jV{sSk~*atjAh5id3vwGKjwBU{55d;jjc@4!&oT)A=uBa1^& zV*2;(+;#veKt%+Eq6=13W;crv66JSL!Qosq4#!WPn3v}u9IwHGdzhlc%9FWw$;nfH zg}GQ<YLOBoN&4NfR1qo`2la)2!%zeq^BbqnoQBD%X)x42lnRMPW0<2lCwm7M359dt zDn~ds7ZptuP$<r=lL^BT8NGKOu3sCJ8C=e?H*Yf<qx$e<gKxZr{>XRVU4uKrBU0lF zNN=IE$m>;b%brqD@l0B=LUQud*U|xZXu0PCC)Su&wSv1xiX&uDg+jnfC?c<la1d2V zHT#5Q(LM<!0s99o$abL27hCtDRQXzP8Y)XFSLLoH;VNf!`P-{_{VZuJtIL=-6yIMh z;UKD3i$Py_@s2x#iHS)RkOA2e(Dv2%<kA#IgVZ7%&Y$mxDfAzOt6B0O9r9`k2T|2x zC33d*?A+45P@Y8X>Br#`h70Y4y9bAupZ74rP^+lx#N9m?6LA~P_w`G^WI+=d#{>yS zB^~l?)I>EO#k3d7xIe^%(`6J8r%roN8B#Eu>;?yiU~DXsGvrHSfyP9&g@dS>a#zZV zri8@X2b^|v4Zy{Vm!y-&0|7Y;sTwP_QH|e}^4~TY^jg8YPVRyE^hCmjOW#}uxw|iQ z&G&B;2jbmIWXKmwP1S=-A+c>=gp1)!GmbOPU|sG*VaUy3co47cm@lad5xVxJUhq^b zHMd}bDl_9uNFm`E${o0PQ8r+Z#fS@7SjGpbAEf?wR2hoZ6r^6j{c+B^%UPAXRBCcX z5k)P6QPCYs#6G%y{krVrCl{>&l&sjzP({$xiYMZ5_3BlaoS6D3Smj97Le%V%J95xo zes1eQC+@t9cBypMgwNOT@aI9Y<wOj%3X5RU@ZHyTKXb4aobg=L#T@*z_YQ>|haW}9 zJtSz)aR$B}kQ7EJ>IV$J^fP9^^&h`J6owk7u8fqQ{L?QF3C`co_LvcndcpbVH~;#J zgHR{bofUuJy?+Ve40M!bG|V;vQrqD53S*e}o&WmvFM6Sls9QJpC-4385EI$8iJ_*5 z2<Ci*f^i7ykh+co{S38<Eu0VV=9^%sIWV}=8(@id>cQ8@MMDHifBN394xrQizNq7k zMH>k+r!Z;t?tlK<FFu7jr=c<lWOi}E-!_6$HKaMUe9r#x!w(PDZ0NQS8ZOmFW>><o zX)pJ;!B8n0PB1(gHWzIKr6M$}V0biqv4$rooU#4J^ePz*FBl%$R5Qtl7dSn}nJ)@$ z1j9qiazBcC2_fEQz<R)t7Yf$`6Nl?9$yW-ru7_25l*B;>{0*a3=AoUo$c8Bxcfk;G z4>;_A=DrFFg7b-B@Trd4A{$0PvY?r;gi$CyGeVIaBOrPB=|BFeOEA%cXX*{~S!kQ< z4ybP;=u`BgWAPaitoJwn{?8TPF0Y<20#YEZcIm?W%6>d~U3H*g!g%y!P~sSH(QmXw zVHg1^l7j~ibxgHbUEn|uWBdgih#?f75L8~%MDV@C_}(N81E?j!L2mcgbD>}@U<9O0 zc!DDw=s?p9g+_z`_(~kOA$pwdbD!D)xrHg>9@Nz(f-j2T>%!<Wj0?fS4rdX|hJMS6 eM~onphyMpe%%VsY;(5CO0000<MNUMnLSTXi0GrDI literal 0 HcmV?d00001 diff --git a/screens/Profil/Profil.tsx b/screens/Profil/Profil.tsx index 2dd614e..a226e78 100644 --- a/screens/Profil/Profil.tsx +++ b/screens/Profil/Profil.tsx @@ -1,9 +1,29 @@ -import { View } from "react-native"; +import { View, StyleSheet } from "react-native"; +import TemplateMenu from "../../templates/TemplateMenu"; +import { NavigationProp } from "@react-navigation/native"; +import ProfilChild from "./ProfilChild"; -export default function Profil() { - return ( - <View> +interface Props { + navigation: NavigationProp<any>; +} +export default function Profil({navigation}: Props) { + return ( + <View style={styles.containerGlobal}> + <TemplateMenu navigation={navigation} headerNavigation={false}> + <View> + <ProfilChild navigation={navigation}/> + </View> + </TemplateMenu> </View> ); -} \ No newline at end of file +} + +const styles = StyleSheet.create({ + containerGlobal: { + display: 'flex', + width: '100%', + height: '100%', + flexDirection: 'column', + }, +}); \ No newline at end of file diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx new file mode 100644 index 0000000..23a46b8 --- /dev/null +++ b/screens/Profil/ProfilChild.tsx @@ -0,0 +1,33 @@ +import { NavigationProp } from "@react-navigation/native"; +import { View, StyleSheet, Image, Text } from "react-native"; + +interface Props { + navigation: NavigationProp<any>; +} + +export default function ProfilChild({navigation}: Props) { + return ( + <View style={styles.containerGlobal}> + <View> + <Image source={require('../../assets/TitleApp.png')}/> + <Image source={require('../../assets/ProfilBaseImage.png')}/> + <Text>_Pseudo_</Text> + </View> + <View> + <Text>Played quiz : _123_</Text> + <Text>right answer rate : _43%_</Text> + <Text>Favorit category : _HISTORY AND CULTURE_</Text> + </View> + </View> + ) +} + +const styles = StyleSheet.create({ + containerGlobal: { + display: 'flex', + width: '100%', + height: '100%', + justifyContent: 'flex-start', + alignItems: 'center', + }, +}); \ No newline at end of file -- GitLab From 65d468fede71861547bfd77d031d5f860b19cd37 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 3 Dec 2024 20:51:39 +0100 Subject: [PATCH 047/283] feat: Community --- routes/StackNavigator.tsx | 2 ++ routes/TabNavigation.tsx | 15 +++++++++--- screens/Community/Community.tsx | 43 +++++++++++++++++++++++++++++++++ screens/Home/HomeChild.tsx | 8 +++--- templates/TemplateQuizList.tsx | 2 +- 5 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 screens/Community/Community.tsx diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index 3eb2d53..c34183c 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -6,6 +6,7 @@ import EndQuiz from "../screens/EndQuiz/EndQuiz"; import PlayingQuiz from "../screens/PlayingQuiz/PlayingQuiz"; import TabNavigator from "./TabNavigation"; import PlayQuiz from "../screens/PlayQuiz/PlayQuiz"; +import MyQuizzes from "../screens/MyQuizzes/MyQuizzes"; const Stack = createNativeStackNavigator(); @@ -19,6 +20,7 @@ export default function StackNavigator() { <Stack.Screen name="PlayingQuiz" component={PlayingQuiz} options={{ headerShown: false }}/> <Stack.Screen name="PlayQuiz" component={PlayQuiz} options={{ headerShown: false }}/> <Stack.Screen name="EndQuiz" component={EndQuiz} options={{ headerShown: false }}/> + <Stack.Screen name="MyQuizzes" component={MyQuizzes} options={{ headerShown: false }}/> </Stack.Navigator> </NavigationContainer> ); diff --git a/routes/TabNavigation.tsx b/routes/TabNavigation.tsx index 305a8cf..61338eb 100644 --- a/routes/TabNavigation.tsx +++ b/routes/TabNavigation.tsx @@ -3,6 +3,7 @@ import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import Home from "../screens/Home/Home"; import Icon from "react-native-vector-icons/MaterialIcons"; import MyQuizzes from "../screens/MyQuizzes/MyQuizzes"; +import Community from "../screens/Community/Community"; const Tab = createBottomTabNavigator(); @@ -26,9 +27,11 @@ export default function TabNavigator() { // Définir des icônes pour chaque route let iconName; if (route.name === "Home") { - iconName = "home"; // Icône pour Home + iconName = "home"; } else if (route.name === "MyQuizzes") { - iconName = "person"; // Icône pour Stats + iconName = "person"; + } else if (route.name === "Community") { + iconName = "people"; } return <Icon name={iconName} size={64} color={color} />; }, @@ -38,9 +41,13 @@ export default function TabNavigator() { name="Home" component={Home} /> + {/*<Tab.Screen*/} + {/* name="Profil"*/} + {/* component={Profil}*/} + {/*/>*/} <Tab.Screen - name="MyQuizzes" - component={MyQuizzes} + name="Community" + component={Community} /> </Tab.Navigator> diff --git a/screens/Community/Community.tsx b/screens/Community/Community.tsx new file mode 100644 index 0000000..3381b65 --- /dev/null +++ b/screens/Community/Community.tsx @@ -0,0 +1,43 @@ +import {View, Text} from "react-native"; +import TemplateQuizList from "../../templates/TemplateQuizList"; +import {NavigationProp} from "@react-navigation/native"; +import {useState} from "react"; +import QuizModel from "../../models/QuizModel"; + +interface Props { + navigation: NavigationProp<any> +} +export default function Community({navigation}: Props) { + const [quizList, setQuizList] = useState<QuizModel[] | null>([ + { + code: "QUIZ123", + category: "General Knowledge", + questions: [], + nbQuestions: 3, + nbActualQuestion: 1, + score: 0, + }, + { + code: "QUIZ456", + category: "Science", + questions: [], + nbQuestions: 5, + nbActualQuestion: 1, + score: 0, + }, + { + code: "QUIZ789", + category: "Entertainment", + questions: [], + nbQuestions: 10, + nbActualQuestion: 1, + score: 0, + }, + ]) + const [input, setInput] = useState<string>("") + + + return ( + <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={quizList} navigation={navigation}/> + ); +} \ No newline at end of file diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index a750275..9589ac5 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -19,8 +19,8 @@ export default function HomeChild({navigation}: Props) { const [modalVisible, setModalVisible] = useState(false); const {t} = useTranslation(); - const handleButtonJoinPressed = () => { - setModalVisible(true); + const handleButtonMyQuizzesPressed = () => { + navigation.navigate("MyQuizzes"); } const handleButtonGeneratePressed = () => { @@ -33,9 +33,9 @@ export default function HomeChild({navigation}: Props) { <Image source={require('../../assets/TitleApp.png')} style={styles.image} /> </View> <View style={styles.buttonContainer}> - <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonJoinPressed}/> + <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonMyQuizzesPressed}/> <MenuButton text={"PLAY A QUIZ"} handleButtonPressed={handleButtonGeneratePressed}/> - <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonJoinPressed}/> + <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/> </View> <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> </View> diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index 8d49146..963da5e 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -55,7 +55,7 @@ const styles = StyleSheet.create({ display: "flex", flexDirection: "column", width: "100%", // Utilise tout l'espace restant à l'intérieur de `globalContainer` - height: "80%", + height: "75%", alignItems: "center", // Centre tous les enfants horizontalement gap: "2%", }, -- GitLab From df81b6756c69eab911604405b83d2090d5430e1a Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 3 Dec 2024 21:08:12 +0100 Subject: [PATCH 048/283] feat: bouton goback optionnel dans le header --- screens/Community/Community.tsx | 2 +- screens/Home/HeaderNavigation.tsx | 12 ++++++++---- screens/Home/Home.tsx | 3 +-- screens/MyQuizzes/MyQuizzes.tsx | 2 +- screens/PlayQuiz/PlayQuiz.tsx | 2 +- templates/TemplateMenu.tsx | 6 ++++-- templates/TemplateQuizList.tsx | 6 ++++-- 7 files changed, 20 insertions(+), 13 deletions(-) diff --git a/screens/Community/Community.tsx b/screens/Community/Community.tsx index 3381b65..ec8bfa5 100644 --- a/screens/Community/Community.tsx +++ b/screens/Community/Community.tsx @@ -38,6 +38,6 @@ export default function Community({navigation}: Props) { return ( - <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={quizList} navigation={navigation}/> + <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={quizList} navigation={navigation} buttonGoBack={false}/> ); } \ No newline at end of file diff --git a/screens/Home/HeaderNavigation.tsx b/screens/Home/HeaderNavigation.tsx index 5b71d9c..1a5e961 100644 --- a/screens/Home/HeaderNavigation.tsx +++ b/screens/Home/HeaderNavigation.tsx @@ -3,18 +3,22 @@ import { NavigationProp } from "@react-navigation/native"; interface Props { navigation: NavigationProp<any>; + buttonGoBack: boolean } /** * HeaderNavigation : Header qui permet de remonter à l'écran précédent * @param navigation - navigation + * @param buttonGoBack - boolean qui dit si le bouton de retour doit être affiché */ -export default function HeaderNavigation({ navigation }: Props) { +export default function HeaderNavigation({ navigation, buttonGoBack }: Props) { return ( <View style={styles.container}> - <TouchableOpacity onPress={() => navigation.goBack()} style={styles.goBackContainer}> - <Image source={require('../../assets/GoBackNavigation.png')}/> - </TouchableOpacity> + {buttonGoBack && ( // Affiche le bouton uniquement si buttonGoBack est true + <TouchableOpacity onPress={() => navigation.goBack()} style={styles.goBackContainer}> + <Image source={require('../../assets/GoBackNavigation.png')} /> + </TouchableOpacity> + )} <Image source={require('../../assets/TitleApp.png')} style={styles.titleImage} /> </View> ); diff --git a/screens/Home/Home.tsx b/screens/Home/Home.tsx index 53bb379..dc3b638 100644 --- a/screens/Home/Home.tsx +++ b/screens/Home/Home.tsx @@ -3,7 +3,6 @@ import TemplateMono from "../../templates/TemplateMono"; import HomeChild from "./HomeChild"; import TemplateMenu from "../../templates/TemplateMenu"; import {NavigationProp} from "@react-navigation/native"; -import HeaderNavigation from "./HeaderNavigation"; interface Props { navigation: NavigationProp<any>; @@ -12,7 +11,7 @@ interface Props { export default function Home({navigation}: Props) { return ( <View style={styles.containerGlobal}> - <TemplateMenu navigation={navigation} headerNavigation={false}> + <TemplateMenu navigation={navigation} headerNavigation={false} buttonGoBack={false}> <View> <HomeChild navigation={navigation}/> </View> diff --git a/screens/MyQuizzes/MyQuizzes.tsx b/screens/MyQuizzes/MyQuizzes.tsx index 7364854..7419d53 100644 --- a/screens/MyQuizzes/MyQuizzes.tsx +++ b/screens/MyQuizzes/MyQuizzes.tsx @@ -38,6 +38,6 @@ export default function MyQuizzes({navigation}: Props) { return ( - <TemplateQuizList title={"My quizzes"} setTextInInput={setInput} quizList={quizList} navigation={navigation}/> + <TemplateQuizList title={"My quizzes"} setTextInInput={setInput} quizList={quizList} navigation={navigation} buttonGoBack={true}/> ); } \ No newline at end of file diff --git a/screens/PlayQuiz/PlayQuiz.tsx b/screens/PlayQuiz/PlayQuiz.tsx index 25fe9f8..991c6ba 100644 --- a/screens/PlayQuiz/PlayQuiz.tsx +++ b/screens/PlayQuiz/PlayQuiz.tsx @@ -12,7 +12,7 @@ export default function PlayQuiz({navigation}: Props) { return ( <View style={styles.containerGlobal}> - <TemplateMenu navigation={navigation} headerNavigation={true}> + <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> <View> <PlayQuizChild navigation={navigation}/> </View> diff --git a/templates/TemplateMenu.tsx b/templates/TemplateMenu.tsx index ee54727..6be8186 100644 --- a/templates/TemplateMenu.tsx +++ b/templates/TemplateMenu.tsx @@ -6,6 +6,7 @@ interface Props{ children: React.ReactNode navigation: NavigationProp<any>; headerNavigation: boolean; + buttonGoBack: boolean } /** @@ -13,14 +14,15 @@ interface Props{ * @param children - Contenu du menu * @param navigation - Navigation * @param headerNavigation - Si le menu est en haut de page + * @param buttonGoBack - boolean qui dit si le bouton de retour doit être affiché */ -export default function TemplateMenu({children, navigation, headerNavigation}: Props) { +export default function TemplateMenu({children, navigation, headerNavigation, buttonGoBack}: Props) { return ( <View style={styles.container}> <ImageBackground source={require('../assets/MenuBackground.png')} style={styles.image}> <SafeAreaView> - {headerNavigation && <HeaderNavigation navigation={navigation}/>} + {headerNavigation && <HeaderNavigation navigation={navigation} buttonGoBack={buttonGoBack}/>} <View style={styles.container}> {children} </View> diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index 963da5e..5c6eda4 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -11,6 +11,7 @@ interface Props { title: string setTextInInput: (text: string) => void quizList: QuizModel[] | null + buttonGoBack: boolean } /** @@ -21,10 +22,11 @@ interface Props { * @param title - Le titre affiché en haut de la liste * @param setTextInInput - Fonction pour gérer les modifications dans le champ d'entrée * @param quizList - La liste des quiz à afficher + * @param buttonGoBack - boolean qui dit si le bouton de retour doit être affiché */ -export default function TemplateQuizList({navigation, title, setTextInInput, quizList}: Props) { +export default function TemplateQuizList({navigation, title, setTextInInput, quizList, buttonGoBack}: Props) { return ( - <TemplateMenu navigation={navigation} headerNavigation={true}> + <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={buttonGoBack}> <View style={styles.globalContainer}> <Text style={styles.title}>{title}</Text> <View style={styles.contentContainer}> -- GitLab From 0283c9784aef26bca54fba65216122fe07fbfb59 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Tue, 3 Dec 2024 21:19:39 +0100 Subject: [PATCH 049/283] add: ajout du style de la page profil --- screens/Profil/ProfilChild.tsx | 44 +++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index 23a46b8..5bcad82 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -8,15 +8,15 @@ interface Props { export default function ProfilChild({navigation}: Props) { return ( <View style={styles.containerGlobal}> - <View> - <Image source={require('../../assets/TitleApp.png')}/> - <Image source={require('../../assets/ProfilBaseImage.png')}/> - <Text>_Pseudo_</Text> + <View style={styles.containerPlayerInfos}> + <Image source={require('../../assets/TitleApp.png')} style={styles.titleImage}/> + <Image source={require('../../assets/ProfilBaseImage.png')} style={styles.profilImage}/> + <Text style={styles.pseudoText}>_Pseudo_</Text> </View> - <View> - <Text>Played quiz : _123_</Text> - <Text>right answer rate : _43%_</Text> - <Text>Favorit category : _HISTORY AND CULTURE_</Text> + <View style={styles.containerPlayerStats}> + <Text style={styles.statsText}>Played quiz : _123_</Text> + <Text style={styles.statsText}>right answer rate : _43%_</Text> + <Text style={styles.statsText}>Favorit category : _HISTORY AND CULTURE_</Text> </View> </View> ) @@ -30,4 +30,32 @@ const styles = StyleSheet.create({ justifyContent: 'flex-start', alignItems: 'center', }, + containerPlayerInfos: { + width: '100%', + display: 'flex', + alignItems: 'center', + }, + titleImage: { + width: 170, // Ajustez en fonction de la taille de l'image + height: 80, + resizeMode: 'contain', // Pour conserver les proportions + }, + profilImage: { + height: 200, + width: 200, + borderRadius: 100, + }, + pseudoText: { + fontSize: 35, + fontWeight: 'bold', + color: '#00B3F4', + }, + containerPlayerStats: { + width: '100%', + padding: 30, + }, + statsText: { + fontSize: 17, + fontWeight: 'bold', + }, }); \ No newline at end of file -- GitLab From 4b4da25f3bfd056bb0215b64c480d1445769176d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 3 Dec 2024 21:54:37 +0100 Subject: [PATCH 050/283] feat: InformationsOfQuiz --- components/lists/QuizList.tsx | 9 +- routes/StackNavigator.tsx | 2 + .../InformationsOfQuiz/InformationsOfQuiz.tsx | 98 +++++++++++++++++++ templates/TemplateQuizList.tsx | 2 +- 4 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 screens/InformationsOfQuiz/InformationsOfQuiz.tsx diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index 085b34a..13cc1e4 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -1,6 +1,7 @@ import React from "react"; import { FlatList, View, Text, StyleSheet, TouchableOpacity } from "react-native"; import QuizModel from "../../models/QuizModel"; +import {NavigationProp} from "@react-navigation/native"; const quizzes: QuizModel[] = [ { @@ -49,11 +50,15 @@ const quizzes: QuizModel[] = [ * @returns Un composant React contenant une liste interactive de quiz. */ interface Props{ + navigation: NavigationProp<any>; quizList: QuizModel[] | null } -export default function QuizList({quizList}: Props) { +export default function QuizList({navigation,quizList}: Props) { + const onQuizPressed = (quiz: QuizModel) => { + navigation.navigate('InformationsOfQuiz', {quiz: quiz}); + }; const renderQuizItem = ({ item }: { item: QuizModel }) => ( - <TouchableOpacity style={styles.quizItem}> + <TouchableOpacity style={styles.quizItem} onPress={() => onQuizPressed(item)}> <Text style={styles.quizTitle}>{item.code}</Text> <Text style={styles.quizDetails}> {item.nbQuestions} QUESTIONS | {item.category.toUpperCase()} diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index c34183c..e2c2b96 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -7,6 +7,7 @@ import PlayingQuiz from "../screens/PlayingQuiz/PlayingQuiz"; import TabNavigator from "./TabNavigation"; import PlayQuiz from "../screens/PlayQuiz/PlayQuiz"; import MyQuizzes from "../screens/MyQuizzes/MyQuizzes"; +import InformationsOfQuiz from "../screens/InformationsOfQuiz/InformationsOfQuiz"; const Stack = createNativeStackNavigator(); @@ -21,6 +22,7 @@ export default function StackNavigator() { <Stack.Screen name="PlayQuiz" component={PlayQuiz} options={{ headerShown: false }}/> <Stack.Screen name="EndQuiz" component={EndQuiz} options={{ headerShown: false }}/> <Stack.Screen name="MyQuizzes" component={MyQuizzes} options={{ headerShown: false }}/> + <Stack.Screen name="InformationsOfQuiz" component={InformationsOfQuiz} options={{ headerShown: false }}/> </Stack.Navigator> </NavigationContainer> ); diff --git a/screens/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/InformationsOfQuiz/InformationsOfQuiz.tsx new file mode 100644 index 0000000..2fdfb96 --- /dev/null +++ b/screens/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -0,0 +1,98 @@ +import React from "react"; +import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import TemplateMenu from "../../templates/TemplateMenu"; +import {NavigationProp, RouteProp} from "@react-navigation/native"; +import QuizModel from "../../models/QuizModel"; + +type RoutePropsType = { + InformationsOfQuiz: { + quiz: QuizModel; + }; +}; +interface Props { + navigation: NavigationProp<any>; + route: RouteProp<RoutePropsType, "InformationsOfQuiz">; +} + +export default function InformationsOfQuiz({ navigation, route }: Props) { + const { quiz } = route.params; + + const onPlay = () => { + // navigation.navigate('PlayQuiz', {quiz: quiz}); + } + + return ( + <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> + <View style={styles.container}> + <Text style={styles.quizTitle}>{quiz.category}</Text> + <Text style={styles.quizDescription}>{quiz.category}</Text> + + <View style={styles.aboutContainer}> + <Text style={styles.aboutTitle}>About this quiz :</Text> + <Text style={styles.aboutDetails}>{quiz.nbQuestions} questions</Text> + <Text style={styles.aboutDetails}>By : Unknown</Text> + </View> + + <TouchableOpacity style={styles.playButton} onPress={onPlay}> + <Text style={styles.playButtonText}>PLAY</Text> + </TouchableOpacity> + </View> + </TemplateMenu> + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingHorizontal: 20, + paddingVertical: 20, + justifyContent: "flex-start", + backgroundColor: "#f4f4f4", + }, + quizTitle: { + fontSize: 24, + fontWeight: "bold", + color: "#00b3f4", + marginBottom: 10, + }, + quizDescription: { + fontSize: 16, + color: "#555", + marginBottom: 20, + }, + aboutContainer: { + backgroundColor: "white", + borderRadius: 10, + padding: 15, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 3, + elevation: 5, + }, + aboutTitle: { + fontSize: 18, + fontWeight: "bold", + marginBottom: 10, + color: "#000", + }, + aboutDetails: { + fontSize: 16, + color: "#555", + marginBottom: 5, + }, + playButton: { + backgroundColor: "#007BFF", + paddingVertical: 15, + borderRadius: 10, + alignItems: "center", + justifyContent: "center", + marginTop: 20, + }, + playButtonText: { + fontSize: 18, + fontWeight: "bold", + color: "white", + }, +}); diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index 5c6eda4..5abce9e 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -31,7 +31,7 @@ export default function TemplateQuizList({navigation, title, setTextInInput, qui <Text style={styles.title}>{title}</Text> <View style={styles.contentContainer}> <InputPlayQuiz setText={setTextInInput} noTitle={true} /> - <QuizList quizList={quizList}/> + <QuizList quizList={quizList} navigation={navigation}/> </View> </View> </TemplateMenu> -- GitLab From 14572aaae516b764b97d5696a66e3c7656da2b2f Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Tue, 3 Dec 2024 22:41:33 +0100 Subject: [PATCH 051/283] =?UTF-8?q?add:=20d=C3=A9but=20de=20la=20modal=20l?= =?UTF-8?q?oginsignup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- screens/Profil/ProfilChild.tsx | 5 ++- screens/Profil/ProfilModal.tsx | 72 ++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 screens/Profil/ProfilModal.tsx diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index 5bcad82..c74af25 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -1,5 +1,7 @@ import { NavigationProp } from "@react-navigation/native"; -import { View, StyleSheet, Image, Text } from "react-native"; +import { View, StyleSheet, Image, Text, Modal, Button } from "react-native"; +import ProfilModal from "./ProfilModal"; +import React, { useState } from "react"; interface Props { navigation: NavigationProp<any>; @@ -18,6 +20,7 @@ export default function ProfilChild({navigation}: Props) { <Text style={styles.statsText}>right answer rate : _43%_</Text> <Text style={styles.statsText}>Favorit category : _HISTORY AND CULTURE_</Text> </View> + <ProfilModal/> </View> ) } diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/ProfilModal.tsx new file mode 100644 index 0000000..a94b70d --- /dev/null +++ b/screens/Profil/ProfilModal.tsx @@ -0,0 +1,72 @@ +import React, { useState } from "react"; +import { Modal, View, StyleSheet, Button, TextInput } from "react-native"; + +export default function ProfilModal() { + const [modalVisible, setModalVisible] = useState(true); + return ( + <Modal + animationType="slide" + transparent={true} + visible={modalVisible} + onRequestClose={() => { + setModalVisible(!modalVisible); + }}> + <View style={styles.container}> + <View style={styles.backgroundShadow}/> + <View style={styles.containerModal}> + <View style={styles.containerNav}> + <Button title="LOGIN"></Button> + <Button title="SIGNUP"></Button> + </View> + <View> + <TextInput placeholder="EMAIL..." style={styles.textInput}/> + <TextInput placeholder="PASSWORD..." style={styles.textInput}/> + <TextInput placeholder="REPEAT PASSWORD..." style={styles.textInput}/> + </View> + <Button title="SIGNUP"/> + </View> + </View> + </Modal> + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + height: '100%', + width: '100%', + }, + backgroundShadow: { + height: '100%', + width: '100%', + backgroundColor: '#000000', + opacity: 0.55, + position: 'absolute', + }, + containerModal: { + height: '90%', + width: '90%', + backgroundColor: '#D9D9D9', + borderRadius: 40, + padding: 30 + }, + containerNav: { + height: '20%', + width: '90%', + padding: '10%', + backgroundColor: '#FFFFFF', + borderRadius: 10, + display: 'flex', + flexDirection: 'row' + }, + textInput: { + height: '20%', + width: '90%', + backgroundColor: '#FFFFFF', + borderWidth: 4, + borderColor: '#1FA9FF', + borderRadius: 10 + } + }); \ No newline at end of file -- GitLab From ecc5161f912f49f2f1273a9cef867667dac4b9b6 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 09:40:09 +0100 Subject: [PATCH 052/283] add: style modal signup --- screens/Profil/ProfilModal.tsx | 89 +++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 17 deletions(-) diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/ProfilModal.tsx index a94b70d..0661c1a 100644 --- a/screens/Profil/ProfilModal.tsx +++ b/screens/Profil/ProfilModal.tsx @@ -1,8 +1,23 @@ import React, { useState } from "react"; import { Modal, View, StyleSheet, Button, TextInput } from "react-native"; +import DefaultButton from "../../components/DefaultButton"; export default function ProfilModal() { const [modalVisible, setModalVisible] = useState(true); + + const handleLoginPressed = () => { + + }; + + const handleSignupPressed = () => { + + }; + + const handleSubmitPressed = () => { + + } + + return ( <Modal animationType="slide" @@ -15,15 +30,15 @@ export default function ProfilModal() { <View style={styles.backgroundShadow}/> <View style={styles.containerModal}> <View style={styles.containerNav}> - <Button title="LOGIN"></Button> - <Button title="SIGNUP"></Button> + <DefaultButton text="LOGIN" handleButtonPressed={handleLoginPressed} buttonStyle={styles.navButton} buttonText={styles.navButtonText}/> + <DefaultButton text="SIGNUP" handleButtonPressed={handleSignupPressed} buttonStyle={styles.navButton} buttonText={styles.navButtonText}/> </View> - <View> + <View style={styles.containerForm}> <TextInput placeholder="EMAIL..." style={styles.textInput}/> <TextInput placeholder="PASSWORD..." style={styles.textInput}/> <TextInput placeholder="REPEAT PASSWORD..." style={styles.textInput}/> </View> - <Button title="SIGNUP"/> + <DefaultButton text="SIGNUP" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> </View> </View> </Modal> @@ -46,27 +61,67 @@ const styles = StyleSheet.create({ position: 'absolute', }, containerModal: { - height: '90%', + height: 680, width: '90%', - backgroundColor: '#D9D9D9', - borderRadius: 40, - padding: 30 + backgroundColor: '#D9D9D9', + borderRadius: 40, + padding: 30 }, containerNav: { - height: '20%', - width: '90%', - padding: '10%', + height: 80, + width: '100%', + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-around', + alignItems: 'center', backgroundColor: '#FFFFFF', borderRadius: 10, + shadowColor: "#000", + shadowOffset: { + width: 0, + height: 3, + }, + shadowOpacity: 0.29, + shadowRadius: 4.65, + elevation: 7, + }, + navButton: { + height: 50, + width: '45%', + backgroundColor: '#FFFFFF', + borderBottomColor: '#B9B9B9', + borderBottomWidth: 3, + borderStyle: 'solid', + }, + navButtonText: { + color: '#B9B9B9' + }, + containerForm: { + height: 450, + width: '100%', display: 'flex', - flexDirection: 'row' + flexDirection: 'column', + justifyContent: 'space-around' }, textInput: { - height: '20%', - width: '90%', + height: 80, + width: '100%', backgroundColor: '#FFFFFF', borderWidth: 4, borderColor: '#1FA9FF', - borderRadius: 10 - } - }); \ No newline at end of file + borderRadius: 10, + color: '#DFDFDF', + fontWeight: 'bold' + }, + SubmitButton: { + height: 80, + width: '100%', + backgroundColor: '#1FA9FF', + borderRadius: 10, + }, + SubmitText: { + fontSize: 20, + fontWeight: 'bold', + color: '#FFFFFF', + }, +}); \ No newline at end of file -- GitLab From 94c10697359f15ae155a1904758f0a08e6039aac Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 09:56:05 +0100 Subject: [PATCH 053/283] feat: PlayingQuiz UX refait --- assets/ImageJoinQuiz.png | Bin 58320 -> 0 bytes .../{PlayButton.tsx => BlueButton.tsx} | 5 ++- routes/StackNavigator.tsx | 2 +- screens/Home/HomeChild.tsx | 5 ++- screens/PlayQuiz/PlayQuizChild.tsx | 2 +- screens/PlayQuiz/PlayQuizGenerateQuiz.tsx | 4 +- screens/PlayQuiz/PlayQuizJoinQuiz.tsx | 4 +- screens/PlayingQuiz/PlayingQuizBody.tsx | 35 ++++++++++----- screens/PlayingQuiz/PlayingQuizHeader.tsx | 41 +++++++++++++----- styles/ButtonsStyles.ts | 2 +- styles/TextsStyles.ts | 4 +- templates/TemplateDuo.tsx | 22 +++++----- 12 files changed, 82 insertions(+), 44 deletions(-) delete mode 100644 assets/ImageJoinQuiz.png rename components/PlayQuiz/{PlayButton.tsx => BlueButton.tsx} (77%) diff --git a/assets/ImageJoinQuiz.png b/assets/ImageJoinQuiz.png deleted file mode 100644 index 1f83b59b59b5aab112be427fcf1e1f76e914a316..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58320 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 B--G}F diff --git a/components/PlayQuiz/PlayButton.tsx b/components/PlayQuiz/BlueButton.tsx similarity index 77% rename from components/PlayQuiz/PlayButton.tsx rename to components/PlayQuiz/BlueButton.tsx index 2a68248..4e04f8d 100644 --- a/components/PlayQuiz/PlayButton.tsx +++ b/components/PlayQuiz/BlueButton.tsx @@ -4,10 +4,11 @@ import {NavigationProp} from "@react-navigation/native"; interface Props { onPress: () => void text: string + buttonStyle?: any } -export default function PlayButton({onPress, text} : Props) { +export default function BlueButton({onPress, text, buttonStyle} : Props) { return ( - <TouchableOpacity style={styles.container} onPress={onPress}> + <TouchableOpacity style={[styles.container, buttonStyle]} onPress={onPress}> <Text style={styles.text}>{text}</Text> </TouchableOpacity> ); diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index e2c2b96..cbfdc43 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -17,7 +17,7 @@ export default function StackNavigator() { <Stack.Navigator initialRouteName="TabNavigator"> <Stack.Screen name="TabNavigator" component={TabNavigator} options={{ headerShown: false }}/> <Stack.Screen name="Home" component={Home} options={{ headerShown: false }}/> - <Stack.Screen name="CreateQuiz" component={GenerateQuiz} options={{ headerShown: false }}/> + <Stack.Screen name="GenerateQuiz" component={GenerateQuiz} options={{ headerShown: false }}/> <Stack.Screen name="PlayingQuiz" component={PlayingQuiz} options={{ headerShown: false }}/> <Stack.Screen name="PlayQuiz" component={PlayQuiz} options={{ headerShown: false }}/> <Stack.Screen name="EndQuiz" component={EndQuiz} options={{ headerShown: false }}/> diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 9589ac5..cc792b7 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -19,6 +19,9 @@ export default function HomeChild({navigation}: Props) { const [modalVisible, setModalVisible] = useState(false); const {t} = useTranslation(); + const handleButtonQuickGamePressed = () => { + navigation.navigate("GenerateQuiz"); + } const handleButtonMyQuizzesPressed = () => { navigation.navigate("MyQuizzes"); } @@ -33,7 +36,7 @@ export default function HomeChild({navigation}: Props) { <Image source={require('../../assets/TitleApp.png')} style={styles.image} /> </View> <View style={styles.buttonContainer}> - <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonMyQuizzesPressed}/> + <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonQuickGamePressed}/> <MenuButton text={"PLAY A QUIZ"} handleButtonPressed={handleButtonGeneratePressed}/> <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/> </View> diff --git a/screens/PlayQuiz/PlayQuizChild.tsx b/screens/PlayQuiz/PlayQuizChild.tsx index ffd4f08..6f2089c 100644 --- a/screens/PlayQuiz/PlayQuizChild.tsx +++ b/screens/PlayQuiz/PlayQuizChild.tsx @@ -5,7 +5,7 @@ import React, {useState} from "react"; import {SelectModeType} from "../../models/SelectModeType"; import SelectListBox from "../../components/PlayQuiz/SelectListBox"; import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; -import PlayButton from "../../components/PlayQuiz/PlayButton"; +import BlueButton from "../../components/PlayQuiz/BlueButton"; import PlayQuizGenerateQuiz from "./PlayQuizGenerateQuiz"; import PlayQuizJoinQuiz from "./PlayQuizJoinQuiz"; diff --git a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx index 438202c..7a2d380 100644 --- a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx +++ b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx @@ -2,7 +2,7 @@ import { Text,View, StyleSheet } from "react-native"; import SelectListBox from "../../components/PlayQuiz/SelectListBox"; import React from "react"; import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; -import PlayButton from "../../components/PlayQuiz/PlayButton"; +import BlueButton from "../../components/PlayQuiz/BlueButton"; const difficulty = [ { key: "easy", value: "Easy" }, @@ -44,7 +44,7 @@ export default function PlayQuizGenerateQuiz() { <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={() => {}} /> </View> <View style={styles.buttonPlayContainer}> - <PlayButton onPress={handlePlayButtonPress} text={"PLAY"} /> + <BlueButton onPress={handlePlayButtonPress} text={"PLAY"} /> </View> </View> </View> diff --git a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx index b5117f2..93f5310 100644 --- a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx +++ b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx @@ -1,6 +1,6 @@ import {StyleSheet, View} from "react-native"; import AboutAQuiz from "../../components/PlayQuiz/AboutAQuiz"; -import PlayButton from "../../components/PlayQuiz/PlayButton"; +import BlueButton from "../../components/PlayQuiz/BlueButton"; import React from "react"; import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; import QuizModel from "../../models/QuizModel"; @@ -20,7 +20,7 @@ export default function PlayQuizJoinQuiz() { <AboutAQuiz /> </View> <View style={styles.buttonPlayContainer}> - <PlayButton onPress={handlePlayButtonPress} text={"PLAY"} /> + <BlueButton onPress={handlePlayButtonPress} text={"PLAY"} /> </View> </View> </View> diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 44afa35..1b2c895 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -9,6 +9,7 @@ import QuestionModel from "../../models/QuestionModel"; import AnswerModel from "../../models/AnswerModel"; import {NavigationProp} from "@react-navigation/native"; import HttpError from "../../services/HttpError"; +import BlueButton from "../../components/PlayQuiz/BlueButton"; interface Props { quiz: QuizModel; @@ -104,22 +105,28 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA return ( <View style={styles.buttonContainer}> {currentQuestion ? ( - currentQuestion.answers.map((answer, index) => ( - <DefaultButton - key={index} - text={answer.text} - handleButtonPressed={() => handleAnswerPress(index)} - buttonStyle={getButtonStyle(index)} - buttonText={getTextStyle(index)} - /> - )) + <> + {currentQuestion.answers.map((answer, index) => ( + <DefaultButton + key={index} + text={answer.text} + handleButtonPressed={() => handleAnswerPress(index)} + buttonStyle={getButtonStyle(index)} + buttonText={getTextStyle(index)} + /> + ))} + {/* Ajout du PlayButton à la fin */} + <View style={styles.playButtonContainer}> + <BlueButton onPress={() => console.log("Play button pressed")} text="Play" buttonStyle={{borderRadius: 50}}/> + </View> + </> ) : ( <Text style={styles.loadingText}>Loading...</Text> )} </View> ); -} +} const styles = StyleSheet.create({ buttonContainer: { display: 'flex', @@ -127,7 +134,12 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'flex-start', width: '100%', - marginVertical: '10%', + marginVertical: '3%', + }, + playButtonContainer: { + marginTop: 20, // Ajoute un espacement au-dessus du bouton Play + width: '80%', // S'assure que le bouton prend toute la largeur disponible + alignItems: 'center', // Centre le bouton horizontalement }, correctAnswer: { ...ButtonsStyles.answerButton, @@ -153,3 +165,4 @@ const styles = StyleSheet.create({ textAlign: 'center', }, }); + diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index 0b0b803..a04bded 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -5,6 +5,7 @@ import {useTranslation} from "react-i18next"; import DefaultButton from "../../components/DefaultButton"; interface Props { + quiz: QuizModel; } @@ -18,15 +19,17 @@ export default function PlayingQuizHeader({quiz}: Props) { return ( <View style={styles.container}> <View> - <DefaultButton text="<" handleButtonPressed={handleButtonBackToHomePressed}/> - <Text>ID QUIZZ : 7</Text> - </View> - <View style={styles.infoQuizContainer}> - <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.category")} : {quiz.questions[quiz.nbActualQuestion-1].category ? quiz.questions[quiz.nbActualQuestion-1].category : "Loading..."}</Text> - <Text style={TextsStyles.infoQuizText}>{t("app.screens.question.score")} : {quiz ? quiz.score : 0}</Text> + <Text style={styles.codeQuizText}>ID QUIZZ : {quiz ? quiz.code : "?"}</Text> </View> - <View style={styles.QuizNumContainer}> - <Text style={TextsStyles.titleText}>{t("app.screens.question.question")} {quiz ? quiz.nbActualQuestion : "?"}/{quiz ? quiz.nbQuestions : "?"}</Text> + <View style={styles.questionAndScoreContainer}> + <View style={styles.InformationsContainer}> + <Text style={TextsStyles.titleText}>{t("app.screens.question.question")}</Text> + <Text style={TextsStyles.titleText}>{quiz ? quiz.nbActualQuestion : "?"}/{quiz ? quiz.nbQuestions : "?"}</Text> + </View> + <View style={styles.InformationsContainer}> + <Text style={TextsStyles.titleText}>{t("app.screens.question.score")}</Text> + <Text style={TextsStyles.titleText}>{quiz ? quiz.score : 0}/{quiz ? quiz.nbQuestions : "?"}</Text> + </View> </View> <View style={styles.QuizQuestionContainer}> <Text style={TextsStyles.subtitleText}>{quiz.questions[quiz.nbActualQuestion-1].question ? quiz.questions[quiz.nbActualQuestion-1].question : "Loading..."}</Text> @@ -37,11 +40,23 @@ export default function PlayingQuizHeader({quiz}: Props) { const styles = StyleSheet.create({ container: { - marginVertical: "10%", - marginHorizontal: "5%", + marginHorizontal: '5%', + }, + questionAndScoreContainer: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-around', + alignItems: 'center', + marginVertical: '2%', + }, + InformationsContainer: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + gap: '5%', }, QuizNumContainer: { - marginVertical: "2.5%", }, QuizQuestionContainer: { marginVertical: "2.5%", @@ -49,4 +64,8 @@ const styles = StyleSheet.create({ infoQuizContainer: { marginBottom: '5%', }, + codeQuizText: { + fontSize: 18, + color: '#000000', + }, }); \ No newline at end of file diff --git a/styles/ButtonsStyles.ts b/styles/ButtonsStyles.ts index ebe03d7..4f56435 100644 --- a/styles/ButtonsStyles.ts +++ b/styles/ButtonsStyles.ts @@ -9,7 +9,7 @@ export const ButtonsStyles = StyleSheet.create( { }, answerButton: { backgroundColor: "#F3F3F3", - height: '20%', + height: '15%', alignItems: 'center', width: '80%', marginVertical: '3%', diff --git a/styles/TextsStyles.ts b/styles/TextsStyles.ts index a6bdfb9..1f80ebd 100644 --- a/styles/TextsStyles.ts +++ b/styles/TextsStyles.ts @@ -2,11 +2,11 @@ import { StyleSheet } from "react-native"; export const TextsStyles = StyleSheet.create( { defaultText: { color: 'black', - fontSize: 20, + fontSize: 17, }, defaultButtonText: { color: "white", - fontSize: 20, + fontSize: 17, }, titleText: { fontSize: 24, diff --git a/templates/TemplateDuo.tsx b/templates/TemplateDuo.tsx index 786c5ec..97a225e 100644 --- a/templates/TemplateDuo.tsx +++ b/templates/TemplateDuo.tsx @@ -1,4 +1,4 @@ -import { ImageBackground, Text, View, StyleSheet } from "react-native"; +import {ImageBackground, Text, View, StyleSheet, SafeAreaView} from "react-native"; interface Props{ childrenHeader?: React.ReactNode @@ -8,13 +8,15 @@ interface Props{ export default function TemplateDuo({childrenHeader, childrenBody}: Props) { return ( <View style={styles.container}> - <ImageBackground source={require('../assets/FondDuo.png')} style={styles.image}> - <View style={styles.containerHeader}> - {childrenHeader} - </View> - <View style={styles.containerBody}> - {childrenBody} - </View> + <ImageBackground source={require('../assets/BackgroundQuestion.png')} style={styles.image}> + <SafeAreaView> + <View style={styles.containerHeader}> + {childrenHeader} + </View> + <View style={styles.containerBody}> + {childrenBody} + </View> + </SafeAreaView> </ImageBackground> </View> ); @@ -29,11 +31,11 @@ const styles = StyleSheet.create({ }, containerHeader: { width: '100%', - height: '30%', + height: '26%', }, containerBody: { width: '100%', - height: '70%', + height: '78%', backgroundColor: '#FFFFFF', borderTopLeftRadius: 46, borderTopRightRadius: 46 -- GitLab From dde2a789e49f341588210ac3f0359e1481008ab2 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 10:00:09 +0100 Subject: [PATCH 054/283] =?UTF-8?q?add:=20header=20pour=20cr=C3=A9er=20un?= =?UTF-8?q?=20compte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- screens/Profil/ProfilChild.tsx | 39 +++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index c74af25..05672f8 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -1,5 +1,5 @@ import { NavigationProp } from "@react-navigation/native"; -import { View, StyleSheet, Image, Text, Modal, Button } from "react-native"; +import { View, StyleSheet, Image, Text, Modal, Button, TouchableOpacity } from "react-native"; import ProfilModal from "./ProfilModal"; import React, { useState } from "react"; @@ -10,6 +10,7 @@ interface Props { export default function ProfilChild({navigation}: Props) { return ( <View style={styles.containerGlobal}> + <View style={styles.containerPlayerInfos}> <Image source={require('../../assets/TitleApp.png')} style={styles.titleImage}/> <Image source={require('../../assets/ProfilBaseImage.png')} style={styles.profilImage}/> @@ -20,6 +21,22 @@ export default function ProfilChild({navigation}: Props) { <Text style={styles.statsText}>right answer rate : _43%_</Text> <Text style={styles.statsText}>Favorit category : _HISTORY AND CULTURE_</Text> </View> + + + <View style={styles.containerHeader}> + <Text style={styles.textHeader}> + <TouchableOpacity onPress={() => handlePress('bouton 1')}> + <Text style={styles.buttonHeader}>LOGIN</Text> + </TouchableOpacity> + {' OR '} + <TouchableOpacity onPress={() => handlePress('bouton 2')}> + <Text style={styles.buttonHeader}>CREATE AN ACCOUNT</Text> + </TouchableOpacity> + </Text> + </View> + + + <ProfilModal/> </View> ) @@ -61,4 +78,24 @@ const styles = StyleSheet.create({ fontSize: 17, fontWeight: 'bold', }, + containerHeader: { + backgroundColor: '#D9D9D9', + height: '10%', + width: '100%', + position: 'absolute', + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }, + textHeader: { + color: '#000000', + fontSize: 15, + fontWeight: 'bold', + }, + buttonHeader: { + textDecorationLine: 'underline', + color: '#000000', + fontSize: 15, + fontWeight: 'bold', + } }); \ No newline at end of file -- GitLab From 2db7d5baba58670c0f328293d7a1f0cbd5911ab2 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 10:06:34 +0100 Subject: [PATCH 055/283] add: gestion de l'affichage de la modal --- screens/Profil/ProfilChild.tsx | 12 +++++++++--- screens/Profil/ProfilModal.tsx | 9 +++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index 05672f8..0e56187 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -8,6 +8,12 @@ interface Props { } export default function ProfilChild({navigation}: Props) { + const [modalVisible, setModalVisible] = useState(false); + + const handleAccountPress = (IsLogin:Boolean) => { + setModalVisible(true); + }; + return ( <View style={styles.containerGlobal}> @@ -25,11 +31,11 @@ export default function ProfilChild({navigation}: Props) { <View style={styles.containerHeader}> <Text style={styles.textHeader}> - <TouchableOpacity onPress={() => handlePress('bouton 1')}> + <TouchableOpacity onPress={() => handleAccountPress(true)}> <Text style={styles.buttonHeader}>LOGIN</Text> </TouchableOpacity> {' OR '} - <TouchableOpacity onPress={() => handlePress('bouton 2')}> + <TouchableOpacity onPress={() => handleAccountPress(false)}> <Text style={styles.buttonHeader}>CREATE AN ACCOUNT</Text> </TouchableOpacity> </Text> @@ -37,7 +43,7 @@ export default function ProfilChild({navigation}: Props) { - <ProfilModal/> + <ProfilModal modalVisible={modalVisible} setModalVisible={setModalVisible}/> </View> ) } diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/ProfilModal.tsx index 0661c1a..418c0a7 100644 --- a/screens/Profil/ProfilModal.tsx +++ b/screens/Profil/ProfilModal.tsx @@ -2,8 +2,13 @@ import React, { useState } from "react"; import { Modal, View, StyleSheet, Button, TextInput } from "react-native"; import DefaultButton from "../../components/DefaultButton"; -export default function ProfilModal() { - const [modalVisible, setModalVisible] = useState(true); +interface Props{ + modalVisible: boolean; + setModalVisible: (visible: boolean) => void; +} + +export default function ProfilModal({modalVisible ,setModalVisible}: Props) { + const handleLoginPressed = () => { -- GitLab From eab8795090b654662d85f834b290184a683e6606 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 10:11:38 +0100 Subject: [PATCH 056/283] refactor: creation d'un composant profilheaderaccount --- screens/Profil/ProfilChild.tsx | 47 ++++-------------------- screens/Profil/ProfilHeaderAccount.tsx | 49 ++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 41 deletions(-) create mode 100644 screens/Profil/ProfilHeaderAccount.tsx diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index 0e56187..1916f1f 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -2,6 +2,7 @@ import { NavigationProp } from "@react-navigation/native"; import { View, StyleSheet, Image, Text, Modal, Button, TouchableOpacity } from "react-native"; import ProfilModal from "./ProfilModal"; import React, { useState } from "react"; +import ProfilHeaderAccount from "./ProfilHeaderAccount"; interface Props { navigation: NavigationProp<any>; @@ -10,13 +11,10 @@ interface Props { export default function ProfilChild({navigation}: Props) { const [modalVisible, setModalVisible] = useState(false); - const handleAccountPress = (IsLogin:Boolean) => { - setModalVisible(true); - }; + return ( <View style={styles.containerGlobal}> - <View style={styles.containerPlayerInfos}> <Image source={require('../../assets/TitleApp.png')} style={styles.titleImage}/> <Image source={require('../../assets/ProfilBaseImage.png')} style={styles.profilImage}/> @@ -28,21 +26,7 @@ export default function ProfilChild({navigation}: Props) { <Text style={styles.statsText}>Favorit category : _HISTORY AND CULTURE_</Text> </View> - - <View style={styles.containerHeader}> - <Text style={styles.textHeader}> - <TouchableOpacity onPress={() => handleAccountPress(true)}> - <Text style={styles.buttonHeader}>LOGIN</Text> - </TouchableOpacity> - {' OR '} - <TouchableOpacity onPress={() => handleAccountPress(false)}> - <Text style={styles.buttonHeader}>CREATE AN ACCOUNT</Text> - </TouchableOpacity> - </Text> - </View> - - - + <ProfilHeaderAccount setModalVisible={setModalVisible}/> <ProfilModal modalVisible={modalVisible} setModalVisible={setModalVisible}/> </View> ) @@ -62,9 +46,9 @@ const styles = StyleSheet.create({ alignItems: 'center', }, titleImage: { - width: 170, // Ajustez en fonction de la taille de l'image + width: 170, height: 80, - resizeMode: 'contain', // Pour conserver les proportions + resizeMode: 'contain', }, profilImage: { height: 200, @@ -84,24 +68,5 @@ const styles = StyleSheet.create({ fontSize: 17, fontWeight: 'bold', }, - containerHeader: { - backgroundColor: '#D9D9D9', - height: '10%', - width: '100%', - position: 'absolute', - display: 'flex', - justifyContent: 'center', - alignItems: 'center' - }, - textHeader: { - color: '#000000', - fontSize: 15, - fontWeight: 'bold', - }, - buttonHeader: { - textDecorationLine: 'underline', - color: '#000000', - fontSize: 15, - fontWeight: 'bold', - } + }); \ No newline at end of file diff --git a/screens/Profil/ProfilHeaderAccount.tsx b/screens/Profil/ProfilHeaderAccount.tsx new file mode 100644 index 0000000..c508ce9 --- /dev/null +++ b/screens/Profil/ProfilHeaderAccount.tsx @@ -0,0 +1,49 @@ +import { View, Text, TouchableOpacity, StyleSheet } from "react-native"; + +interface Props { + setModalVisible: (visible:boolean) => void; +} + +export default function ProfilHeaderAccount({ setModalVisible }: Props) { + const handleAccountPress = (IsLogin:Boolean) => { + setModalVisible(true); + }; + + return ( + <View style={styles.containerHeader}> + <Text style={styles.textHeader}> + <TouchableOpacity onPress={() => handleAccountPress(true)}> + <Text style={styles.buttonHeader}>LOGIN</Text> + </TouchableOpacity> + {' OR '} + <TouchableOpacity onPress={() => handleAccountPress(false)}> + <Text style={styles.buttonHeader}>CREATE AN ACCOUNT</Text> + </TouchableOpacity> + </Text> + </View> + ) +} + + +const styles = StyleSheet.create({ + containerHeader: { + backgroundColor: '#D9D9D9', + height: '10%', + width: '100%', + position: 'absolute', + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }, + textHeader: { + color: '#000000', + fontSize: 15, + fontWeight: 'bold', + }, + buttonHeader: { + textDecorationLine: 'underline', + color: '#000000', + fontSize: 15, + fontWeight: 'bold', + }, +}); \ No newline at end of file -- GitLab From 1ac49130e82c4327748851b7a68e3b3ebb925d3b Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 11:14:28 +0100 Subject: [PATCH 057/283] add: utilisation du composat selectmode pour la modal profil --- models/SelectModeType.ts | 3 +- screens/Profil/ProfilModal.tsx | 19 ++++++-- screens/Profil/ProfilSelectMode.tsx | 74 +++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 screens/Profil/ProfilSelectMode.tsx diff --git a/models/SelectModeType.ts b/models/SelectModeType.ts index b664180..8c155fa 100644 --- a/models/SelectModeType.ts +++ b/models/SelectModeType.ts @@ -1 +1,2 @@ -export type SelectModeType = "play" | "join"; \ No newline at end of file +export type SelectModeType = "play" | "join"; +export type ProfilSelectModeType = "login" | "signup"; \ No newline at end of file diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/ProfilModal.tsx index 418c0a7..bee2b5f 100644 --- a/screens/Profil/ProfilModal.tsx +++ b/screens/Profil/ProfilModal.tsx @@ -1,6 +1,9 @@ import React, { useState } from "react"; import { Modal, View, StyleSheet, Button, TextInput } from "react-native"; -import DefaultButton from "../../components/DefaultButton"; +import ProfilSelectMode from "./ProfilSelectMode"; +import { ProfilSelectModeType } from "../../models/SelectModeType"; +import ProfilModalLogin from "./ProfilModalLogin"; +import ProfilModalSignup from "./ProfilModalSignup"; interface Props{ modalVisible: boolean; @@ -8,7 +11,7 @@ interface Props{ } export default function ProfilModal({modalVisible ,setModalVisible}: Props) { - + const [mode, setMode] = useState<ProfilSelectModeType>("login"); const handleLoginPressed = () => { @@ -34,7 +37,13 @@ export default function ProfilModal({modalVisible ,setModalVisible}: Props) { <View style={styles.container}> <View style={styles.backgroundShadow}/> <View style={styles.containerModal}> - <View style={styles.containerNav}> + <ProfilSelectMode mode={mode} setMode={setMode}/> + {mode === "login" ? ( + <ProfilModalLogin /> + ) : ( + <ProfilModalSignup /> + )} + {/* <View style={styles.containerNav}> <DefaultButton text="LOGIN" handleButtonPressed={handleLoginPressed} buttonStyle={styles.navButton} buttonText={styles.navButtonText}/> <DefaultButton text="SIGNUP" handleButtonPressed={handleSignupPressed} buttonStyle={styles.navButton} buttonText={styles.navButtonText}/> </View> @@ -43,8 +52,8 @@ export default function ProfilModal({modalVisible ,setModalVisible}: Props) { <TextInput placeholder="PASSWORD..." style={styles.textInput}/> <TextInput placeholder="REPEAT PASSWORD..." style={styles.textInput}/> </View> - <DefaultButton text="SIGNUP" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> - </View> + <DefaultButton text="SIGNUP" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> */} + </View> </View> </Modal> ); diff --git a/screens/Profil/ProfilSelectMode.tsx b/screens/Profil/ProfilSelectMode.tsx new file mode 100644 index 0000000..7a6d0d7 --- /dev/null +++ b/screens/Profil/ProfilSelectMode.tsx @@ -0,0 +1,74 @@ +import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import { ProfilSelectModeType } from "../../models/SelectModeType"; + +interface Props { + mode: ProfilSelectModeType; + setMode: (mode: ProfilSelectModeType) => void; +} + +export default function ProfilSelectMode({ mode, setMode }: Props) { + return ( + <View style={styles.container}> + <View style={styles.buttonContainer}> + <TouchableOpacity onPress={() => setMode("login")} style={{marginLeft: '10%'}}> + <Text style={[styles.text, mode === "login" && styles.activeText]}>LOGIN</Text> + </TouchableOpacity> + <View style={[styles.bar, mode === "login" ? styles.activeBar : styles.inactiveBar]} /> + </View> + + <View style={styles.buttonContainer}> + <TouchableOpacity onPress={() => setMode("signup")} style={{marginRight: '10%'}}> + <Text style={[styles.text, mode === "signup" && styles.activeText]}>SIGNUP</Text> + </TouchableOpacity> + <View style={[styles.bar, mode === "signup" ? styles.activeBar : styles.inactiveBar]} /> + </View> + + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + display: 'flex', + width: '100%', + height: '10%', + flexDirection: 'row', + backgroundColor: '#f3f3f3', + borderRadius: 10, + justifyContent: 'space-around', + alignItems: 'center', + // Ombre pour Android + elevation: 5, + // Ombre pour iOS + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, // Décalage de l'ombre en bas + shadowOpacity: 0.25, // Opacité de l'ombre + shadowRadius: 3.84, // Flou de l'ombre + }, + buttonContainer: { + display: 'flex', + flexDirection: 'column', + width: '40%', + alignItems: 'center', + gap: "5%", + }, + text: { + fontSize: 25, + color: '#bebebe', + }, + activeText: { + color: '#2ec7ff', + }, + bar: { + width: '100%', + height: 4, + marginTop: 4, + borderRadius: 10, + }, + activeBar: { + backgroundColor: '#2ec7ff', // Couleur pour le bouton actif + }, + inactiveBar: { + backgroundColor: '#bebebe', // Couleur grise pour les boutons inactifs + }, +}); -- GitLab From 73f39aaa7567b4878a903b3f0f5daf67a2a0d583 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 11:15:01 +0100 Subject: [PATCH 058/283] refactor: creation de composant pour les soous partie de la modal --- screens/Profil/ProfilModalLogin.tsx | 51 +++++++++++++++++++++++++++ screens/Profil/ProfilModalSignup.tsx | 52 ++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 screens/Profil/ProfilModalLogin.tsx create mode 100644 screens/Profil/ProfilModalSignup.tsx diff --git a/screens/Profil/ProfilModalLogin.tsx b/screens/Profil/ProfilModalLogin.tsx new file mode 100644 index 0000000..d4fbbe8 --- /dev/null +++ b/screens/Profil/ProfilModalLogin.tsx @@ -0,0 +1,51 @@ +import { TextInput, View, StyleSheet } from "react-native"; +import DefaultButton from "../../components/DefaultButton"; + +export default function ProfilModalLogin() { + const handleSubmitPressed = () => { + + } + + return ( + <View> + <View style={styles.containerForm}> + <TextInput placeholder="EMAIL..." style={styles.textInput}/> + <TextInput placeholder="PASSWORD..." style={styles.textInput}/> + </View> + <DefaultButton text="SIGNUP" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> + </View> + ) +} + +const styles = StyleSheet.create({ + containerForm: { + height: 400, + width: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-start', + rowGap: 50, + marginTop: 50, + }, + textInput: { + height: 80, + width: '100%', + backgroundColor: '#FFFFFF', + borderWidth: 4, + borderColor: '#1FA9FF', + borderRadius: 10, + color: '#DFDFDF', + fontWeight: 'bold' + }, + SubmitButton: { + height: 80, + width: '100%', + backgroundColor: '#1FA9FF', + borderRadius: 10, + }, + SubmitText: { + fontSize: 20, + fontWeight: 'bold', + color: '#FFFFFF', + }, +}); \ No newline at end of file diff --git a/screens/Profil/ProfilModalSignup.tsx b/screens/Profil/ProfilModalSignup.tsx new file mode 100644 index 0000000..43fa0f2 --- /dev/null +++ b/screens/Profil/ProfilModalSignup.tsx @@ -0,0 +1,52 @@ +import { TextInput, View, StyleSheet } from "react-native"; +import DefaultButton from "../../components/DefaultButton"; + +export default function ProfilModalSignup() { + const handleSubmitPressed = () => { + + } + + return ( + <View> + <View style={styles.containerForm}> + <TextInput placeholder="EMAIL..." style={styles.textInput}/> + <TextInput placeholder="PASSWORD..." style={styles.textInput}/> + <TextInput placeholder="REPEAT PASSWORD..." style={styles.textInput}/> + </View> + <DefaultButton text="SIGNUP" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> + </View> + ) +} + +const styles = StyleSheet.create({ + containerForm: { + height: 400, + width: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-start', + rowGap: 50, + marginTop: 50 + }, + textInput: { + height: 80, + width: '100%', + backgroundColor: '#FFFFFF', + borderWidth: 4, + borderColor: '#1FA9FF', + borderRadius: 10, + color: '#DFDFDF', + fontWeight: 'bold' + }, + SubmitButton: { + height: 80, + width: '100%', + backgroundColor: '#1FA9FF', + borderRadius: 10, + }, + SubmitText: { + fontSize: 20, + fontWeight: 'bold', + color: '#FFFFFF', + }, +}); \ No newline at end of file -- GitLab From 352b58091c2a270b308855aae21f94f0cff36f7d Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 11:21:27 +0100 Subject: [PATCH 059/283] add: redirection vers la bonne sous page de la modal --- screens/Profil/ProfilChild.tsx | 8 ++++---- screens/Profil/ProfilHeaderAccount.tsx | 11 +++++++---- screens/Profil/ProfilModal.tsx | 16 +++------------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index 1916f1f..f5f6ecb 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -3,6 +3,7 @@ import { View, StyleSheet, Image, Text, Modal, Button, TouchableOpacity } from " import ProfilModal from "./ProfilModal"; import React, { useState } from "react"; import ProfilHeaderAccount from "./ProfilHeaderAccount"; +import { ProfilSelectModeType } from "../../models/SelectModeType"; interface Props { navigation: NavigationProp<any>; @@ -10,8 +11,7 @@ interface Props { export default function ProfilChild({navigation}: Props) { const [modalVisible, setModalVisible] = useState(false); - - + const [mode, setMode] = useState<ProfilSelectModeType>("login"); return ( <View style={styles.containerGlobal}> @@ -26,8 +26,8 @@ export default function ProfilChild({navigation}: Props) { <Text style={styles.statsText}>Favorit category : _HISTORY AND CULTURE_</Text> </View> - <ProfilHeaderAccount setModalVisible={setModalVisible}/> - <ProfilModal modalVisible={modalVisible} setModalVisible={setModalVisible}/> + <ProfilHeaderAccount setModalVisible={setModalVisible} setMode={setMode}/> + <ProfilModal modalVisible={modalVisible} setModalVisible={setModalVisible} mode={mode} setMode={setMode}/> </View> ) } diff --git a/screens/Profil/ProfilHeaderAccount.tsx b/screens/Profil/ProfilHeaderAccount.tsx index c508ce9..c6e4a3d 100644 --- a/screens/Profil/ProfilHeaderAccount.tsx +++ b/screens/Profil/ProfilHeaderAccount.tsx @@ -1,22 +1,25 @@ import { View, Text, TouchableOpacity, StyleSheet } from "react-native"; +import { ProfilSelectModeType } from "../../models/SelectModeType"; interface Props { setModalVisible: (visible:boolean) => void; + setMode: (mode: ProfilSelectModeType) => void; } -export default function ProfilHeaderAccount({ setModalVisible }: Props) { - const handleAccountPress = (IsLogin:Boolean) => { +export default function ProfilHeaderAccount({ setModalVisible, setMode }: Props) { + const handleAccountPress = (mode:ProfilSelectModeType) => { + setMode(mode); setModalVisible(true); }; return ( <View style={styles.containerHeader}> <Text style={styles.textHeader}> - <TouchableOpacity onPress={() => handleAccountPress(true)}> + <TouchableOpacity onPress={() => handleAccountPress('login')}> <Text style={styles.buttonHeader}>LOGIN</Text> </TouchableOpacity> {' OR '} - <TouchableOpacity onPress={() => handleAccountPress(false)}> + <TouchableOpacity onPress={() => handleAccountPress('signup')}> <Text style={styles.buttonHeader}>CREATE AN ACCOUNT</Text> </TouchableOpacity> </Text> diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/ProfilModal.tsx index bee2b5f..4049c6d 100644 --- a/screens/Profil/ProfilModal.tsx +++ b/screens/Profil/ProfilModal.tsx @@ -8,11 +8,11 @@ import ProfilModalSignup from "./ProfilModalSignup"; interface Props{ modalVisible: boolean; setModalVisible: (visible: boolean) => void; + mode: ProfilSelectModeType; + setMode: (mode: ProfilSelectModeType) => void; } -export default function ProfilModal({modalVisible ,setModalVisible}: Props) { - const [mode, setMode] = useState<ProfilSelectModeType>("login"); - +export default function ProfilModal({modalVisible ,setModalVisible, mode, setMode}: Props) { const handleLoginPressed = () => { }; @@ -43,16 +43,6 @@ export default function ProfilModal({modalVisible ,setModalVisible}: Props) { ) : ( <ProfilModalSignup /> )} - {/* <View style={styles.containerNav}> - <DefaultButton text="LOGIN" handleButtonPressed={handleLoginPressed} buttonStyle={styles.navButton} buttonText={styles.navButtonText}/> - <DefaultButton text="SIGNUP" handleButtonPressed={handleSignupPressed} buttonStyle={styles.navButton} buttonText={styles.navButtonText}/> - </View> - <View style={styles.containerForm}> - <TextInput placeholder="EMAIL..." style={styles.textInput}/> - <TextInput placeholder="PASSWORD..." style={styles.textInput}/> - <TextInput placeholder="REPEAT PASSWORD..." style={styles.textInput}/> - </View> - <DefaultButton text="SIGNUP" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> */} </View> </View> </Modal> -- GitLab From 245deeb73620718d36fd5f48eb31668c17b91f43 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 11:37:22 +0100 Subject: [PATCH 060/283] refactor: suppression du code inutile --- package-lock.json | 22 ++++++++++++++++++++++ package.json | 1 + routes/TabNavigation.tsx | 4 ---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82b5a9e..b4a0a5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@babel/core": "^7.20.0", "@types/he": "^1.2.3", "@types/react": "~18.3.12", + "@types/react-native-vector-icons": "^6.4.18", "typescript": "~5.3.3" } }, @@ -4078,6 +4079,27 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-native": { + "version": "0.70.19", + "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.19.tgz", + "integrity": "sha512-c6WbyCgWTBgKKMESj/8b4w+zWcZSsCforson7UdXtXMecG3MxCinYi6ihhrHVPyUrVzORsvEzK8zg32z4pK6Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-native-vector-icons": { + "version": "6.4.18", + "resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.18.tgz", + "integrity": "sha512-YGlNWb+k5laTBHd7+uZowB9DpIK3SXUneZqAiKQaj1jnJCZM0x71GDim5JCTMi4IFkhc9m8H/Gm28T5BjyivUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "@types/react-native": "^0.70" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", diff --git a/package.json b/package.json index 6193974..c8ebea8 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@babel/core": "^7.20.0", "@types/he": "^1.2.3", "@types/react": "~18.3.12", + "@types/react-native-vector-icons": "^6.4.18", "typescript": "~5.3.3" }, "private": true diff --git a/routes/TabNavigation.tsx b/routes/TabNavigation.tsx index 4dc8def..834245c 100644 --- a/routes/TabNavigation.tsx +++ b/routes/TabNavigation.tsx @@ -44,10 +44,6 @@ export default function TabNavigator() { name="Home" component={Home} /> - {/*<Tab.Screen*/} - {/* name="Profil"*/} - {/* component={Profil}*/} - {/*/>*/} <Tab.Screen name="Community" component={Community} -- GitLab From 82ab3618cf6d79972290b5fbda11b1d0ff5287b1 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 11:45:10 +0100 Subject: [PATCH 061/283] fix: alignement texte header --- screens/Profil/ProfilHeaderAccount.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/screens/Profil/ProfilHeaderAccount.tsx b/screens/Profil/ProfilHeaderAccount.tsx index c6e4a3d..6afcc5d 100644 --- a/screens/Profil/ProfilHeaderAccount.tsx +++ b/screens/Profil/ProfilHeaderAccount.tsx @@ -14,15 +14,13 @@ export default function ProfilHeaderAccount({ setModalVisible, setMode }: Props) return ( <View style={styles.containerHeader}> - <Text style={styles.textHeader}> - <TouchableOpacity onPress={() => handleAccountPress('login')}> + <TouchableOpacity onPress={() => handleAccountPress('login')}> <Text style={styles.buttonHeader}>LOGIN</Text> - </TouchableOpacity> - {' OR '} - <TouchableOpacity onPress={() => handleAccountPress('signup')}> + </TouchableOpacity> + <Text style={styles.textHeader}>{' OR '}</Text> + <TouchableOpacity onPress={() => handleAccountPress('signup')}> <Text style={styles.buttonHeader}>CREATE AN ACCOUNT</Text> - </TouchableOpacity> - </Text> + </TouchableOpacity> </View> ) } @@ -35,18 +33,19 @@ const styles = StyleSheet.create({ width: '100%', position: 'absolute', display: 'flex', + flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }, textHeader: { color: '#000000', - fontSize: 15, + fontSize: 20, fontWeight: 'bold', }, buttonHeader: { textDecorationLine: 'underline', color: '#000000', - fontSize: 15, + fontSize: 20, fontWeight: 'bold', }, }); \ No newline at end of file -- GitLab From 8a00ae459599edebe1295a08d8ec9835e8b89c30 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 13:08:34 +0100 Subject: [PATCH 062/283] fix: image --- assets/BackgroundQuestion.png | Bin 0 -> 53750 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/BackgroundQuestion.png diff --git a/assets/BackgroundQuestion.png b/assets/BackgroundQuestion.png new file mode 100644 index 0000000000000000000000000000000000000000..fa5cdaa069e93679016f12bd8b8989e1b15ed607 GIT binary patch literal 53750 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 PObitg3=e7zjLrWK`#i+y literal 0 HcmV?d00001 -- GitLab From 99f9a7eac6531bf8ef8673863c202b28ae572d1d Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 13:55:03 +0100 Subject: [PATCH 063/283] add: ajout du boutton quit modal --- screens/Profil/Profil.tsx | 2 +- screens/Profil/ProfilModal.tsx | 36 ++++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/screens/Profil/Profil.tsx b/screens/Profil/Profil.tsx index a226e78..87201d9 100644 --- a/screens/Profil/Profil.tsx +++ b/screens/Profil/Profil.tsx @@ -10,7 +10,7 @@ interface Props { export default function Profil({navigation}: Props) { return ( <View style={styles.containerGlobal}> - <TemplateMenu navigation={navigation} headerNavigation={false}> + <TemplateMenu navigation={navigation} headerNavigation={false} buttonGoBack> <View> <ProfilChild navigation={navigation}/> </View> diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/ProfilModal.tsx index 4049c6d..f947a97 100644 --- a/screens/Profil/ProfilModal.tsx +++ b/screens/Profil/ProfilModal.tsx @@ -1,9 +1,10 @@ -import React, { useState } from "react"; -import { Modal, View, StyleSheet, Button, TextInput } from "react-native"; +import React from "react"; +import { Modal, View, StyleSheet, Button, TextInput, TouchableOpacity } from "react-native"; import ProfilSelectMode from "./ProfilSelectMode"; import { ProfilSelectModeType } from "../../models/SelectModeType"; import ProfilModalLogin from "./ProfilModalLogin"; import ProfilModalSignup from "./ProfilModalSignup"; +import Icon from "react-native-vector-icons/MaterialIcons"; interface Props{ modalVisible: boolean; @@ -25,6 +26,10 @@ export default function ProfilModal({modalVisible ,setModalVisible, mode, setMod } + const handleBackPressed = () => { + + } + return ( <Modal @@ -35,8 +40,15 @@ export default function ProfilModal({modalVisible ,setModalVisible, mode, setMod setModalVisible(!modalVisible); }}> <View style={styles.container}> + <TouchableOpacity + style={styles.closeButton} + onPress={() => setModalVisible(false)} // Ferme la modal + > + <Icon name="close" size={30} color="#000" /> + </TouchableOpacity> <View style={styles.backgroundShadow}/> <View style={styles.containerModal}> + <ProfilSelectMode mode={mode} setMode={setMode}/> {mode === "login" ? ( <ProfilModalLogin /> @@ -51,11 +63,11 @@ export default function ProfilModal({modalVisible ,setModalVisible, mode, setMod const styles = StyleSheet.create({ container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - height: '100%', - width: '100%', + flex: 1, + justifyContent: 'center', + alignItems: 'center', + height: '100%', + width: '100%', }, backgroundShadow: { height: '100%', @@ -65,7 +77,7 @@ const styles = StyleSheet.create({ position: 'absolute', }, containerModal: { - height: 680, + height: 650, width: '90%', backgroundColor: '#D9D9D9', borderRadius: 40, @@ -128,4 +140,12 @@ const styles = StyleSheet.create({ fontWeight: 'bold', color: '#FFFFFF', }, + closeButton: { + position: "absolute", + top: 10, + right: 10, + zIndex: 1, + backgroundColor: 'white', + borderRadius: 10 + }, }); \ No newline at end of file -- GitLab From 582d207e5edd349f5c25ebfe27b5abc3af93849b Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 13:58:06 +0100 Subject: [PATCH 064/283] add: rename bouton pour login --- screens/Profil/ProfilModalLogin.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/Profil/ProfilModalLogin.tsx b/screens/Profil/ProfilModalLogin.tsx index d4fbbe8..3fd32dd 100644 --- a/screens/Profil/ProfilModalLogin.tsx +++ b/screens/Profil/ProfilModalLogin.tsx @@ -12,7 +12,7 @@ export default function ProfilModalLogin() { <TextInput placeholder="EMAIL..." style={styles.textInput}/> <TextInput placeholder="PASSWORD..." style={styles.textInput}/> </View> - <DefaultButton text="SIGNUP" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> + <DefaultButton text="LOGIN" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> </View> ) } -- GitLab From c610ec5c7d9320050e47467d6a27fbb1ce313890 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 14:03:40 +0100 Subject: [PATCH 065/283] add: modif pos croix back --- screens/Profil/ProfilModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/ProfilModal.tsx index f947a97..b3c90ec 100644 --- a/screens/Profil/ProfilModal.tsx +++ b/screens/Profil/ProfilModal.tsx @@ -146,6 +146,7 @@ const styles = StyleSheet.create({ right: 10, zIndex: 1, backgroundColor: 'white', - borderRadius: 10 + borderRadius: 10, + margin: 10, }, }); \ No newline at end of file -- GitLab From a4cf7d67fd22cdbab206437e128449bee072e3e4 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 14:14:41 +0100 Subject: [PATCH 066/283] feat: touchable feed back --- screens/Profil/ProfilModal.tsx | 163 +++++++++++---------------------- 1 file changed, 52 insertions(+), 111 deletions(-) diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/ProfilModal.tsx index b3c90ec..4398c76 100644 --- a/screens/Profil/ProfilModal.tsx +++ b/screens/Profil/ProfilModal.tsx @@ -1,36 +1,32 @@ import React from "react"; -import { Modal, View, StyleSheet, Button, TextInput, TouchableOpacity } from "react-native"; +import { + Modal, + View, + StyleSheet, + TextInput, + TouchableOpacity, + Keyboard, + TouchableWithoutFeedback, +} from "react-native"; import ProfilSelectMode from "./ProfilSelectMode"; import { ProfilSelectModeType } from "../../models/SelectModeType"; import ProfilModalLogin from "./ProfilModalLogin"; import ProfilModalSignup from "./ProfilModalSignup"; -import Icon from "react-native-vector-icons/MaterialIcons"; +import Icon from "react-native-vector-icons/MaterialIcons"; -interface Props{ +interface Props { modalVisible: boolean; setModalVisible: (visible: boolean) => void; mode: ProfilSelectModeType; setMode: (mode: ProfilSelectModeType) => void; } -export default function ProfilModal({modalVisible ,setModalVisible, mode, setMode}: Props) { - const handleLoginPressed = () => { - - }; - - const handleSignupPressed = () => { - - }; - - const handleSubmitPressed = () => { - - } - - const handleBackPressed = () => { - - } - - +export default function ProfilModal({ + modalVisible, + setModalVisible, + mode, + setMode, + }: Props) { return ( <Modal animationType="slide" @@ -38,25 +34,27 @@ export default function ProfilModal({modalVisible ,setModalVisible, mode, setMod visible={modalVisible} onRequestClose={() => { setModalVisible(!modalVisible); - }}> - <View style={styles.container}> - <TouchableOpacity - style={styles.closeButton} - onPress={() => setModalVisible(false)} // Ferme la modal - > - <Icon name="close" size={30} color="#000" /> - </TouchableOpacity> - <View style={styles.backgroundShadow}/> - <View style={styles.containerModal}> - - <ProfilSelectMode mode={mode} setMode={setMode}/> - {mode === "login" ? ( - <ProfilModalLogin /> - ) : ( - <ProfilModalSignup /> - )} - </View> - </View> + }} + > + <TouchableWithoutFeedback onPress={Keyboard.dismiss}> + <View style={styles.container}> + <TouchableOpacity + style={styles.closeButton} + onPress={() => setModalVisible(false)} // Ferme la modal + > + <Icon name="close" size={50} color="#000" /> + </TouchableOpacity> + <View style={styles.backgroundShadow} /> + <View style={styles.containerModal}> + <ProfilSelectMode mode={mode} setMode={setMode} /> + {mode === "login" ? ( + <ProfilModalLogin /> + ) : ( + <ProfilModalSignup /> + )} + </View> + </View> + </TouchableWithoutFeedback> </Modal> ); } @@ -64,89 +62,32 @@ export default function ProfilModal({modalVisible ,setModalVisible, mode, setMod const styles = StyleSheet.create({ container: { flex: 1, - justifyContent: 'center', - alignItems: 'center', - height: '100%', - width: '100%', + justifyContent: "center", + alignItems: "center", + height: "100%", + width: "100%", }, backgroundShadow: { - height: '100%', - width: '100%', - backgroundColor: '#000000', + height: "100%", + width: "100%", + backgroundColor: "#000000", opacity: 0.55, - position: 'absolute', + position: "absolute", }, containerModal: { height: 650, - width: '90%', - backgroundColor: '#D9D9D9', + width: "90%", + backgroundColor: "#D9D9D9", borderRadius: 40, - padding: 30 - }, - containerNav: { - height: 80, - width: '100%', - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-around', - alignItems: 'center', - backgroundColor: '#FFFFFF', - borderRadius: 10, - shadowColor: "#000", - shadowOffset: { - width: 0, - height: 3, - }, - shadowOpacity: 0.29, - shadowRadius: 4.65, - elevation: 7, - }, - navButton: { - height: 50, - width: '45%', - backgroundColor: '#FFFFFF', - borderBottomColor: '#B9B9B9', - borderBottomWidth: 3, - borderStyle: 'solid', - }, - navButtonText: { - color: '#B9B9B9' - }, - containerForm: { - height: 450, - width: '100%', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-around' - }, - textInput: { - height: 80, - width: '100%', - backgroundColor: '#FFFFFF', - borderWidth: 4, - borderColor: '#1FA9FF', - borderRadius: 10, - color: '#DFDFDF', - fontWeight: 'bold' - }, - SubmitButton: { - height: 80, - width: '100%', - backgroundColor: '#1FA9FF', - borderRadius: 10, - }, - SubmitText: { - fontSize: 20, - fontWeight: 'bold', - color: '#FFFFFF', + padding: 30, }, closeButton: { position: "absolute", top: 10, right: 10, zIndex: 1, - backgroundColor: 'white', + backgroundColor: "white", borderRadius: 10, - margin: 10, + margin: 20, }, -}); \ No newline at end of file +}); -- GitLab From ad48bba7cee26f1e925dc089ff6105db23a956ab Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 14:22:25 +0100 Subject: [PATCH 067/283] fix: taille des modals en fix --- screens/Profil/ProfilModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/ProfilModal.tsx index 4398c76..c5aadb4 100644 --- a/screens/Profil/ProfilModal.tsx +++ b/screens/Profil/ProfilModal.tsx @@ -75,7 +75,7 @@ const styles = StyleSheet.create({ position: "absolute", }, containerModal: { - height: 650, + height: "80%", width: "90%", backgroundColor: "#D9D9D9", borderRadius: 40, -- GitLab From c419416e10fefc7dea85dd640ae378c197d62ef1 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 14:52:05 +0100 Subject: [PATCH 068/283] =?UTF-8?q?fix:=20passer=20=C3=A0=20la=20prochaine?= =?UTF-8?q?=20question?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/PlayQuiz/BlueButton.tsx | 5 +-- helper/QuizHelper.ts | 6 ++-- screens/PlayingQuiz/PlayingQuizBody.tsx | 45 ++++++++++++++++--------- services/QuizService.ts | 2 +- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/components/PlayQuiz/BlueButton.tsx b/components/PlayQuiz/BlueButton.tsx index 4e04f8d..1d39741 100644 --- a/components/PlayQuiz/BlueButton.tsx +++ b/components/PlayQuiz/BlueButton.tsx @@ -1,12 +1,13 @@ -import {View, Text, StyleSheet, TouchableOpacity} from "react-native"; +import {View, Text, StyleSheet, TouchableOpacity, GestureResponderEvent} from "react-native"; import {NavigationProp} from "@react-navigation/native"; interface Props { - onPress: () => void + onPress: ((event: GestureResponderEvent) => void) | undefined text: string buttonStyle?: any } export default function BlueButton({onPress, text, buttonStyle} : Props) { + return ( <TouchableOpacity style={[styles.container, buttonStyle]} onPress={onPress}> <Text style={styles.text}>{text}</Text> diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index 3ff16ac..6456903 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -55,7 +55,7 @@ export const transformToQuizModel = (data: any): QuizModel => { return { id: question.id, question: question.text, // Texte de la question - category: question.theme.name, + category: question.category.name, answers: answers, // Réponses associées multiple: question.type === "multiple", // Détermine si c'est une question multiple nbOfTheQuestion: question.order, // Numéro de la question @@ -64,8 +64,8 @@ export const transformToQuizModel = (data: any): QuizModel => { // Création de l'objet QuizModel const quiz: QuizModel = { - code: data.codeQuiz, // Mapping de codeQuiz vers code - category: data.theme.name, // Extraction du nom de la catégorie + code: data.id, // Mapping de codeQuiz vers code + category: data.category.name, // Extraction du nom de la catégorie questions: questions, // Questions transformées nbQuestions: data.questionCount, // Nombre total de questions nbActualQuestion: data.questionIndex, // Index actuel de la question diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 1b2c895..560ef21 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -23,6 +23,7 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA const [selectedAnswerIndex, setSelectedAnswerIndex] = useState<number | null>(null); const [isCorrect, setIsCorrect] = useState<boolean | null>(null); const [correctAnswer, setCorrectAnswer] = useState<AnswerModel | null>(null); + const [isAnswered, setIsAnswered] = useState<boolean>(false); const { getAnswerQuiz } = useQuizService(); @@ -31,6 +32,7 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA setSelectedAnswerIndex(null); setIsCorrect(null); setCorrectAnswer(null); + setIsAnswered(false); } }, [quiz?.nbActualQuestion]); @@ -38,29 +40,28 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA const currentQuestion: QuestionModel = quiz.questions[quiz.nbActualQuestion - 1]; - const handleAnswerPress = async (index: number) => { - if (isAlreadyPlayed) return; + const handleConfirmAnswerPressed = async () => { + if(selectedAnswerIndex === null) return; + setIsAnswered(true); - const answers = await getAnswerQuiz(quiz.code, currentQuestion.id, currentQuestion.answers[index].id); + const answers = await getAnswerQuiz(quiz.code, currentQuestion.id, currentQuestion.answers[selectedAnswerIndex].id); - if(HttpError.isHttpError(answers)) - { + if (HttpError.isHttpError(answers)) { navigation.navigate("Home"); return; } const correctAnswer = getCorrectAnswer(answers); - const isAnswerCorrect = currentQuestion.answers[index].id === correctAnswer.id; + const isAnswerCorrect = currentQuestion.answers[selectedAnswerIndex].id === correctAnswer.id; - setSelectedAnswerIndex(index); + setSelectedAnswerIndex(selectedAnswerIndex); setIsCorrect(isAnswerCorrect); setCorrectAnswer(correctAnswer); - setIsAlreadyPlayed(true); await new Promise((resolve) => setTimeout(resolve, 2000)); - if(quiz.nbActualQuestion === quiz.questions.length) { - navigation.navigate("EndQuiz", { quiz: quiz }); + if (quiz.nbActualQuestion === quiz.questions.length) { + navigation.navigate("EndQuiz", {quiz: quiz}); } setQuiz((prevQuiz) => ({ @@ -68,8 +69,12 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA nbActualQuestion: prevQuiz.nbActualQuestion + 1, score: isAnswerCorrect ? prevQuiz.score + 1 : prevQuiz.score })); - setIsAlreadyPlayed(false); + } + const handleAnswerPress = async (index: number) => { + if (isAnswered) return + console.log(index); + setSelectedAnswerIndex(index); }; const getCorrectAnswer = (answers: AnswerModel[]): AnswerModel => { @@ -81,7 +86,10 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA const getButtonStyle = (index: number) => { if (selectedAnswerIndex === null) return ButtonsStyles.answerButton; - if (index === selectedAnswerIndex) { + if(!isAnswered && index === selectedAnswerIndex && !isCorrect) return styles.selectedAnswer; + + if (index === selectedAnswerIndex && isCorrect !== null) { + console.log(isCorrect); return isCorrect ? styles.correctAnswer : styles.incorrectAnswer; } if (currentQuestion.answers[index].id === correctAnswer?.id) { @@ -91,9 +99,11 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA }; const getTextStyle = (index: number) => { - if (selectedAnswerIndex === null) return TextsStyles.defaultText; + if (selectedAnswerIndex === null || !isAnswered && index === selectedAnswerIndex) return TextsStyles.defaultText; - if (index === selectedAnswerIndex) { + + + if (index === selectedAnswerIndex && isCorrect !== null) { return isCorrect ? styles.correctText : styles.incorrectText; } if (currentQuestion.answers[index].id === correctAnswer?.id) { @@ -117,7 +127,7 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA ))} {/* Ajout du PlayButton à la fin */} <View style={styles.playButtonContainer}> - <BlueButton onPress={() => console.log("Play button pressed")} text="Play" buttonStyle={{borderRadius: 50}}/> + <BlueButton onPress={handleConfirmAnswerPressed} text="Confirm" buttonStyle={{borderRadius: 50}}/> </View> </> ) : ( @@ -151,6 +161,11 @@ const styles = StyleSheet.create({ borderColor: 'red', borderWidth: 2, }, + selectedAnswer: { + ...ButtonsStyles.answerButton, + borderColor: '#45128c', + borderWidth: 2, + }, correctText: { fontWeight: 'bold', color: 'green', diff --git a/services/QuizService.ts b/services/QuizService.ts index 690a15a..5a34173 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -82,7 +82,7 @@ export const useQuizService = () => { const getAnswerQuiz = async (codeQuiz: string, questionID: number, answerID: number) : Promise<AnswerModel[] | HttpError> => { try { const requestBody = { - codeQuiz: codeQuiz, + id: codeQuiz, questionID: questionID, answerID: answerID }; -- GitLab From 602400dbb34c118e323aa37f6bafae2dc8193544 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 15:18:16 +0100 Subject: [PATCH 069/283] feat: connection between playquiz and quiz playingquiz --- screens/PlayQuiz/PlayQuizChild.tsx | 2 +- screens/PlayQuiz/PlayQuizGenerateQuiz.tsx | 40 ++++++++++++++++++----- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/screens/PlayQuiz/PlayQuizChild.tsx b/screens/PlayQuiz/PlayQuizChild.tsx index 6f2089c..92ca46a 100644 --- a/screens/PlayQuiz/PlayQuizChild.tsx +++ b/screens/PlayQuiz/PlayQuizChild.tsx @@ -22,7 +22,7 @@ export default function PlayQuizChild({navigation}: Props){ <View style={styles.contentContainer}> <SelectMode mode={mode} setMode={setMode}/> {mode === "play" ? ( - <PlayQuizGenerateQuiz /> + <PlayQuizGenerateQuiz navigation={navigation}/> ) : ( <PlayQuizJoinQuiz /> )} diff --git a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx index 7a2d380..066ccc7 100644 --- a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx +++ b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx @@ -1,13 +1,16 @@ import { Text,View, StyleSheet } from "react-native"; import SelectListBox from "../../components/PlayQuiz/SelectListBox"; -import React from "react"; +import React, {useEffect} from "react"; import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; import BlueButton from "../../components/PlayQuiz/BlueButton"; +import {getIdOfCategory} from "../../helper/QuizHelper"; +import {useQuizService} from "../../services/QuizService"; +import {NavigationProp} from "@react-navigation/native"; const difficulty = [ - { key: "easy", value: "Easy" }, - { key: "medium", value: "Medium" }, - { key: "hard", value: "Hard" }, + { key: "easy", value: "easy" }, + { key: "medium", value: "medium" }, + { key: "hard", value: "hard" }, ]; const categories = [ @@ -26,13 +29,34 @@ const categories = [ { key: "21", value: "Sports" }, { key: "22", value: "Geography" }, ]; +interface Props { + navigation: NavigationProp<any>; +} -export default function PlayQuizGenerateQuiz() { +export default function PlayQuizGenerateQuiz({navigation}: Props) { const [difficutlyChoose, setDifficutlyChoose] = React.useState(""); const [categoryChoose, setCategoryChoose] = React.useState(""); + const [nbQuestions, setNbQuestions] = React.useState(""); + const {generateQuiz} = useQuizService(); + + useEffect(() => { + console.log(difficutlyChoose + ", " + getIdOfCategory(categoryChoose) + ", " + nbQuestions); + }, [difficutlyChoose,categoryChoose,nbQuestions]); + + + const handlePlayButtonPress = async () => { + if (nbQuestions === "" && difficutlyChoose === "" && categoryChoose === "") return; - const handlePlayButtonPress = () => { - console.log("Play button pressed"); + const quizGenerated = await generateQuiz(parseInt(nbQuestions), getIdOfCategory(categoryChoose), difficutlyChoose); + navigation.reset({ + index: 0, + routes: [ + { + name: "PlayingQuiz", + params: {quizRecovered: quizGenerated}, + }, + ] + }); }; return ( @@ -41,7 +65,7 @@ export default function PlayQuizGenerateQuiz() { <View style={styles.fieldContainer}> <SelectListBox title="CHOOSE A DIFFICULTY" data={difficulty} setSelected={setDifficutlyChoose} /> <SelectListBox title="CHOOSE A CATEGORY" data={categories} setSelected={setCategoryChoose} /> - <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={() => {}} /> + <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={setNbQuestions} /> </View> <View style={styles.buttonPlayContainer}> <BlueButton onPress={handlePlayButtonPress} text={"PLAY"} /> -- GitLab From 4c06e4506f9df17a80dda8c6a26d3e1a01689a2a Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 16:19:16 +0100 Subject: [PATCH 070/283] add: responsive profil --- screens/Profil/ProfilChild.tsx | 10 ++++------ screens/Profil/ProfilModal.tsx | 18 +++++++++--------- screens/Profil/ProfilModalLogin.tsx | 8 ++++---- screens/Profil/ProfilModalSignup.tsx | 8 ++++---- screens/Profil/ProfilSelectMode.tsx | 6 +++--- 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index f5f6ecb..17911d9 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -46,14 +46,12 @@ const styles = StyleSheet.create({ alignItems: 'center', }, titleImage: { - width: 170, - height: 80, + width: '45%', resizeMode: 'contain', }, profilImage: { - height: 200, - width: 200, - borderRadius: 100, + width: '45%', + aspectRatio: 1 }, pseudoText: { fontSize: 35, @@ -62,7 +60,7 @@ const styles = StyleSheet.create({ }, containerPlayerStats: { width: '100%', - padding: 30, + padding: '5%', }, statsText: { fontSize: 17, diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/ProfilModal.tsx index c5aadb4..bec04df 100644 --- a/screens/Profil/ProfilModal.tsx +++ b/screens/Profil/ProfilModal.tsx @@ -22,11 +22,11 @@ interface Props { } export default function ProfilModal({ - modalVisible, - setModalVisible, - mode, - setMode, - }: Props) { + modalVisible, + setModalVisible, + mode, + setMode, + }: Props) { return ( <Modal animationType="slide" @@ -79,15 +79,15 @@ const styles = StyleSheet.create({ width: "90%", backgroundColor: "#D9D9D9", borderRadius: 40, - padding: 30, + padding: '7%', }, closeButton: { position: "absolute", - top: 10, - right: 10, + top: '1%', + right: '1%', zIndex: 1, backgroundColor: "white", borderRadius: 10, - margin: 20, + margin: '5%', }, }); diff --git a/screens/Profil/ProfilModalLogin.tsx b/screens/Profil/ProfilModalLogin.tsx index 3fd32dd..4f0e723 100644 --- a/screens/Profil/ProfilModalLogin.tsx +++ b/screens/Profil/ProfilModalLogin.tsx @@ -19,16 +19,16 @@ export default function ProfilModalLogin() { const styles = StyleSheet.create({ containerForm: { - height: 400, + height: '67%', width: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'flex-start', - rowGap: 50, + rowGap: '10%', marginTop: 50, }, textInput: { - height: 80, + height: '20%', width: '100%', backgroundColor: '#FFFFFF', borderWidth: 4, @@ -38,7 +38,7 @@ const styles = StyleSheet.create({ fontWeight: 'bold' }, SubmitButton: { - height: 80, + height: '15%', width: '100%', backgroundColor: '#1FA9FF', borderRadius: 10, diff --git a/screens/Profil/ProfilModalSignup.tsx b/screens/Profil/ProfilModalSignup.tsx index 43fa0f2..0356718 100644 --- a/screens/Profil/ProfilModalSignup.tsx +++ b/screens/Profil/ProfilModalSignup.tsx @@ -20,16 +20,16 @@ export default function ProfilModalSignup() { const styles = StyleSheet.create({ containerForm: { - height: 400, + height: '67%', width: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'flex-start', - rowGap: 50, + rowGap: '10%', marginTop: 50 }, textInput: { - height: 80, + height: '20%', width: '100%', backgroundColor: '#FFFFFF', borderWidth: 4, @@ -39,7 +39,7 @@ const styles = StyleSheet.create({ fontWeight: 'bold' }, SubmitButton: { - height: 80, + height: '15%', width: '100%', backgroundColor: '#1FA9FF', borderRadius: 10, diff --git a/screens/Profil/ProfilSelectMode.tsx b/screens/Profil/ProfilSelectMode.tsx index 7a6d0d7..4d67988 100644 --- a/screens/Profil/ProfilSelectMode.tsx +++ b/screens/Profil/ProfilSelectMode.tsx @@ -31,7 +31,7 @@ const styles = StyleSheet.create({ container: { display: 'flex', width: '100%', - height: '10%', + height: '15%', flexDirection: 'row', backgroundColor: '#f3f3f3', borderRadius: 10, @@ -61,8 +61,8 @@ const styles = StyleSheet.create({ }, bar: { width: '100%', - height: 4, - marginTop: 4, + height: '4%', + marginTop: '4%', borderRadius: 10, }, activeBar: { -- GitLab From 4cd84232f0c0a77ba6b934b52ea51a6ebe2b14eb Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 16:24:44 +0100 Subject: [PATCH 071/283] feat: join quiz --- components/PlayQuiz/InputPlayQuiz.tsx | 7 +++- screens/PlayQuiz/PlayQuizChild.tsx | 2 +- screens/PlayQuiz/PlayQuizJoinQuiz.tsx | 56 +++++++++++++++++++++++---- services/QuizService.ts | 8 ++-- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/components/PlayQuiz/InputPlayQuiz.tsx b/components/PlayQuiz/InputPlayQuiz.tsx index 0f6d7e0..e0b00e4 100644 --- a/components/PlayQuiz/InputPlayQuiz.tsx +++ b/components/PlayQuiz/InputPlayQuiz.tsx @@ -16,8 +16,11 @@ export default function InputPlayQuiz({ title, setText, noTitle, placeholder }: style={styles.input} placeholder={placeholder || ""} placeholderTextColor="#bebebe" - keyboardType="numeric" - onChangeText={(text) => setText(text)} + keyboardType="default" + onChangeText={(text) =>{ + setText(text) + console.log("test") + }} /> </View> ); diff --git a/screens/PlayQuiz/PlayQuizChild.tsx b/screens/PlayQuiz/PlayQuizChild.tsx index 92ca46a..6fb6902 100644 --- a/screens/PlayQuiz/PlayQuizChild.tsx +++ b/screens/PlayQuiz/PlayQuizChild.tsx @@ -24,7 +24,7 @@ export default function PlayQuizChild({navigation}: Props){ {mode === "play" ? ( <PlayQuizGenerateQuiz navigation={navigation}/> ) : ( - <PlayQuizJoinQuiz /> + <PlayQuizJoinQuiz navigation={navigation}/> )} </View> </View> diff --git a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx index 93f5310..2e1a023 100644 --- a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx +++ b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx @@ -1,23 +1,58 @@ -import {StyleSheet, View} from "react-native"; +import { StyleSheet, View, Text } from "react-native"; import AboutAQuiz from "../../components/PlayQuiz/AboutAQuiz"; import BlueButton from "../../components/PlayQuiz/BlueButton"; -import React from "react"; +import React, { useEffect } from "react"; import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; import QuizModel from "../../models/QuizModel"; +import { useQuizService } from "../../services/QuizService"; +import HttpError from "../../services/HttpError"; +import {getIdOfCategory} from "../../helper/QuizHelper"; +import {NavigationProp} from "@react-navigation/native"; -export default function PlayQuizJoinQuiz() { +interface Props { + navigation: NavigationProp<any>; +} +export default function PlayQuizJoinQuiz({navigation}: Props) { const [codeQuiz, setCodeQuiz] = React.useState(""); const [quiz, setQuiz] = React.useState<QuizModel>(); + const [error, setError] = React.useState<string>(""); + + const { remainingQuiz } = useQuizService(); + + useEffect(() => { + fetchQuiz(); + }, [codeQuiz]); + + const fetchQuiz = async () => { + const quizFetched = await remainingQuiz(codeQuiz); + if (quizFetched instanceof HttpError) { + setError("The quiz does not exist"); + return; + } + setError(""); + setQuiz(quizFetched); + }; + const handlePlayButtonPress = () => { - console.log("Play button pressed"); + if(error !== "") return; + navigation.reset({ + index: 0, + routes: [ + { + name: "PlayingQuiz", + params: {quizRecovered: quiz}, + }, + ] + }); }; return ( <View style={styles.container}> <View style={styles.contentContainer}> <View style={styles.fieldContainer}> - <InputPlayQuiz placeholder={"Enter quiz ID"} setText={setCodeQuiz} noTitle={true}/> - <AboutAQuiz /> + {error ? <Text style={styles.errorText}>{error}</Text> : null} + <InputPlayQuiz placeholder={"Enter quiz ID"} setText={setCodeQuiz} noTitle={true} /> + <AboutAQuiz quiz={quiz} /> </View> <View style={styles.buttonPlayContainer}> <BlueButton onPress={handlePlayButtonPress} text={"PLAY"} /> @@ -27,7 +62,6 @@ export default function PlayQuizJoinQuiz() { ); } - const styles = StyleSheet.create({ container: { display: "flex", @@ -51,4 +85,10 @@ const styles = StyleSheet.create({ alignItems: "center", paddingVertical: 20, }, -}); \ No newline at end of file + errorText: { + color: "red", + fontSize: 16, + marginBottom: 5, + textAlign: "left", // Aligne le texte à gauche + }, +}); diff --git a/services/QuizService.ts b/services/QuizService.ts index 5a34173..d2bed7c 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -44,12 +44,13 @@ export const useQuizService = () => { } } - const remainingQuiz = async (codeQuiz: string) : Promise<QuizModel | HttpError> => { + const remainingQuiz = async (id: string) : Promise<QuizModel | HttpError> => { // Création du corps de la requête avec les variables passées const requestBody = { - codeQuiz + id }; + console.log(requestBody); try { const response = await fetch(url + "/quiz/remaining", { @@ -67,9 +68,10 @@ export const useQuizService = () => { // Récupération des données retournées par l'API const data = await response.json(); const quiz = transformToQuizModel(data); + console.log(quiz); return quiz; } catch (error) { - console.error("Error while creating quiz:", error); + console.log("Error while remaining quiz:", error); if (HttpError.isHttpError(error)) { return error; -- GitLab From 72e8d136511b71153018f0d6a49ad538d1264b53 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 17:20:30 +0100 Subject: [PATCH 072/283] add: creation de la page end statiquement --- assets/uWin.png | Bin 0 -> 32604 bytes screens/EndQuiz/EndQuiz.tsx | 144 +++++++++++++++++++------------ screens/EndQuiz/EndQuizChild.tsx | 54 ++++++++++++ 3 files changed, 141 insertions(+), 57 deletions(-) create mode 100644 assets/uWin.png create mode 100644 screens/EndQuiz/EndQuizChild.tsx diff --git a/assets/uWin.png b/assets/uWin.png new file mode 100644 index 0000000000000000000000000000000000000000..9456efb9f5b9be207760a222ab4871ea3d73eac8 GIT binary patch literal 32604 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 literal 0 HcmV?d00001 diff --git a/screens/EndQuiz/EndQuiz.tsx b/screens/EndQuiz/EndQuiz.tsx index 12db032..cb13d78 100644 --- a/screens/EndQuiz/EndQuiz.tsx +++ b/screens/EndQuiz/EndQuiz.tsx @@ -1,74 +1,104 @@ +import { NavigationProp } from "@react-navigation/native"; import { View, StyleSheet, Image, Text } from "react-native"; -import DefaultButton from "../../components/DefaultButton"; -import { TextsStyles } from "../../styles/TextsStyles"; -import { ButtonsStyles } from "../../styles/ButtonsStyles"; -import {useTranslation} from "react-i18next"; -import QuizModel from "../../models/QuizModel"; -import {NavigationProp, RouteProp} from "@react-navigation/native"; - -type RoutePropsType = { - EndQuiz: { - quiz: QuizModel; - }; -}; +import EndQuizChild from "./EndQuizChild"; +import TemplateMenu from "../../templates/TemplateMenu"; interface Props { navigation: NavigationProp<any>; - route: RouteProp<RoutePropsType, "EndQuiz">; } -export default function EndQuiz({ navigation, route }: Props) { - const { quiz } = route.params; - const {t} = useTranslation(); - const handleButtonRePlayPressed = () => { - quiz.nbActualQuestion = 1; - quiz.score = 0; - navigation.reset({ - index: 0, - routes: [ - { - name: "PlayingQuiz", - params: {quiz: quiz}, - }, - ] - }); - } - - const handleButtonHomePressed = () => { - navigation.navigate('Home'); - } - +export default function EndQuiz({navigation}: Props) { return ( - <View style={styles.container}> - <Image source={require('../../assets/FondTemplate.png')} style={styles.image}/> - <Text style={TextsStyles.titleText}>{t("app.screens.endQuiz.score")} : {quiz.score}</Text> - <Text style={TextsStyles.subtitleText}>{((quiz.score/quiz.nbQuestions)*100 > 50 ) ? t("app.screens.endQuiz.lose") : t("app.screens.endQuiz.win")}</Text> - <View style={styles.buttonContainer}> - <DefaultButton text={t("app.screens.endQuiz.replay")} handleButtonPressed={handleButtonRePlayPressed} buttonStyle={ButtonsStyles.defaultButton}></DefaultButton> - <DefaultButton text={t("app.screens.endQuiz.goHome")} handleButtonPressed={handleButtonHomePressed}></DefaultButton> - </View> + <View style={styles.containerGlobal}> + <TemplateMenu navigation={navigation} headerNavigation={false} buttonGoBack={false}> + <View> + <EndQuizChild navigation={navigation}/> + </View> + </TemplateMenu> </View> - ) + ); } const styles = StyleSheet.create({ - container: { + containerGlobal: { display: 'flex', width: '100%', height: '100%', - alignItems: 'center', - justifyContent: 'center', - gap: 10, - }, - buttonContainer: { - width: '80%', - margin: '10%', - gap: 20 - }, - image: { - width: '100%', - height: '100%', - position: 'absolute', + flexDirection: 'column', }, }); + + +// import DefaultButton from "../../components/DefaultButton"; +// import { TextsStyles } from "../../styles/TextsStyles"; +// import { ButtonsStyles } from "../../styles/ButtonsStyles"; +// import {useTranslation} from "react-i18next"; +// import QuizModel from "../../models/QuizModel"; +// import {NavigationProp, RouteProp} from "@react-navigation/native"; + +// type RoutePropsType = { +// EndQuiz: { +// quiz: QuizModel; +// }; +// }; + +// interface Props { +// navigation: NavigationProp<any>; +// route: RouteProp<RoutePropsType, "EndQuiz">; +// } + +// export default function EndQuiz({ navigation, route }: Props) { +// const { quiz } = route.params; +// const {t} = useTranslation(); +// const handleButtonRePlayPressed = () => { +// quiz.nbActualQuestion = 1; +// quiz.score = 0; +// navigation.reset({ +// index: 0, +// routes: [ +// { +// name: "PlayingQuiz", +// params: {quiz: quiz}, +// }, +// ] +// }); +// } + +// const handleButtonHomePressed = () => { +// navigation.navigate('Home'); +// } + +// return ( +// <View style={styles.container}> +// <Image source={require('../../assets/FondTemplate.png')} style={styles.image}/> +// <Text style={TextsStyles.titleText}>{t("app.screens.endQuiz.score")} : {quiz.score}</Text> +// <Text style={TextsStyles.subtitleText}>{((quiz.score/quiz.nbQuestions)*100 > 50 ) ? t("app.screens.endQuiz.lose") : t("app.screens.endQuiz.win")}</Text> +// <View style={styles.buttonContainer}> +// <DefaultButton text={t("app.screens.endQuiz.replay")} handleButtonPressed={handleButtonRePlayPressed} buttonStyle={ButtonsStyles.defaultButton}></DefaultButton> +// <DefaultButton text={t("app.screens.endQuiz.goHome")} handleButtonPressed={handleButtonHomePressed}></DefaultButton> +// </View> +// </View> +// ) +// } + +// const styles = StyleSheet.create({ +// container: { +// display: 'flex', +// width: '100%', +// height: '100%', +// alignItems: 'center', +// justifyContent: 'center', +// gap: 10, +// }, +// buttonContainer: { +// width: '80%', +// margin: '10%', +// gap: 20 +// }, +// image: { +// width: '100%', +// height: '100%', +// position: 'absolute', +// }, +// }); \ No newline at end of file diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/EndQuiz/EndQuizChild.tsx new file mode 100644 index 0000000..57e4750 --- /dev/null +++ b/screens/EndQuiz/EndQuizChild.tsx @@ -0,0 +1,54 @@ +import { View, Image, Text, StyleSheet } from "react-native"; +import DefaultButton from "../../components/DefaultButton"; +import { NavigationProp } from "@react-navigation/native"; + +// type RoutePropsType = { +// EndQuiz: { +// quiz: QuizModel; +// }; +// }; + +interface Props { + navigation: NavigationProp<any>; + // route: RouteProp<RoutePropsType, "EndQuiz">; +} + +export default function EndQuizChild({ navigation/* , route */}: Props) { + // const { quiz } = route.params; + + const handleRestartPress = () => { + // quiz.nbActualQuestion = 1; + // quiz.score = 0; + // navigation.reset({ + // index: 0, + // routes: [ + // { + // name: "PlayingQuiz", + // params: {quiz: quiz}, + // }, + // ] + // }); + }; + + const handleBackToMenu = () => { + navigation.navigate('Home'); + }; + + return ( + <View> + <View> + <Image source={require('../../assets/TitleApp.png')} /> + </View> + <View> + <Text>CONGRATS !</Text> + <Text>you finish the test with a score of 12/20 ! </Text> + <Image source={require('../../assets/uWin.png')} /> + <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress}></DefaultButton> + <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu}></DefaultButton> + </View> + </View> + ); +} + +const styles = StyleSheet.create({ +}); \ No newline at end of file -- GitLab From fafa32ef2a2610a28def464256f3071a08ec2ecd Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 17:55:28 +0100 Subject: [PATCH 073/283] =?UTF-8?q?add:=20ajout=20du=20style=20de=20l'?= =?UTF-8?q?=C3=A9cran=20de=20fin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- screens/EndQuiz/EndQuizChild.tsx | 80 ++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/EndQuiz/EndQuizChild.tsx index 57e4750..f4add94 100644 --- a/screens/EndQuiz/EndQuizChild.tsx +++ b/screens/EndQuiz/EndQuizChild.tsx @@ -35,20 +35,84 @@ export default function EndQuizChild({ navigation/* , route */}: Props) { }; return ( - <View> - <View> - <Image source={require('../../assets/TitleApp.png')} /> + <View style={styles.container}> + <View style={styles.containerHeader}> + <Image source={require('../../assets/TitleApp.png')}/> </View> - <View> - <Text>CONGRATS !</Text> - <Text>you finish the test with a score of 12/20 ! </Text> + <View style={styles.containerBody}> + <Text style={styles.TextBody}>CONGRATS !</Text> + <Text style={styles.TextBody}>you finish the test with a score of 12/20 ! </Text> <Image source={require('../../assets/uWin.png')} /> - <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress}></DefaultButton> - <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu}></DefaultButton> + <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton> + <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu} buttonStyle={styles.blueButton} buttonText={styles.blueButtonText}></DefaultButton> </View> </View> ); } const styles = StyleSheet.create({ + container: { + height: '100%', + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-around' + }, + containerHeader: { + height: '10%', + width: '100%', + alignItems: 'center', + }, + containerBody: { + height: '80%', + width: '90%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-around', + backgroundColor: '#F3F3F3', + borderRadius: 40, + padding: '10%', + shadowColor: "#000", + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + + elevation: 5, + }, + TextBody: { + textAlign: 'center', + fontSize: 20, + fontWeight: 'bold', + }, + whiteButton: { + height: '12%', + width: '80%', + backgroundColor: '#FFFFFF', + borderColor: '#2B73FE', + borderWidth: 4, + borderRadius: 10, + padding: 0 + }, + whiteButtonText: { + fontSize: 24, + fontWeight: 'bold', + color: '#2B73FE', + }, + blueButton: { + height: '12%', + width: '80%', + backgroundColor: '#2B73FE', + borderRadius: 10, + padding: 0 + }, + blueButtonText: { + fontSize: 24, + fontWeight: 'bold', + color: '#FFFFFF', + }, }); \ No newline at end of file -- GitLab From a7d1b319d9a5debefe3ac847e96b8c4850645925 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 18:05:46 +0100 Subject: [PATCH 074/283] style: color blue or account modal --- screens/Profil/ProfilModalLogin.tsx | 4 ++-- screens/Profil/ProfilModalSignup.tsx | 4 ++-- screens/Profil/ProfilSelectMode.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/screens/Profil/ProfilModalLogin.tsx b/screens/Profil/ProfilModalLogin.tsx index 4f0e723..3d86621 100644 --- a/screens/Profil/ProfilModalLogin.tsx +++ b/screens/Profil/ProfilModalLogin.tsx @@ -32,7 +32,7 @@ const styles = StyleSheet.create({ width: '100%', backgroundColor: '#FFFFFF', borderWidth: 4, - borderColor: '#1FA9FF', + borderColor: '#1fa9ff', borderRadius: 10, color: '#DFDFDF', fontWeight: 'bold' @@ -40,7 +40,7 @@ const styles = StyleSheet.create({ SubmitButton: { height: '15%', width: '100%', - backgroundColor: '#1FA9FF', + backgroundColor: '#2b73fe', borderRadius: 10, }, SubmitText: { diff --git a/screens/Profil/ProfilModalSignup.tsx b/screens/Profil/ProfilModalSignup.tsx index 0356718..7ced6dd 100644 --- a/screens/Profil/ProfilModalSignup.tsx +++ b/screens/Profil/ProfilModalSignup.tsx @@ -33,7 +33,7 @@ const styles = StyleSheet.create({ width: '100%', backgroundColor: '#FFFFFF', borderWidth: 4, - borderColor: '#1FA9FF', + borderColor: '#1fa9ff', borderRadius: 10, color: '#DFDFDF', fontWeight: 'bold' @@ -41,7 +41,7 @@ const styles = StyleSheet.create({ SubmitButton: { height: '15%', width: '100%', - backgroundColor: '#1FA9FF', + backgroundColor: '#2b73fe', borderRadius: 10, }, SubmitText: { diff --git a/screens/Profil/ProfilSelectMode.tsx b/screens/Profil/ProfilSelectMode.tsx index 4d67988..0abae69 100644 --- a/screens/Profil/ProfilSelectMode.tsx +++ b/screens/Profil/ProfilSelectMode.tsx @@ -57,7 +57,7 @@ const styles = StyleSheet.create({ color: '#bebebe', }, activeText: { - color: '#2ec7ff', + color: '#2b73fe', }, bar: { width: '100%', @@ -66,7 +66,7 @@ const styles = StyleSheet.create({ borderRadius: 10, }, activeBar: { - backgroundColor: '#2ec7ff', // Couleur pour le bouton actif + backgroundColor: '#2b73fe', // Couleur pour le bouton actif }, inactiveBar: { backgroundColor: '#bebebe', // Couleur grise pour les boutons inactifs -- GitLab From ce82034c283aa0f9b497a2649a08863cb725ceb7 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 18:15:06 +0100 Subject: [PATCH 075/283] style: font size --- screens/EndQuiz/EndQuizChild.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/EndQuiz/EndQuizChild.tsx index f4add94..b811860 100644 --- a/screens/EndQuiz/EndQuizChild.tsx +++ b/screens/EndQuiz/EndQuizChild.tsx @@ -40,7 +40,7 @@ export default function EndQuizChild({ navigation/* , route */}: Props) { <Image source={require('../../assets/TitleApp.png')}/> </View> <View style={styles.containerBody}> - <Text style={styles.TextBody}>CONGRATS !</Text> + <Text style={styles.TextBodyTitle}>CONGRATS !</Text> <Text style={styles.TextBody}>you finish the test with a score of 12/20 ! </Text> <Image source={require('../../assets/uWin.png')} /> <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton> @@ -84,9 +84,14 @@ const styles = StyleSheet.create({ elevation: 5, }, + TextBodyTitle: { + textAlign: 'center', + fontSize: 24, + fontWeight: 'bold', + }, TextBody: { textAlign: 'center', - fontSize: 20, + fontSize: 22, fontWeight: 'bold', }, whiteButton: { -- GitLab From af280b7e03c70730d416b726084386f6ed84295a Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 18:45:39 +0100 Subject: [PATCH 076/283] feat: go back to the menu in quiz --- components/PlayingQuiz/ComfirmModal.tsx | 105 ++++++++++++++++++++++ components/PlayingQuiz/GoHomeButton.tsx | 21 +++++ screens/Home/HeaderNavigation.tsx | 1 + screens/PlayingQuiz/PlayingQuiz.tsx | 3 +- screens/PlayingQuiz/PlayingQuizHeader.tsx | 33 +++++-- 5 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 components/PlayingQuiz/ComfirmModal.tsx create mode 100644 components/PlayingQuiz/GoHomeButton.tsx diff --git a/components/PlayingQuiz/ComfirmModal.tsx b/components/PlayingQuiz/ComfirmModal.tsx new file mode 100644 index 0000000..f31241c --- /dev/null +++ b/components/PlayingQuiz/ComfirmModal.tsx @@ -0,0 +1,105 @@ +import React from "react"; +import { + Modal, + View, + Text, + StyleSheet, + TouchableOpacity, + TouchableWithoutFeedback, +} from "react-native"; +import Icon from "react-native-vector-icons/MaterialIcons"; +import BlueButton from "../PlayQuiz/BlueButton"; // Pour l'icône de fermeture + +interface Props { + visible: boolean; + onClose: () => void; + onConfirm: () => void; +} + +export default function ConfirmModal({ visible, onClose, onConfirm }: Props) { + return ( + <Modal + animationType="slide" + transparent={true} + visible={visible} + onRequestClose={onClose} // Ferme la modal en cas d'interaction système + > + <TouchableWithoutFeedback onPress={onClose}> + <View style={styles.overlay} /> + </TouchableWithoutFeedback> + <View style={styles.modalContainer}> + <TouchableOpacity style={styles.closeButton} onPress={onClose}> + <Icon name="close" size={24} color="#000" /> + </TouchableOpacity> + <Text style={styles.title}>WOULD YOU LIKE TO RETURN TO THE MENU ?</Text> + <View style={styles.separator} /> + <View style={styles.buttonContainer}> + <BlueButton text={"CONFIRM"} onPress={onConfirm}/> + </View> + </View> + </Modal> + ); +} + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: "rgba(0, 0, 0, 0.5)", + }, + modalContainer: { + position: "absolute", + top: "40%", + left: "10%", + right: "10%", + backgroundColor: "white", + borderRadius: 10, + padding: 20, + alignItems: "center", + elevation: 5, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.25, + shadowRadius: 4, + }, + closeButton: { + position: "absolute", + top: 10, + right: 10, + zIndex: 1, + }, + title: { + fontSize: 16, + fontWeight: "bold", + color: "#000", + textAlign: "center", + marginVertical: 20, + }, + separator: { + width: "100%", + height: 2, + backgroundColor: "#bebebe", + marginVertical: 10, + }, + confirmButton: { + backgroundColor: "#1FA9FF", + borderRadius: 8, + paddingVertical: 10, + paddingHorizontal: 20, + alignItems: "center", + justifyContent: "center", + marginTop: 10, + width: "80%", + }, + confirmButtonText: { + color: "#fff", + fontSize: 16, + fontWeight: "bold", + }, + buttonContainer: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + width: "80%", + marginTop: 10, + }, +}); diff --git a/components/PlayingQuiz/GoHomeButton.tsx b/components/PlayingQuiz/GoHomeButton.tsx new file mode 100644 index 0000000..56f2476 --- /dev/null +++ b/components/PlayingQuiz/GoHomeButton.tsx @@ -0,0 +1,21 @@ +import {Image, StyleSheet, TouchableOpacity} from "react-native"; +import {NavigationProp} from "@react-navigation/native"; + +interface Props{ + navigation: NavigationProp<any> + onPress: (bool:boolean) => void +} + + +export default function GoHomeButton({navigation, onPress}: Props){ + const handleGoHomeButtonPressed = () =>{ + onPress(true); + } + + return ( + <TouchableOpacity onPress={handleGoHomeButtonPressed}> + <Image source={require('../../assets/GoHomeNavigation.png')} /> + </TouchableOpacity> + ) +} + diff --git a/screens/Home/HeaderNavigation.tsx b/screens/Home/HeaderNavigation.tsx index 1a5e961..03370bb 100644 --- a/screens/Home/HeaderNavigation.tsx +++ b/screens/Home/HeaderNavigation.tsx @@ -1,5 +1,6 @@ import { View, StyleSheet, Image, TouchableOpacity } from "react-native"; import { NavigationProp } from "@react-navigation/native"; +import ConfirmModal from "../../components/PlayingQuiz/ComfirmModal"; interface Props { navigation: NavigationProp<any>; diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 9ea3d2e..e16dd84 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -5,6 +5,7 @@ import TemplateDuo from "../../templates/TemplateDuo"; import QuestionModel from "../../models/QuestionModel"; import QuizModel from "../../models/QuizModel"; import {NavigationProp, RouteProp} from "@react-navigation/native"; +import ConfirmModal from "../../components/PlayingQuiz/ComfirmModal"; const optionsTheme = [ "General Knowledge", @@ -40,7 +41,7 @@ export default function PlayingQuiz({route, navigation}:Props) { return ( <TemplateDuo - childrenHeader={<PlayingQuizHeader quiz={quiz} ></PlayingQuizHeader>} + childrenHeader={<PlayingQuizHeader quiz={quiz} navigation={navigation}></PlayingQuizHeader>} childrenBody={<PlayingQuizBody quiz={quiz} setQuiz={setQuiz} isAlreadyPlayed={isAlreadyPlayed} setIsAlreadyPlayed={setIsAlreadyPlayed} navigation={navigation}></PlayingQuizBody>} /> ); diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index a04bded..fa1747f 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -3,22 +3,37 @@ import { TextsStyles } from "../../styles/TextsStyles"; import QuizModel from "../../models/QuizModel"; import {useTranslation} from "react-i18next"; import DefaultButton from "../../components/DefaultButton"; +import GoHomeButton from "../../components/PlayingQuiz/GoHomeButton"; +import {NavigationProp} from "@react-navigation/native"; +import ConfirmModal from "../../components/PlayingQuiz/ComfirmModal"; +import {useState} from "react"; interface Props { - + navigation: NavigationProp<any> quiz: QuizModel; } -export default function PlayingQuizHeader({quiz}: Props) { +export default function PlayingQuizHeader({navigation, quiz}: Props) { + const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); const {t} = useTranslation(); + const handleConfirmModalConfirm = () => { + navigation.reset({ + index: 0, + routes: [ + { + name: "Home", + }, + ] + }); + } + if (quiz.nbQuestions <= quiz.nbActualQuestion - 1) return; - const handleButtonBackToHomePressed = () => { - } return ( <View style={styles.container}> - <View> + <View style={styles.header}> + <GoHomeButton navigation={navigation} onPress={setIsConfirmModalVisible}/> <Text style={styles.codeQuizText}>ID QUIZZ : {quiz ? quiz.code : "?"}</Text> </View> <View style={styles.questionAndScoreContainer}> @@ -34,6 +49,8 @@ export default function PlayingQuizHeader({quiz}: Props) { <View style={styles.QuizQuestionContainer}> <Text style={TextsStyles.subtitleText}>{quiz.questions[quiz.nbActualQuestion-1].question ? quiz.questions[quiz.nbActualQuestion-1].question : "Loading..."}</Text> </View> + <ConfirmModal visible={isConfirmModalVisible} onClose={()=>setIsConfirmModalVisible(false)} onConfirm={handleConfirmModalConfirm}/> + </View> ); } @@ -68,4 +85,10 @@ const styles = StyleSheet.create({ fontSize: 18, color: '#000000', }, + header: { + display: "flex", + flexDirection: "row", + justifyContent: "flex-start", + gap: '5%', + } }); \ No newline at end of file -- GitLab From 37034b4479d4f0c7243e866772aa4b41188291ae Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 18:56:28 +0100 Subject: [PATCH 077/283] feat: UserModel --- assets/GoHomeNavigation.png | Bin 0 -> 231 bytes models/UserModel.ts | 13 +++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 assets/GoHomeNavigation.png create mode 100644 models/UserModel.ts diff --git a/assets/GoHomeNavigation.png b/assets/GoHomeNavigation.png new file mode 100644 index 0000000000000000000000000000000000000000..68b716cb9fcb970acf527989ac09384f53062bf9 GIT binary patch literal 231 zcmeAS@N?(olHy`uVBq!ia0vp^B0#Lf!3HGbSN+@zq&N#aB8wRqxP?KOkzv*x37{Zj zage(c!@6@aFM%AEbVpxD28NCO+<y{Tf&3az7srqa#<$llayBRk9Q(LiEu%RsQ`o(L zC3BKn=8`)PezDYYXa(^r?XZ_=;<x|en)1rSMP{0$upzgxdlqlRgktHfWsOtSR=s8P z?wu83aK_;9zJo?PRwrgCye$w~U-h(y&6(Hq%leF}t*`be>0dDWz!tmGTtUxKBK{kn Y=c*NVB2Sj51Kq*k>FVdQ&MBb@0PV_4{Qv*} literal 0 HcmV?d00001 diff --git a/models/UserModel.ts b/models/UserModel.ts new file mode 100644 index 0000000..f206e7d --- /dev/null +++ b/models/UserModel.ts @@ -0,0 +1,13 @@ +export default class UserModel { + id: number; + name: string; + token: string; + + constructor(id: number, name: string, token: string) { + this.id = id; + this.name = name; + this.token = token; + } +} + + -- GitLab From c858610e1ca8171b1554869d4400b17a8e8e7b7b Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 4 Dec 2024 19:21:11 +0100 Subject: [PATCH 078/283] feat: useState user --- hooks/UseUser.ts | 60 +++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 34 +++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 95 insertions(+) create mode 100644 hooks/UseUser.ts diff --git a/hooks/UseUser.ts b/hooks/UseUser.ts new file mode 100644 index 0000000..810fc17 --- /dev/null +++ b/hooks/UseUser.ts @@ -0,0 +1,60 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; +export const useUser = () => { + const getUser = async <T>(key: string): Promise<T | undefined> => { + try { + const item = await AsyncStorage.getItem(key); + if (!item) { + return undefined; + } + console.log("User informations loaded"); + return JSON.parse(item); + } catch (error) { + console.error("Erreur lors de la récupération de la donnée des informations utilisateur", error); + return undefined; + } + }; + + const setUser = async <T>(key: string, newValue: T) => { + try { + await AsyncStorage.setItem(key, JSON.stringify(newValue)); + console.log("User informations saved"); + } catch (error) { + console.error("Erreur lors de l'enregistrement de la donnée", error); + } + }; + + const getAnonymousUser = async <T>(): Promise<T | undefined> => { + return getUser("anonymousUser"); + } + + const setAnonymousUser = async <T>(newValue: T) => { + return setUser("anonymousUser", newValue); + } + + const getRealUser = async <T>(): Promise<T | undefined> => { + return getUser("realUser"); + } + + const setRealUser = async <T>(newValue: T) => { + return setUser("realUser", newValue); + } + + const connectUser = async <T>(user: T) => { + // TODO: Implement + } + + const logOutUser = async () => { + // TODO: Implement + } + + return { + getAnonymousUser: getAnonymousUser, + setAnonymousUser: setAnonymousUser, + getRealUser: getRealUser, + setRealUser: setRealUser, + connectUser: connectUser, + logOutUser: logOutUser, + }; +} + + diff --git a/package-lock.json b/package-lock.json index b4a0a5f..2fa9495 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "vili", "version": "1.0.0", "dependencies": { + "@react-native-async-storage/async-storage": "^2.1.0", "@react-native-picker/picker": "^2.9.0", "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", @@ -3483,6 +3484,18 @@ "node": ">=14" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.0.tgz", + "integrity": "sha512-eAGQGPTAuFNEoIQSB5j2Jh1zm5NPyBRTfjRMfCN0W1OakC5WIB5vsDyIQhUweKN9XOE2/V07lqTMGsL0dGXNkA==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native-picker/picker": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.9.0.tgz", @@ -6774,6 +6787,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -7663,6 +7685,18 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/package.json b/package.json index c8ebea8..a87f186 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "web": "expo start --web" }, "dependencies": { + "@react-native-async-storage/async-storage": "^2.1.0", "@react-native-picker/picker": "^2.9.0", "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", -- GitLab From e108df1c31ab4c195daba48a458f46133cf22f76 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 4 Dec 2024 19:29:53 +0100 Subject: [PATCH 079/283] add: ajout du splashscreen --- App.tsx | 18 ++++++++++++++++++ app.json | 2 +- assets/splashscreen.png | Bin 0 -> 39550 bytes 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 assets/splashscreen.png diff --git a/App.tsx b/App.tsx index 8d4b928..41a0a91 100644 --- a/App.tsx +++ b/App.tsx @@ -3,8 +3,26 @@ import {I18nextProvider, useTranslation} from "react-i18next"; import i18n from "./translation/i18n"; // Importez l'instance déjà initialisée import { SafeAreaProvider } from "react-native-safe-area-context"; import StackNavigator from "./routes/StackNavigator"; +import { useEffect } from 'react'; +import * as SplashScreen from 'expo-splash-screen'; + export default function App() { // LogBox.ignoreAllLogs(true); + useEffect(() => { + async function prepare() { + try { + await SplashScreen.preventAutoHideAsync(); // affichage + + // Chargement des fonts, appels API externes, etc... + + // On fait une pause 2 sec pour simuler un chargement lent + await new Promise(resolve => setTimeout(resolve, 2000)); + + } catch (e) { console.warn(e); } finally { + await SplashScreen.hideAsync(); // masquage + } + } prepare(); }, []); + const { t } = useTranslation(); return ( diff --git a/app.json b/app.json index 0363479..409bd9c 100644 --- a/app.json +++ b/app.json @@ -8,7 +8,7 @@ "icon": "./assets/icon.png", "userInterfaceStyle": "light", "splash": { - "image": "./assets/splash.png", + "image": "./assets/splashscreen.png", "resizeMode": "contain", "backgroundColor": "#ffffff" }, diff --git a/assets/splashscreen.png b/assets/splashscreen.png new file mode 100644 index 0000000000000000000000000000000000000000..7fb90c52530abcbaf7a7f980aeef669199e6d076 GIT binary patch literal 39550 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⁢SLQsfk`t<ci%jd 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 G3I7EeDe3$G literal 0 HcmV?d00001 -- GitLab From 206611d229c2d871610b507a9ddba2d145aeff5f Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 08:56:28 +0100 Subject: [PATCH 080/283] feat: numeric param in InputPlayQuiz --- components/PlayQuiz/InputPlayQuiz.tsx | 40 +++++++++++------------ screens/PlayQuiz/PlayQuizGenerateQuiz.tsx | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/components/PlayQuiz/InputPlayQuiz.tsx b/components/PlayQuiz/InputPlayQuiz.tsx index e0b00e4..be655db 100644 --- a/components/PlayQuiz/InputPlayQuiz.tsx +++ b/components/PlayQuiz/InputPlayQuiz.tsx @@ -6,9 +6,10 @@ interface Props { setText: (text: string) => void; noTitle?: boolean; placeholder?: string; + numeric?: boolean; } -export default function InputPlayQuiz({ title, setText, noTitle, placeholder }: Props) { +export default function InputPlayQuiz({ title, setText, noTitle, placeholder, numeric }: Props) { return ( <View style={styles.container}> {!noTitle && title && <Text style={styles.title}>{title}</Text>} @@ -16,10 +17,10 @@ export default function InputPlayQuiz({ title, setText, noTitle, placeholder }: style={styles.input} placeholder={placeholder || ""} placeholderTextColor="#bebebe" - keyboardType="default" - onChangeText={(text) =>{ - setText(text) - console.log("test") + keyboardType={numeric ? "numeric" : "default"} // Clavier conditionnel + onChangeText={(text) => { + setText(text); + console.log("Input changed:", text); }} /> </View> @@ -30,30 +31,29 @@ const styles = StyleSheet.create({ container: { display: "flex", flexDirection: "column", - alignItems: "flex-start", // Aligne les éléments à gauche - width: "100%", // S'assure que le conteneur prend toute la largeur + alignItems: "flex-start", + width: "100%", }, input: { width: "100%", - height: 70, // Hauteur augmentée comme dans boxStyles - backgroundColor: "white", // Fond blanc comme dans boxStyles + height: 70, + backgroundColor: "white", borderColor: "#2ec7ff", borderWidth: 3, borderRadius: 8, - paddingLeft: 10, // Espacement à gauche pour le texte - elevation: 5, // Ombre pour Android - // Ombre pour iOS + paddingLeft: 10, + elevation: 5, shadowColor: "#000", - shadowOffset: { width: 0, height: 4 }, // Décalage de l'ombre en bas - shadowOpacity: 0.25, // Opacité de l'ombre - shadowRadius: 3.84, // Flou de l'ombre + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.25, + shadowRadius: 3.84, }, title: { fontSize: 20, - fontWeight: "bold", // Met le texte en gras - marginBottom: 5, // Ajoute un espace entre le titre et la boîte déroulante - color: "#333", // Couleur du texte - textAlign: "left", // Aligne le texte à gauche - width: "100%", // S'assure que le texte utilise toute la largeur disponible + fontWeight: "bold", + marginBottom: 5, + color: "#333", + textAlign: "left", + width: "100%", }, }); diff --git a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx index 066ccc7..b8368ae 100644 --- a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx +++ b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx @@ -65,7 +65,7 @@ export default function PlayQuizGenerateQuiz({navigation}: Props) { <View style={styles.fieldContainer}> <SelectListBox title="CHOOSE A DIFFICULTY" data={difficulty} setSelected={setDifficutlyChoose} /> <SelectListBox title="CHOOSE A CATEGORY" data={categories} setSelected={setCategoryChoose} /> - <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={setNbQuestions} /> + <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={setNbQuestions} numeric={true} /> </View> <View style={styles.buttonPlayContainer}> <BlueButton onPress={handlePlayButtonPress} text={"PLAY"} /> -- GitLab From e71d794e9e670c3934361c57965ed653bca65b24 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 08:57:52 +0100 Subject: [PATCH 081/283] style: console.log --- components/PlayQuiz/InputPlayQuiz.tsx | 1 - screens/PlayQuiz/PlayQuizGenerateQuiz.tsx | 4 ---- screens/PlayingQuiz/PlayingQuizBody.tsx | 2 -- services/QuizService.ts | 2 -- 4 files changed, 9 deletions(-) diff --git a/components/PlayQuiz/InputPlayQuiz.tsx b/components/PlayQuiz/InputPlayQuiz.tsx index be655db..4df76b8 100644 --- a/components/PlayQuiz/InputPlayQuiz.tsx +++ b/components/PlayQuiz/InputPlayQuiz.tsx @@ -20,7 +20,6 @@ export default function InputPlayQuiz({ title, setText, noTitle, placeholder, nu keyboardType={numeric ? "numeric" : "default"} // Clavier conditionnel onChangeText={(text) => { setText(text); - console.log("Input changed:", text); }} /> </View> diff --git a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx index b8368ae..2cad214 100644 --- a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx +++ b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx @@ -39,10 +39,6 @@ export default function PlayQuizGenerateQuiz({navigation}: Props) { const [nbQuestions, setNbQuestions] = React.useState(""); const {generateQuiz} = useQuizService(); - useEffect(() => { - console.log(difficutlyChoose + ", " + getIdOfCategory(categoryChoose) + ", " + nbQuestions); - }, [difficutlyChoose,categoryChoose,nbQuestions]); - const handlePlayButtonPress = async () => { if (nbQuestions === "" && difficutlyChoose === "" && categoryChoose === "") return; diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 560ef21..dfce563 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -73,7 +73,6 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA const handleAnswerPress = async (index: number) => { if (isAnswered) return - console.log(index); setSelectedAnswerIndex(index); }; @@ -89,7 +88,6 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA if(!isAnswered && index === selectedAnswerIndex && !isCorrect) return styles.selectedAnswer; if (index === selectedAnswerIndex && isCorrect !== null) { - console.log(isCorrect); return isCorrect ? styles.correctAnswer : styles.incorrectAnswer; } if (currentQuestion.answers[index].id === correctAnswer?.id) { diff --git a/services/QuizService.ts b/services/QuizService.ts index d2bed7c..fbbf5de 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -50,7 +50,6 @@ export const useQuizService = () => { const requestBody = { id }; - console.log(requestBody); try { const response = await fetch(url + "/quiz/remaining", { @@ -68,7 +67,6 @@ export const useQuizService = () => { // Récupération des données retournées par l'API const data = await response.json(); const quiz = transformToQuizModel(data); - console.log(quiz); return quiz; } catch (error) { console.log("Error while remaining quiz:", error); -- GitLab From c17f13f8aef937dd44207ab39f0a1eaf974caf18 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 09:04:09 +0100 Subject: [PATCH 082/283] style: to much imports --- screens/PlayQuiz/PlayQuizChild.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/screens/PlayQuiz/PlayQuizChild.tsx b/screens/PlayQuiz/PlayQuizChild.tsx index 6fb6902..ad6e515 100644 --- a/screens/PlayQuiz/PlayQuizChild.tsx +++ b/screens/PlayQuiz/PlayQuizChild.tsx @@ -3,9 +3,6 @@ import {NavigationProp} from "@react-navigation/native"; import SelectMode from "../../components/PlayQuiz/SelectMode"; import React, {useState} from "react"; import {SelectModeType} from "../../models/SelectModeType"; -import SelectListBox from "../../components/PlayQuiz/SelectListBox"; -import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; -import BlueButton from "../../components/PlayQuiz/BlueButton"; import PlayQuizGenerateQuiz from "./PlayQuizGenerateQuiz"; import PlayQuizJoinQuiz from "./PlayQuizJoinQuiz"; -- GitLab From fabffed7be47cfe6baa1d0fa613429c77be714c6 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 09:11:45 +0100 Subject: [PATCH 083/283] feat: BlueButton can be disabled --- components/PlayQuiz/BlueButton.tsx | 50 +++++++++++++++++++----------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/components/PlayQuiz/BlueButton.tsx b/components/PlayQuiz/BlueButton.tsx index 1d39741..21e78e7 100644 --- a/components/PlayQuiz/BlueButton.tsx +++ b/components/PlayQuiz/BlueButton.tsx @@ -1,34 +1,48 @@ -import {View, Text, StyleSheet, TouchableOpacity, GestureResponderEvent} from "react-native"; -import {NavigationProp} from "@react-navigation/native"; +import { View, Text, StyleSheet, TouchableOpacity, GestureResponderEvent } from "react-native"; interface Props { - onPress: ((event: GestureResponderEvent) => void) | undefined - text: string - buttonStyle?: any + onPress: ((event: GestureResponderEvent) => void) | undefined; + text: string; + buttonStyle?: any; + isDisabled: boolean; } -export default function BlueButton({onPress, text, buttonStyle} : Props) { +export default function BlueButton({ onPress, text, buttonStyle, isDisabled }: Props) { return ( - <TouchableOpacity style={[styles.container, buttonStyle]} onPress={onPress}> - <Text style={styles.text}>{text}</Text> + <TouchableOpacity + style={[ + styles.container, + isDisabled ? styles.disabledContainer : styles.enabledContainer, + buttonStyle, + ]} + onPress={onPress} + disabled={isDisabled} + > + <Text style={[styles.text, isDisabled && styles.disabledText]}>{text}</Text> </TouchableOpacity> ); } const styles = StyleSheet.create({ - container: { - width: '100%', + width: "100%", height: 70, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: '#2b73fe', + alignItems: "center", + justifyContent: "center", borderRadius: 10, }, + enabledContainer: { + backgroundColor: "#2b73fe", // Couleur bleue par défaut + }, + disabledContainer: { + backgroundColor: "#b0b0b0", // Gris désactivé + }, text: { - color: 'white', fontSize: 28, - fontWeight: 'bold', - } - -}); \ No newline at end of file + color: "white", + fontWeight: "bold", + }, + disabledText: { + color: "#d0d0d0", // Texte grisé pour un bouton désactivé + }, +}); -- GitLab From e0763d95336fb3a01e4881ff8018b0be8e71657f Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 09:12:07 +0100 Subject: [PATCH 084/283] feat: Generate quiz blue button can be disabled --- screens/PlayQuiz/PlayQuizGenerateQuiz.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx index 2cad214..b4dbe30 100644 --- a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx +++ b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx @@ -37,12 +37,22 @@ export default function PlayQuizGenerateQuiz({navigation}: Props) { const [difficutlyChoose, setDifficutlyChoose] = React.useState(""); const [categoryChoose, setCategoryChoose] = React.useState(""); const [nbQuestions, setNbQuestions] = React.useState(""); + const [buttonIsDisabled, setButtonIsDisabled] = React.useState(true); const {generateQuiz} = useQuizService(); + useEffect(() => { + if(nbQuestions === "" || difficutlyChoose === "" || categoryChoose === "") + { + setButtonIsDisabled(true); + } + else + { + setButtonIsDisabled(false); + } - const handlePlayButtonPress = async () => { - if (nbQuestions === "" && difficutlyChoose === "" && categoryChoose === "") return; + }, [difficutlyChoose,categoryChoose,nbQuestions]); + const handlePlayButtonPress = async () => { const quizGenerated = await generateQuiz(parseInt(nbQuestions), getIdOfCategory(categoryChoose), difficutlyChoose); navigation.reset({ index: 0, @@ -64,7 +74,7 @@ export default function PlayQuizGenerateQuiz({navigation}: Props) { <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={setNbQuestions} numeric={true} /> </View> <View style={styles.buttonPlayContainer}> - <BlueButton onPress={handlePlayButtonPress} text={"PLAY"} /> + <BlueButton onPress={handlePlayButtonPress} text={"PLAY"} isDisabled={buttonIsDisabled}/> </View> </View> </View> -- GitLab From 20291a803e61d79b357d080ac5b57cfdb05d375c Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 09:15:59 +0100 Subject: [PATCH 085/283] feat: Join quiz blue button can be disabled --- screens/PlayQuiz/PlayQuizJoinQuiz.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx index 2e1a023..a1d2523 100644 --- a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx +++ b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx @@ -24,6 +24,10 @@ export default function PlayQuizJoinQuiz({navigation}: Props) { }, [codeQuiz]); const fetchQuiz = async () => { + if(codeQuiz === "") + { + return; + } const quizFetched = await remainingQuiz(codeQuiz); if (quizFetched instanceof HttpError) { setError("The quiz does not exist"); @@ -34,7 +38,6 @@ export default function PlayQuizJoinQuiz({navigation}: Props) { }; const handlePlayButtonPress = () => { - if(error !== "") return; navigation.reset({ index: 0, routes: [ @@ -55,7 +58,7 @@ export default function PlayQuizJoinQuiz({navigation}: Props) { <AboutAQuiz quiz={quiz} /> </View> <View style={styles.buttonPlayContainer}> - <BlueButton onPress={handlePlayButtonPress} text={"PLAY"} /> + <BlueButton onPress={handlePlayButtonPress} text={"PLAY"} isDisabled={codeQuiz==="" || error!==""}/> </View> </View> </View> -- GitLab From 82f24900b531d1d58af56436d2fcb35bbc1c8cf6 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 09:21:00 +0100 Subject: [PATCH 086/283] feat: PlayingQuiz blue button can be disabled --- screens/PlayingQuiz/PlayingQuizBody.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index dfce563..7031a60 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -125,7 +125,7 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA ))} {/* Ajout du PlayButton à la fin */} <View style={styles.playButtonContainer}> - <BlueButton onPress={handleConfirmAnswerPressed} text="Confirm" buttonStyle={{borderRadius: 50}}/> + <BlueButton onPress={handleConfirmAnswerPressed} text="Confirm" buttonStyle={{borderRadius: 50}} isDisabled={selectedAnswerIndex===null}/> </View> </> ) : ( -- GitLab From edb5e5ffd2b9341aaee12f89ad4c744273ff835c Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 09:32:40 +0100 Subject: [PATCH 087/283] feat: Inputs changed on login --- screens/Profil/ProfilModalLogin.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/screens/Profil/ProfilModalLogin.tsx b/screens/Profil/ProfilModalLogin.tsx index 3d86621..6dacfc4 100644 --- a/screens/Profil/ProfilModalLogin.tsx +++ b/screens/Profil/ProfilModalLogin.tsx @@ -1,7 +1,12 @@ import { TextInput, View, StyleSheet } from "react-native"; import DefaultButton from "../../components/DefaultButton"; +import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; +import React, {useState} from "react"; export default function ProfilModalLogin() { + + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); const handleSubmitPressed = () => { } @@ -9,8 +14,8 @@ export default function ProfilModalLogin() { return ( <View> <View style={styles.containerForm}> - <TextInput placeholder="EMAIL..." style={styles.textInput}/> - <TextInput placeholder="PASSWORD..." style={styles.textInput}/> + <InputPlayQuiz placeholder="EMAIL..." setText={setEmail} noTitle={true}/> + <InputPlayQuiz placeholder="PASSWORD..." setText={setPassword} noTitle={true}/> </View> <DefaultButton text="LOGIN" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> </View> -- GitLab From 2b179fc8fab56c6fc5839910ea4c6943eaeeca25 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 09:37:28 +0100 Subject: [PATCH 088/283] feat: Inputs changed on signup --- screens/Profil/ProfilModalSignup.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/screens/Profil/ProfilModalSignup.tsx b/screens/Profil/ProfilModalSignup.tsx index 7ced6dd..3b7b6de 100644 --- a/screens/Profil/ProfilModalSignup.tsx +++ b/screens/Profil/ProfilModalSignup.tsx @@ -1,7 +1,13 @@ import { TextInput, View, StyleSheet } from "react-native"; import DefaultButton from "../../components/DefaultButton"; +import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; +import React, {useState} from "react"; export default function ProfilModalSignup() { + + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [repeatPassword, setRepeatPassword] = useState(""); const handleSubmitPressed = () => { } @@ -9,9 +15,9 @@ export default function ProfilModalSignup() { return ( <View> <View style={styles.containerForm}> - <TextInput placeholder="EMAIL..." style={styles.textInput}/> - <TextInput placeholder="PASSWORD..." style={styles.textInput}/> - <TextInput placeholder="REPEAT PASSWORD..." style={styles.textInput}/> + <InputPlayQuiz placeholder="EMAIL..." setText={setEmail} noTitle={true}/> + <InputPlayQuiz placeholder="PASSWORD..." setText={setPassword} noTitle={true}/> + <InputPlayQuiz placeholder="REPEAT PASSWORD..." setText={setRepeatPassword} noTitle={true}/> </View> <DefaultButton text="SIGNUP" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> </View> -- GitLab From cc55c01fb62f96815598d08ddbc5531b1d95c36f Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 10:08:00 +0100 Subject: [PATCH 089/283] feat: getRandomQuiz service --- services/QuizService.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/services/QuizService.ts b/services/QuizService.ts index fbbf5de..6248af7 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -112,6 +112,42 @@ export const useQuizService = () => { } } } + + const getRandomQuiz = async () : Promise<QuizModel | HttpError> => { + + // Création du corps de la requête avec les variables passées + const requestBody = { + amount: 10, + }; + + try { + const response = await fetch(url + "/quiz/create", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(requestBody), + }); + + if (!response.ok) { + throw new HttpError(response.status, "HTTP error! status: " + response.status); + } + + // Récupération des données retournées par l'API + const data = await response.json(); + const quiz = transformToQuizModel(data); + return quiz; + } catch (error) { + console.error("Error while generating random quiz:", error); + + if (HttpError.isHttpError(error)) { + return error; + } else { + return new HttpError(500, "Unexpected error: " + error); + } + } + } + return { generateQuiz: generateQuiz, getAnswerQuiz: getAnswerQuiz, -- GitLab From 40162ba1ff3f7a85e768f8e8bc7a8fabfb3d637d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 16:52:04 +0100 Subject: [PATCH 090/283] feat: splashscreen --- app.json | 2 +- assets/icon.png | Bin 22380 -> 59707 bytes package-lock.json | 13 +++++++++++++ package.json | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app.json b/app.json index 409bd9c..d7e9736 100644 --- a/app.json +++ b/app.json @@ -9,7 +9,7 @@ "userInterfaceStyle": "light", "splash": { "image": "./assets/splashscreen.png", - "resizeMode": "contain", + "resizeMode": "cover", "backgroundColor": "#ffffff" }, "ios": { diff --git a/assets/icon.png b/assets/icon.png index a0b1526fc7b78680fd8d733dbc6113e1af695487..faee7622c57c0e59493f8dffdf1998d64e14b981 100644 GIT binary patch literal 59707 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 literal 22380 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 diff --git a/package-lock.json b/package-lock.json index 2fa9495..f44f12d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", "expo": "^52.0.8", + "expo-splash-screen": "^0.29.13", "expo-status-bar": "~2.0.0", "he": "^1.2.0", "i18n-js": "^4.5.0", @@ -5958,6 +5959,18 @@ "invariant": "^2.2.4" } }, + "node_modules/expo-splash-screen": { + "version": "0.29.13", + "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.29.13.tgz", + "integrity": "sha512-OTaW6v2ErKTlguFDNi4PN+qrEFRA4TIEyFag4204CiFIpAB+13PqQiW4Ovt29aYlLERIjWD++PyoHeSdLSiZwg==", + "license": "MIT", + "dependencies": { + "@expo/prebuild-config": "^8.0.17" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-status-bar": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-2.0.0.tgz", diff --git a/package.json b/package.json index a87f186..922dd69 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", "expo": "^52.0.8", + "expo-splash-screen": "^0.29.13", "expo-status-bar": "~2.0.0", "he": "^1.2.0", "i18n-js": "^4.5.0", -- GitLab From e8a990659701552482a01bb43d4b846ef23a4660 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 5 Dec 2024 17:37:07 +0100 Subject: [PATCH 091/283] feat: add initial value in form create quiz --- components/PlayQuiz/InputPlayQuiz.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/PlayQuiz/InputPlayQuiz.tsx b/components/PlayQuiz/InputPlayQuiz.tsx index 4df76b8..715165f 100644 --- a/components/PlayQuiz/InputPlayQuiz.tsx +++ b/components/PlayQuiz/InputPlayQuiz.tsx @@ -21,6 +21,9 @@ export default function InputPlayQuiz({ title, setText, noTitle, placeholder, nu onChangeText={(text) => { setText(text); }} + aria-valuemin={1} + aria-valuemax={50} + defaultValue="10" /> </View> ); -- GitLab From c1074efead44706d5daef58aa79d91e5e15ae0ba Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 5 Dec 2024 17:48:14 +0100 Subject: [PATCH 092/283] feat: add the file to create an apk --- app.json | 5 +++++ eas.json | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 eas.json diff --git a/app.json b/app.json index d7e9736..c930458 100644 --- a/app.json +++ b/app.json @@ -23,6 +23,11 @@ }, "web": { "favicon": "./assets/favicon.png" + }, + "extra": { + "eas": { + "projectId": "8ee47c40-1e74-451c-972a-e1cc75ab11ff" + } } } } diff --git a/eas.json b/eas.json new file mode 100644 index 0000000..5da8fba --- /dev/null +++ b/eas.json @@ -0,0 +1,21 @@ +{ + "cli": { + "version": ">= 13.4.2", + "appVersionSource": "remote" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal" + }, + "preview": { + "distribution": "internal" + }, + "production": { + "autoIncrement": true + } + }, + "submit": { + "production": {} + } +} -- GitLab From 1c92a859d1d087b878bd1aeabfafdcd443d6c797 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 22:16:16 +0100 Subject: [PATCH 093/283] feat: user services --- helper/UserHelper.ts | 10 + models/UserModel.ts | 11 +- package-lock.json | 503 ++++++++++++++++++++-------------------- package.json | 8 +- services/UserService.ts | 119 ++++++++++ 5 files changed, 396 insertions(+), 255 deletions(-) create mode 100644 helper/UserHelper.ts create mode 100644 services/UserService.ts diff --git a/helper/UserHelper.ts b/helper/UserHelper.ts new file mode 100644 index 0000000..123b556 --- /dev/null +++ b/helper/UserHelper.ts @@ -0,0 +1,10 @@ +import UserModel from "../models/UserModel"; + +export const transformToUserModel = (data: any): UserModel => { + const user: UserModel = { + id: data.id, + email: data.email, + username: data.username, + } + return user; +}; \ No newline at end of file diff --git a/models/UserModel.ts b/models/UserModel.ts index f206e7d..66ecffe 100644 --- a/models/UserModel.ts +++ b/models/UserModel.ts @@ -1,12 +1,11 @@ export default class UserModel { id: number; - name: string; - token: string; - - constructor(id: number, name: string, token: string) { + username: string; + email: string; + constructor(id: number, username: string, email: string) { this.id = id; - this.name = name; - this.token = token; + this.username = username; + this.email = email; } } diff --git a/package-lock.json b/package-lock.json index f44f12d..f808720 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,20 +8,21 @@ "name": "vili", "version": "1.0.0", "dependencies": { - "@react-native-async-storage/async-storage": "^2.1.0", + "@react-native-async-storage/async-storage": "1.23.1", + "@react-native-cookies/cookies": "^6.2.1", "@react-native-picker/picker": "^2.9.0", "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", - "expo": "^52.0.8", - "expo-splash-screen": "^0.29.13", + "axios": "^1.7.9", + "expo": "~52.0.14", "expo-status-bar": "~2.0.0", "he": "^1.2.0", "i18n-js": "^4.5.0", "i18next": "^23.16.5", "react": "^18.3.1", "react-i18next": "^15.1.1", - "react-native": "^0.76.2", + "react-native": "0.76.3", "react-native-dropdown-select-list": "^2.0.5", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", @@ -116,13 +117,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -143,20 +144,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", - "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", @@ -195,13 +182,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", - "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.1.1", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -212,9 +199,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -325,19 +312,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", - "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", @@ -492,12 +466,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -1230,13 +1204,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", - "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { @@ -1390,14 +1363,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", - "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-simple-access": "^7.25.9" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1876,9 +1848,9 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz", - "integrity": "sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.3.tgz", + "integrity": "sha512-6+5hpdr6mETwSKjmJUdYw0EIkATiQhnELWlE3kJFBwSg/BGIVwVaVbX+gOXBCdc7Ln1RXZxyWGecIXhUfnl7oA==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", @@ -2077,9 +2049,9 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.25.9.tgz", - "integrity": "sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", + "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", @@ -2180,16 +2152,16 @@ }, "node_modules/@babel/traverse--for-generate-function-map": { "name": "@babel/traverse", - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.3.tgz", + "integrity": "sha512-yTmc8J+Sj8yLzwr4PD5Xb/WF3bOYu2C2OoSZPzbuqRm4n98XirsbzaX+GloeO376UnSYIYJ4NCanwV5/ugZkwA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2198,9 +2170,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -2223,16 +2195,16 @@ } }, "node_modules/@expo/cli": { - "version": "0.21.6", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.21.6.tgz", - "integrity": "sha512-xGeQUK/IX5Wzg2ngXV89wL2s1tWaMJR+sxF8HgGwJqFshC0ikQSV0TRCtz4SCdDjfM7dTPkf4UvOSA45zUMXmA==", + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.22.1.tgz", + "integrity": "sha512-tpkFzlTYb/sNh8WqeTCa2ZVKMDkPgMggvY3tZeTfxeAO5olVXddKV+0U5zhHSd7pGM2w2fhEJNKI4Ub7IfyDmA==", "license": "MIT", "dependencies": { "@0no-co/graphql.web": "^1.0.8", "@babel/runtime": "^7.20.0", "@expo/code-signing-certificates": "^0.0.5", "@expo/config": "~10.0.4", - "@expo/config-plugins": "~9.0.3", + "@expo/config-plugins": "~9.0.10", "@expo/devcert": "^1.1.2", "@expo/env": "~0.4.0", "@expo/image-utils": "^0.6.0", @@ -2241,11 +2213,11 @@ "@expo/osascript": "^2.0.31", "@expo/package-manager": "^1.5.0", "@expo/plist": "^0.2.0", - "@expo/prebuild-config": "^8.0.16", + "@expo/prebuild-config": "^8.0.17", "@expo/rudder-sdk-node": "^1.1.1", "@expo/spawn-async": "^1.7.2", "@expo/xcpretty": "^4.3.0", - "@react-native/dev-middleware": "0.76.2", + "@react-native/dev-middleware": "0.76.3", "@urql/core": "^5.0.6", "@urql/exchange-retry": "^1.3.0", "accepts": "^1.3.8", @@ -2371,13 +2343,13 @@ } }, "node_modules/@expo/config": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-10.0.4.tgz", - "integrity": "sha512-pkvdPqKTaP6+Qvc8aTmDLQ9Dfwp98P1GO37MFKwsF5XormfN/9/eN8HfIRoM6d3uSIVKCcWW3X2yAEbNmOyfXw==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-10.0.6.tgz", + "integrity": "sha512-xXkfPElrtxznkOZxFASJ7OPa6E9IHSjcZwj5BQ6XUF2dz5M7AFa2h5sXM8AalSaDU5tEBSgoUOjTh5957TlR8g==", "license": "MIT", "dependencies": { "@babel/code-frame": "~7.10.4", - "@expo/config-plugins": "~9.0.0", + "@expo/config-plugins": "~9.0.10", "@expo/config-types": "^52.0.0", "@expo/json-file": "^9.0.0", "deepmerge": "^4.3.1", @@ -2392,9 +2364,9 @@ } }, "node_modules/@expo/config-plugins": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-9.0.9.tgz", - "integrity": "sha512-pbgbY3SwCMwkijhfe163J05BrTx4MqzeaV+nVgUMs7vRcjHY1tfM57Pdv6SPtgeDvZ8fvdXFXXzkJva+a7C9Bw==", + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-9.0.11.tgz", + "integrity": "sha512-zufuPQWkeEpXfMWFx2lWStoN43p6cO13p8n2KMIEK6jJMC/kkfldYyl8gYtEEYAL1nFfOf/W2pIXXPQ2sggnSw==", "license": "MIT", "dependencies": { "@expo/config-types": "^52.0.0", @@ -2627,9 +2599,9 @@ } }, "node_modules/@expo/fingerprint": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.11.2.tgz", - "integrity": "sha512-WPibADqymGSKkNNnrGfw4dRipz7F8DwMSv7zb6T9oTGtdRiObrUpGmtBXmvo6z9MqWkNRprEJNxPjvkkvMvwhQ==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.11.3.tgz", + "integrity": "sha512-9lgXmcIePvZ7Wef63XtvuN3HfCUevF4E4tQPdEbH9/dUWwpOvvwQ3KT4OJ9jdh8JJ3nTdO9eDQ/8k8xr1aQ5Kg==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", @@ -2755,9 +2727,9 @@ } }, "node_modules/@expo/metro-config": { - "version": "0.19.4", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.19.4.tgz", - "integrity": "sha512-2SWwYN8MZvMIRawWEr+1RBYncitPwu2VMACRYig+wBycJ9fsPb6BMVmBYi+3MHDUlJHNy/Bqfw++jn1eqBFETQ==", + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.19.5.tgz", + "integrity": "sha512-wl5lVgXq4FN4kBJHNyU5U9J5hH8S8rYXrp/pgbwA+J/smQfiElYKMYomTGbHUb4LQ0VnmlX6/kI4x/zJk+mq7w==", "license": "MIT", "dependencies": { "@babel/core": "^7.20.0", @@ -2911,17 +2883,17 @@ } }, "node_modules/@expo/prebuild-config": { - "version": "8.0.17", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.0.17.tgz", - "integrity": "sha512-HM+XpDox3fAZuXZXvy55VRcBbsZSDijGf8jI8i/pexgWvtsnt1ouelPXRuE1pXDicMX+lZO83QV+XkyLmBEXYQ==", + "version": "8.0.21", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.0.21.tgz", + "integrity": "sha512-PVvt7+2dLzmf1X4HaoibnTtfoxnor0YEdu396eLv1SG+KacmN5lMz81yO/2MXvv0SDA6THomgBNvA/uzWV5twA==", "license": "MIT", "dependencies": { "@expo/config": "~10.0.4", - "@expo/config-plugins": "~9.0.0", + "@expo/config-plugins": "~9.0.10", "@expo/config-types": "^52.0.0", "@expo/image-utils": "^0.6.0", "@expo/json-file": "^9.0.0", - "@react-native/normalize-colors": "0.76.2", + "@react-native/normalize-colors": "0.76.3", "debug": "^4.3.1", "fs-extra": "^9.0.0", "resolve-from": "^5.0.0", @@ -3023,9 +2995,9 @@ } }, "node_modules/@expo/xcpretty": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.3.1.tgz", - "integrity": "sha512-sqXgo1SCv+j4VtYEwl/bukuOIBrVgx6euIoCat3Iyx5oeoXwEA2USCoeL0IPubflMxncA2INkqJ/Wr3NGrSgzw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.3.2.tgz", + "integrity": "sha512-ReZxZ8pdnoI3tP/dNnJdnmAk7uLT4FjsKDGW7YeDdvdOMz2XCQSmSCM9IWlrXuWtMF9zeSB6WJtEhCQ41gQOfw==", "license": "BSD-3-Clause", "dependencies": { "@babel/code-frame": "7.10.4", @@ -3486,15 +3458,27 @@ } }, "node_modules/@react-native-async-storage/async-storage": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.0.tgz", - "integrity": "sha512-eAGQGPTAuFNEoIQSB5j2Jh1zm5NPyBRTfjRMfCN0W1OakC5WIB5vsDyIQhUweKN9XOE2/V07lqTMGsL0dGXNkA==", + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.23.1.tgz", + "integrity": "sha512-Qd2kQ3yi6Y3+AcUlrHxSLlnBvpdCEMVGFlVBneVOjaFaPU61g1huc38g339ysXspwY1QZA2aNhrk/KlHGO+ewA==", "license": "MIT", "dependencies": { "merge-options": "^3.0.4" }, "peerDependencies": { - "react-native": "^0.0.0-0 || >=0.65 <1.0" + "react-native": "^0.0.0-0 || >=0.60 <1.0" + } + }, + "node_modules/@react-native-cookies/cookies": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@react-native-cookies/cookies/-/cookies-6.2.1.tgz", + "integrity": "sha512-D17wCA0DXJkGJIxkL74Qs9sZ3sA+c+kCoGmXVknW7bVw/W+Vv1m/7mWTNi9DLBZSRddhzYw8SU0aJapIaM/g5w==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react-native": ">= 0.60.2" } }, "node_modules/@react-native-picker/picker": { @@ -3508,30 +3492,30 @@ } }, "node_modules/@react-native/assets-registry": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.2.tgz", - "integrity": "sha512-0CTWv/FqJzU1vsyx2JpCkyLSUOePU7DdKgFvtHdwOxFpOw3aBecszqZDGJADYV9WSZQlq6RV0HmIaWycGYCOMA==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.3.tgz", + "integrity": "sha512-7Fnc3lzCFFpnoyL1egua6d/qUp0KiIpeSLbfOMln4nI2g2BMzyFHdPjJnpLV2NehmS0omOOkrfRqK5u1F/MXzA==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.76.2.tgz", - "integrity": "sha512-a1IfRho/ZUVbvzSu3JWkxsvqyEI7IXApPQikhGWw4e24QYsIYHdlIULs3rb0840lqpO1dbbuudfO7lmkpkbkMg==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.76.3.tgz", + "integrity": "sha512-mZ7jmIIg4bUnxCqY3yTOkoHvvzsDyrZgfnIKiTGm5QACrsIGa5eT3pMFpMm2OpxGXRDrTMsYdPXE2rCyDX52VQ==", "license": "MIT", "dependencies": { - "@react-native/codegen": "0.76.2" + "@react-native/codegen": "0.76.3" }, "engines": { "node": ">=18" } }, "node_modules/@react-native/babel-preset": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.76.2.tgz", - "integrity": "sha512-/kbxZqy70mGONv23uZg7lm7ZCE4dO5dgMzVPz6QsveXIRHQBRLsSC+9w2iZEnYWpLayoWFmTbq8ZG+4W32D3bA==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.76.3.tgz", + "integrity": "sha512-zi2nPlQf9q2fmfPyzwWEj6DU96v8ziWtEfG7CTAX2PG/Vjfsr94vn/wWrCdhBVvLRQ6Kvd/MFAuDYpxmQwIiVQ==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -3575,7 +3559,7 @@ "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", - "@react-native/babel-plugin-codegen": "0.76.2", + "@react-native/babel-plugin-codegen": "0.76.3", "babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" @@ -3588,9 +3572,9 @@ } }, "node_modules/@react-native/codegen": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.76.2.tgz", - "integrity": "sha512-rIgdI5mHHnNTzAeDYH+ivKMIcv6vr04Ol+TmX77n1HjJkzMhQqSHWcX+Pq9oiu7l2zKkymadrw6OPD8VPgre8g==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.76.3.tgz", + "integrity": "sha512-oJCH/jbYeGmFJql8/y76gqWCCd74pyug41yzYAjREso1Z7xL88JhDyKMvxEnfhSdMOZYVl479N80xFiXPy3ZYA==", "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", @@ -3610,13 +3594,13 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.76.2.tgz", - "integrity": "sha512-ZRL8oTGSMwXqTsVkRL9AVW8C/AZRnxCcFfhestsx//SrQt3J/hbtDOHTIGkkt5AEA0zEvb/UAAyIAN/wuN4llw==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.76.3.tgz", + "integrity": "sha512-vgsLixHS24jR0d0QqPykBWFaC+V8x9cM3cs4oYXw3W199jgBNGP9MWcUJLazD2vzrT/lUTVBVg0rBeB+4XR6fg==", "license": "MIT", "dependencies": { - "@react-native/dev-middleware": "0.76.2", - "@react-native/metro-babel-transformer": "0.76.2", + "@react-native/dev-middleware": "0.76.3", + "@react-native/metro-babel-transformer": "0.76.3", "chalk": "^4.0.0", "execa": "^5.1.1", "invariant": "^2.2.4", @@ -3735,22 +3719,22 @@ } }, "node_modules/@react-native/debugger-frontend": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.76.2.tgz", - "integrity": "sha512-FIcz24Oya2wIO7rZD3dxVyK8t5ZD6Fojl9o7lrjnTWqMedcevRTtdSOIAf4ypksYH/x7HypovE2Zp8U65Xv0Mw==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.76.3.tgz", + "integrity": "sha512-pMHQ3NpPB28RxXciSvm2yD+uDx3pkhzfuWkc7VFgOduyzPSIr0zotUiOJzsAtrj8++bPbOsAraCeQhCqoOTWQw==", "license": "BSD-3-Clause", "engines": { "node": ">=18" } }, "node_modules/@react-native/dev-middleware": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.76.2.tgz", - "integrity": "sha512-qiowXpxofLk0lpIZps7fyyp9NiKlqBwh0R0yVub5l4EJcqjLonjsznYAHbusnPW9kb9MQSdovGPNv5b8RadJww==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.76.3.tgz", + "integrity": "sha512-b+2IpW40z1/S5Jo5JKrWPmucYU/PzeGyGBZZ/SJvmRnBDaP3txb9yIqNZAII1EWsKNhedh8vyRO5PSuJ9Juqzw==", "license": "MIT", "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.76.2", + "@react-native/debugger-frontend": "0.76.3", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", @@ -3790,31 +3774,31 @@ } }, "node_modules/@react-native/gradle-plugin": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.76.2.tgz", - "integrity": "sha512-KC5/uAeLoeD1dOjymx6gnNFHGGLB22xNYjrjrJNK5r0bw2O2KXp4rpB5VCT/2H5B48cVC0xPB7RIKOFrDHr5bQ==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.76.3.tgz", + "integrity": "sha512-t0aYZ8ND7+yc+yIm6Yp52bInneYpki6RSIFZ9/LMUzgMKvEB62ptt/7sfho9QkKHCNxE1DJSWIqLIGi/iHHkyg==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@react-native/js-polyfills": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.76.2.tgz", - "integrity": "sha512-OXunyNn33fa7gQ6iU5rQcYZQsO7OkJIAr/TgVdoHxpOB4i+ZGsfv6df3JKriBVT1ZZm6ZTlKyIa4QpLq3p0dmw==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.76.3.tgz", + "integrity": "sha512-pubJFArMMrdZiytH+W95KngcSQs+LsxOBsVHkwgMnpBfRUxXPMK4fudtBwWvhnwN76Oe+WhxSq7vOS5XgoPhmw==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@react-native/metro-babel-transformer": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.76.2.tgz", - "integrity": "sha512-OIYhmWfN+HDyQLzoEg+2P0h7OopYk4djggg0M+k5e1a+g2dFNJILO/BsDobM8uLA8hAzClAJyJLZbPo5jeqdMA==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.76.3.tgz", + "integrity": "sha512-b2zQPXmW7avw/7zewc9nzMULPIAjsTwN03hskhxHUJH5pzUf7pIklB3FrgYPZrRhJgzHiNl3tOPu7vqiKzBYPg==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.76.2", + "@react-native/babel-preset": "0.76.3", "hermes-parser": "0.23.1", "nullthrows": "^1.1.1" }, @@ -3826,15 +3810,15 @@ } }, "node_modules/@react-native/normalize-colors": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.76.2.tgz", - "integrity": "sha512-ICoOpaTLPsFQjNLSM00NgQr6wal300cZZonHVSDXKntX+BfkLeuCHRtr/Mn+klTtW+/1v2/2FRm9dXjvyGf9Dw==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.76.3.tgz", + "integrity": "sha512-Yrpmrh4IDEupUUM/dqVxhAN8QW1VEUR3Qrk2lzJC1jB2s46hDe0hrMP2vs12YJqlzshteOthjwXQlY0TgIzgbg==", "license": "MIT" }, "node_modules/@react-native/virtualized-lists": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.76.2.tgz", - "integrity": "sha512-FzXvkHgKvJGf0pSuLy6878cxJ6mxWKgZsH9s2kO4LWJocI8Bi3ViDx7IGAWYuvN+Fnue5TKaqGPhfD+4XrKtYQ==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.76.3.tgz", + "integrity": "sha512-wTGv9pVh3vAOWb29xFm+J9VRe9dUcUcb9FyaMLT/Hxa88W4wqa5ZMe1V9UvrrBiA1G5DKjv8/1ZcDsJhyugVKA==", "license": "MIT", "dependencies": { "invariant": "^2.2.4", @@ -4363,6 +4347,31 @@ "node": ">= 4.0.0" } }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -4425,13 +4434,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", + "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", + "@babel/helper-define-polyfill-provider": "^0.6.3", "semver": "^6.3.1" }, "peerDependencies": { @@ -4452,12 +4461,12 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" + "@babel/helper-define-polyfill-provider": "^0.6.3" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -4529,9 +4538,9 @@ } }, "node_modules/babel-preset-expo": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-12.0.1.tgz", - "integrity": "sha512-9T2o+aeKnHOtQhk/undQbibJv02bdCgfs68ZwgAdueljDBcs2oVfq41qG9XThYwa6Dn7CdfnoEUsIyFqBwjcVw==", + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-12.0.3.tgz", + "integrity": "sha512-1695e8y3U/HjifKx33vcNnFMSUSXwPWwhFxRlL6NRx2TENN6gySH82gPOWgxcra6gi+EJgXx52xG3PcqTjwW6w==", "license": "MIT", "dependencies": { "@babel/plugin-proposal-decorators": "^7.12.9", @@ -4540,7 +4549,7 @@ "@babel/plugin-transform-parameters": "^7.22.15", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-preset": "0.76.2", + "@react-native/babel-preset": "0.76.3", "babel-plugin-react-native-web": "~0.19.13", "react-refresh": "^0.14.2" }, @@ -5078,6 +5087,15 @@ "node": ">=8" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -5422,15 +5440,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/defaults/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -5782,26 +5791,26 @@ } }, "node_modules/expo": { - "version": "52.0.8", - "resolved": "https://registry.npmjs.org/expo/-/expo-52.0.8.tgz", - "integrity": "sha512-EQwJFRS2UUTGTe++bcLN3b7TeSpSWhQzIPt4jIh+0iannbYxgXLE6oKApTJhh0dsMWDnZzU5a8DIdadJY8VZJA==", + "version": "52.0.15", + "resolved": "https://registry.npmjs.org/expo/-/expo-52.0.15.tgz", + "integrity": "sha512-7eGv8y/aslaHvKH8XVvx0XpvJIQiJZ63kBHcdjtAmy+7KCMQp4lwmrnCzYhVdROe1i/GmHF2UwYfHXIYgC1WEw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "0.21.6", - "@expo/config": "~10.0.4", - "@expo/config-plugins": "9.0.9", - "@expo/fingerprint": "0.11.2", - "@expo/metro-config": "0.19.4", + "@expo/cli": "0.22.1", + "@expo/config": "~10.0.6", + "@expo/config-plugins": "~9.0.11", + "@expo/fingerprint": "0.11.3", + "@expo/metro-config": "0.19.5", "@expo/vector-icons": "^14.0.0", - "babel-preset-expo": "~12.0.1", + "babel-preset-expo": "~12.0.3", "expo-asset": "~11.0.1", "expo-constants": "~17.0.3", "expo-file-system": "~18.0.4", "expo-font": "~13.0.1", "expo-keep-awake": "~14.0.1", - "expo-modules-autolinking": "2.0.2", - "expo-modules-core": "2.0.4", + "expo-modules-autolinking": "2.0.3", + "expo-modules-core": "2.1.1", "fbemitter": "^3.0.0", "web-streams-polyfill": "^3.3.2", "whatwg-url-without-unicode": "8.0.0-3" @@ -5896,9 +5905,9 @@ } }, "node_modules/expo-modules-autolinking": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.0.2.tgz", - "integrity": "sha512-n3jC7VoJLfOLGk8NWhEAvM5zSjbLh1kMUSo76nJupx5/vASxDdzihppYebrKrNXPHq5mcw8Jr+r7YB+8xHx7QQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.0.3.tgz", + "integrity": "sha512-Q/ALJ54eS7Cr7cmbP+unEDTkHFQivQerWWrqZxuXOrSFYGCYU22+/xAZXaJOpZwseOVsP74zSkoRY/wBimVs7w==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", @@ -5951,26 +5960,14 @@ } }, "node_modules/expo-modules-core": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-2.0.4.tgz", - "integrity": "sha512-nNS40KYh1d7tWXCcEKBrSigIKCVfJwkPLhR/mniAoPzqevUDLVJNJjIgKfQL6kPlsViC3hwwgrUpKSlmWv2DFg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-2.1.1.tgz", + "integrity": "sha512-yQzYCLR2mre4BNMXuqkeJ0oSNgmGEMI6BcmIzeNZbC2NFEjiaDpKvlV9bclYCtyVhUEVNbJcEPYMr6c1Y4eR4w==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" } }, - "node_modules/expo-splash-screen": { - "version": "0.29.13", - "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.29.13.tgz", - "integrity": "sha512-OTaW6v2ErKTlguFDNi4PN+qrEFRA4TIEyFag4204CiFIpAB+13PqQiW4Ovt29aYlLERIjWD++PyoHeSdLSiZwg==", - "license": "MIT", - "dependencies": { - "@expo/prebuild-config": "^8.0.17" - }, - "peerDependencies": { - "expo": "*" - } - }, "node_modules/expo-status-bar": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-2.0.0.tgz", @@ -6160,14 +6157,34 @@ "license": "MIT" }, "node_modules/flow-parser": { - "version": "0.252.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.252.0.tgz", - "integrity": "sha512-z8hKPUjZ33VLn4HVntifqmEhmolUMopysnMNzazoDqo1GLUkBsreLNsxETlKJMPotUWStQnen6SGvUNe1j4Hlg==", + "version": "0.255.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.255.0.tgz", + "integrity": "sha512-7QHV2m2mIMh6yIMaAPOVbyNEW77IARwO69d4DgvfDCjuORiykdMLf7XBjF7Zeov7Cpe1OXJ8sB6/aaCE3xuRBw==", "license": "MIT", "engines": { "node": ">=0.4.0" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/fontfaceobserver": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", @@ -8125,6 +8142,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -9116,6 +9145,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -9298,19 +9333,19 @@ "license": "MIT" }, "node_modules/react-native": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.76.2.tgz", - "integrity": "sha512-mkEBKGOmJxhfq8IOsvmk0QuTzlBt9vS+uo0gwbqfUmEDqoC359v80zhUf94WimYBrBkpRQWFbEu5iqMDHrYzlQ==", + "version": "0.76.3", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.76.3.tgz", + "integrity": "sha512-0TUhgmlouRNf6yuDIIAdbQl0g1VsONgCMsLs7Et64hjj5VLMCA7np+4dMrZvGZ3wRNqzgeyT9oWJsUm49AcwSQ==", "license": "MIT", "dependencies": { "@jest/create-cache-key-function": "^29.6.3", - "@react-native/assets-registry": "0.76.2", - "@react-native/codegen": "0.76.2", - "@react-native/community-cli-plugin": "0.76.2", - "@react-native/gradle-plugin": "0.76.2", - "@react-native/js-polyfills": "0.76.2", - "@react-native/normalize-colors": "0.76.2", - "@react-native/virtualized-lists": "0.76.2", + "@react-native/assets-registry": "0.76.3", + "@react-native/codegen": "0.76.3", + "@react-native/community-cli-plugin": "0.76.3", + "@react-native/gradle-plugin": "0.76.3", + "@react-native/js-polyfills": "0.76.3", + "@react-native/normalize-colors": "0.76.3", + "@react-native/virtualized-lists": "0.76.3", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -9602,15 +9637,15 @@ } }, "node_modules/regexpu-core": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", - "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "license": "MIT", "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.0", "regjsgen": "^0.8.0", - "regjsparser": "^0.11.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -9625,9 +9660,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.2.tgz", - "integrity": "sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.0.2" @@ -9715,9 +9750,9 @@ "license": "MIT" }, "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", "license": "MIT", "engines": { "node": ">=10" @@ -9890,18 +9925,6 @@ "node": ">= 0.8" } }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/send/node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -9971,18 +9994,6 @@ "node": ">= 0.8" } }, - "node_modules/serve-static/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/serve-static/node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -10752,9 +10763,9 @@ } }, "node_modules/terser": { - "version": "5.36.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", - "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", + "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", diff --git a/package.json b/package.json index 922dd69..0b14e67 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,14 @@ "web": "expo start --web" }, "dependencies": { - "@react-native-async-storage/async-storage": "^2.1.0", + "@react-native-async-storage/async-storage": "1.23.1", + "@react-native-cookies/cookies": "^6.2.1", "@react-native-picker/picker": "^2.9.0", "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", - "expo": "^52.0.8", + "axios": "^1.7.9", + "expo": "~52.0.14", "expo-splash-screen": "^0.29.13", "expo-status-bar": "~2.0.0", "he": "^1.2.0", @@ -22,7 +24,7 @@ "i18next": "^23.16.5", "react": "^18.3.1", "react-i18next": "^15.1.1", - "react-native": "^0.76.2", + "react-native": "0.76.3", "react-native-dropdown-select-list": "^2.0.5", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", diff --git a/services/UserService.ts b/services/UserService.ts new file mode 100644 index 0000000..756189d --- /dev/null +++ b/services/UserService.ts @@ -0,0 +1,119 @@ +import HttpError from "./HttpError"; +import UserModel from "../models/UserModel"; +import axios from "axios"; +import {transformToUserModel} from "../helper/UserHelper"; + +export const useUserService = () => { + const url = "https://klebert-host.com:33037"; + + + const loginUser = async (login: string, password: string): Promise<boolean | HttpError> => { + try { + const requestBody = { + login: login, + password: password, + }; + + await axios.post(`${url}/auth/login`, requestBody, { + withCredentials: true, // Autorise Axios à inclure les cookies + }); + + return true; + } catch (error: any) { + + if (error.response) { + // Si le mot de passe ou le login est incorrect + if (error.response.status === 401) { + return new HttpError(401, "Invalid login or password"); + } + + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + // Erreur réseau ou autre + return new HttpError(500, "Unexpected error: " + error.message); + } + } + }; + + const getInformationsUser = async (): Promise<UserModel | HttpError> => { + try { + const response = await axios.get(`${url}/user/me`, { + withCredentials: true, // Inclut les cookies dans la requête + }); + + const user = transformToUserModel(response.data); + + return user; + } catch (error: any) { + if (error.response.status === 401) { + return new HttpError(401, "No user logged in"); + } + + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + }; + + const logoutUser = async (): Promise<boolean | HttpError> => { + try { + await axios.post(`${url}/auth/logout`, null, { + withCredentials: true, // Inclut les cookies dans la requête + }); + return true; + } catch (error: any) { + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + } + + const registerUser = async ( + username: string, + password: string, + email: string + ): Promise<boolean | HttpError> => { + try { + const requestBody = { + username: username, + email: email, + password: password, + }; + + // Enregistrement de l'utilisateur + await axios.post(`${url}/user`, requestBody, { + withCredentials: true, + }); + + + // Connexion après enregistrement + const loginResult = await loginUser(username, password); + + if (loginResult instanceof HttpError) { + return loginResult; // Retourne l'erreur de connexion si échec + } + + return true; // Succès + } catch (error: any) { + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + }; + + + + + return { + loginUser: loginUser, + getInformationsUser: getInformationsUser, + logoutUser: logoutUser, + registerUser: registerUser + }; +} -- GitLab From 8db14d0418711346295fa5f020cf75a9fd5fa286 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 5 Dec 2024 23:08:33 +0100 Subject: [PATCH 094/283] fix: initial value input --- components/PlayQuiz/InputPlayQuiz.tsx | 7 +++---- screens/PlayQuiz/PlayQuizGenerateQuiz.tsx | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/components/PlayQuiz/InputPlayQuiz.tsx b/components/PlayQuiz/InputPlayQuiz.tsx index 715165f..720f523 100644 --- a/components/PlayQuiz/InputPlayQuiz.tsx +++ b/components/PlayQuiz/InputPlayQuiz.tsx @@ -7,9 +7,10 @@ interface Props { noTitle?: boolean; placeholder?: string; numeric?: boolean; + defaultValue?: string; } -export default function InputPlayQuiz({ title, setText, noTitle, placeholder, numeric }: Props) { +export default function InputPlayQuiz({ title, setText, noTitle, placeholder, numeric, defaultValue }: Props) { return ( <View style={styles.container}> {!noTitle && title && <Text style={styles.title}>{title}</Text>} @@ -21,9 +22,7 @@ export default function InputPlayQuiz({ title, setText, noTitle, placeholder, nu onChangeText={(text) => { setText(text); }} - aria-valuemin={1} - aria-valuemax={50} - defaultValue="10" + defaultValue={defaultValue} /> </View> ); diff --git a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx index b4dbe30..4b9bc60 100644 --- a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx +++ b/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx @@ -36,7 +36,7 @@ interface Props { export default function PlayQuizGenerateQuiz({navigation}: Props) { const [difficutlyChoose, setDifficutlyChoose] = React.useState(""); const [categoryChoose, setCategoryChoose] = React.useState(""); - const [nbQuestions, setNbQuestions] = React.useState(""); + const [nbQuestions, setNbQuestions] = React.useState("10"); const [buttonIsDisabled, setButtonIsDisabled] = React.useState(true); const {generateQuiz} = useQuizService(); @@ -71,7 +71,7 @@ export default function PlayQuizGenerateQuiz({navigation}: Props) { <View style={styles.fieldContainer}> <SelectListBox title="CHOOSE A DIFFICULTY" data={difficulty} setSelected={setDifficutlyChoose} /> <SelectListBox title="CHOOSE A CATEGORY" data={categories} setSelected={setCategoryChoose} /> - <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={setNbQuestions} numeric={true} /> + <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={setNbQuestions} numeric={true} defaultValue={nbQuestions}/> </View> <View style={styles.buttonPlayContainer}> <BlueButton onPress={handlePlayButtonPress} text={"PLAY"} isDisabled={buttonIsDisabled}/> -- GitLab From b27bc0f60e2cd0bb773d7df13817ace11e675ef0 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 5 Dec 2024 23:15:43 +0100 Subject: [PATCH 095/283] feat: Community service --- screens/Community/Community.tsx | 23 +++++++++++++++++++++-- services/QuizService.ts | 22 +++++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/screens/Community/Community.tsx b/screens/Community/Community.tsx index ec8bfa5..7addc7d 100644 --- a/screens/Community/Community.tsx +++ b/screens/Community/Community.tsx @@ -1,13 +1,18 @@ import {View, Text} from "react-native"; import TemplateQuizList from "../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; -import {useState} from "react"; +import {useEffect, useState} from "react"; import QuizModel from "../../models/QuizModel"; +import {useQuizService} from "../../services/QuizService"; +import HttpError from "../../services/HttpError"; interface Props { navigation: NavigationProp<any> } export default function Community({navigation}: Props) { + const {getQuizzesCommunity} = useQuizService(); + const [input, setInput] = useState<string>("") + const [quizList, setQuizList] = useState<QuizModel[] | null>([ { code: "QUIZ123", @@ -34,7 +39,21 @@ export default function Community({navigation}: Props) { score: 0, }, ]) - const [input, setInput] = useState<string>("") + + useEffect( () => { + console.log("useEffect"); + getQuizzesCommunity().then((quizzes) => { + if(quizzes instanceof HttpError){ + console.log(quizzes); + return; + } + if(quizzes instanceof Array) { + console.log("array"); + setQuizList(quizzes) + } + }); + + }, []) return ( diff --git a/services/QuizService.ts b/services/QuizService.ts index 6248af7..c5fbe58 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -3,6 +3,7 @@ import {transformToAnswerModel, transformToQuizModel} from "../helper/QuizHelper import AnswerModel from "../models/AnswerModel"; import HttpError from "./HttpError"; import QuizModel from "../models/QuizModel"; +import axios from "axios"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API @@ -148,9 +149,28 @@ export const useQuizService = () => { } } + const getQuizzesCommunity = async () : Promise<QuizModel[] | HttpError> => { + try { + // await axios.post(`${url}/quiz/community`, null, { + // withCredentials: true, // Inclut les cookies dans la requête + // }); + return []; + } catch (error: any) { + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + } + + + return { generateQuiz: generateQuiz, getAnswerQuiz: getAnswerQuiz, - remainingQuiz: remainingQuiz + remainingQuiz: remainingQuiz, + getQuizzesCommunity: getQuizzesCommunity + } } \ No newline at end of file -- GitLab From d95a1460c43def737dde629a914c280a1ebf2cb0 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 5 Dec 2024 23:40:48 +0100 Subject: [PATCH 096/283] feat: prepare end quiz set with quiz value --- screens/EndQuiz/EndQuizChild.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/EndQuiz/EndQuizChild.tsx index b811860..d7c072f 100644 --- a/screens/EndQuiz/EndQuizChild.tsx +++ b/screens/EndQuiz/EndQuizChild.tsx @@ -40,8 +40,8 @@ export default function EndQuizChild({ navigation/* , route */}: Props) { <Image source={require('../../assets/TitleApp.png')}/> </View> <View style={styles.containerBody}> - <Text style={styles.TextBodyTitle}>CONGRATS !</Text> - <Text style={styles.TextBody}>you finish the test with a score of 12/20 ! </Text> + <Text style={styles.TextBodyTitle}>{/*((quiz.score/quiz.nbQuestions)*100 > 50 ) ? 'Victory' : 'Defeat'*/'Victory'}</Text> + <Text style={styles.TextBody}>you finish the test with a score of {/*`${quiz.score}/${quiz.nbQuestions}`*/'12/20'} ! </Text> <Image source={require('../../assets/uWin.png')} /> <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton> <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu} buttonStyle={styles.blueButton} buttonText={styles.blueButtonText}></DefaultButton> -- GitLab From 12fc6d9e2c41b3d4f751ac943cadabf6808ff708 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 5 Dec 2024 23:42:59 +0100 Subject: [PATCH 097/283] feat: end quiz with quiz value --- screens/EndQuiz/EndQuizChild.tsx | 45 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/EndQuiz/EndQuizChild.tsx index d7c072f..936e00d 100644 --- a/screens/EndQuiz/EndQuizChild.tsx +++ b/screens/EndQuiz/EndQuizChild.tsx @@ -1,33 +1,34 @@ import { View, Image, Text, StyleSheet } from "react-native"; import DefaultButton from "../../components/DefaultButton"; -import { NavigationProp } from "@react-navigation/native"; +import { NavigationProp, RouteProp } from "@react-navigation/native"; +import QuizModel from "../../models/QuizModel"; -// type RoutePropsType = { -// EndQuiz: { -// quiz: QuizModel; -// }; -// }; +type RoutePropsType = { + EndQuiz: { + quiz: QuizModel; + }; +}; interface Props { navigation: NavigationProp<any>; - // route: RouteProp<RoutePropsType, "EndQuiz">; + route: RouteProp<RoutePropsType, "EndQuiz">; } -export default function EndQuizChild({ navigation/* , route */}: Props) { - // const { quiz } = route.params; +export default function EndQuizChild({ navigation, route }: Props) { + const { quiz } = route.params; const handleRestartPress = () => { - // quiz.nbActualQuestion = 1; - // quiz.score = 0; - // navigation.reset({ - // index: 0, - // routes: [ - // { - // name: "PlayingQuiz", - // params: {quiz: quiz}, - // }, - // ] - // }); + quiz.nbActualQuestion = 1; + quiz.score = 0; + navigation.reset({ + index: 0, + routes: [ + { + name: "PlayingQuiz", + params: {quiz: quiz}, + }, + ] + }); }; const handleBackToMenu = () => { @@ -40,8 +41,8 @@ export default function EndQuizChild({ navigation/* , route */}: Props) { <Image source={require('../../assets/TitleApp.png')}/> </View> <View style={styles.containerBody}> - <Text style={styles.TextBodyTitle}>{/*((quiz.score/quiz.nbQuestions)*100 > 50 ) ? 'Victory' : 'Defeat'*/'Victory'}</Text> - <Text style={styles.TextBody}>you finish the test with a score of {/*`${quiz.score}/${quiz.nbQuestions}`*/'12/20'} ! </Text> + <Text style={styles.TextBodyTitle}>{((quiz.score/quiz.nbQuestions)*100 > 50 ) ? 'Victory' : 'Defeat'}</Text> + <Text style={styles.TextBody}>you finish the test with a score of {`${quiz.score}/${quiz.nbQuestions}`} ! </Text> <Image source={require('../../assets/uWin.png')} /> <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton> <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu} buttonStyle={styles.blueButton} buttonText={styles.blueButtonText}></DefaultButton> -- GitLab From 37b9bfa9ab33f53d62c74b806ca4b7d06c9ee304 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 00:24:41 +0100 Subject: [PATCH 098/283] feat: login and signup with error message --- screens/Profil/ProfilChild.tsx | 2 +- screens/Profil/ProfilModal.tsx | 11 ++++- screens/Profil/ProfilModalLogin.tsx | 60 +++++++++++++++++++++------- screens/Profil/ProfilModalSignup.tsx | 49 ++++++++++++++++++++--- 4 files changed, 98 insertions(+), 24 deletions(-) diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index 17911d9..53b0460 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -27,7 +27,7 @@ export default function ProfilChild({navigation}: Props) { </View> <ProfilHeaderAccount setModalVisible={setModalVisible} setMode={setMode}/> - <ProfilModal modalVisible={modalVisible} setModalVisible={setModalVisible} mode={mode} setMode={setMode}/> + <ProfilModal modalVisible={modalVisible} setModalVisible={setModalVisible} mode={mode} setMode={setMode} navigation={navigation}/> </View> ) } diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/ProfilModal.tsx index bec04df..f0227d7 100644 --- a/screens/Profil/ProfilModal.tsx +++ b/screens/Profil/ProfilModal.tsx @@ -13,12 +13,14 @@ import { ProfilSelectModeType } from "../../models/SelectModeType"; import ProfilModalLogin from "./ProfilModalLogin"; import ProfilModalSignup from "./ProfilModalSignup"; import Icon from "react-native-vector-icons/MaterialIcons"; +import {NavigationProp} from "@react-navigation/native"; interface Props { modalVisible: boolean; setModalVisible: (visible: boolean) => void; mode: ProfilSelectModeType; setMode: (mode: ProfilSelectModeType) => void; + navigation: NavigationProp<any>; } export default function ProfilModal({ @@ -26,7 +28,12 @@ export default function ProfilModal({ setModalVisible, mode, setMode, + navigation }: Props) { + const closeModal = () => { + setModalVisible(false); + } + return ( <Modal animationType="slide" @@ -48,9 +55,9 @@ export default function ProfilModal({ <View style={styles.containerModal}> <ProfilSelectMode mode={mode} setMode={setMode} /> {mode === "login" ? ( - <ProfilModalLogin /> + <ProfilModalLogin navigation={navigation} closeModal={closeModal}/> ) : ( - <ProfilModalSignup /> + <ProfilModalSignup navigation={navigation} closeModal={closeModal}/> )} </View> </View> diff --git a/screens/Profil/ProfilModalLogin.tsx b/screens/Profil/ProfilModalLogin.tsx index 6dacfc4..7a2e74c 100644 --- a/screens/Profil/ProfilModalLogin.tsx +++ b/screens/Profil/ProfilModalLogin.tsx @@ -1,25 +1,46 @@ -import { TextInput, View, StyleSheet } from "react-native"; +import { TextInput, View, StyleSheet, Text } from "react-native"; import DefaultButton from "../../components/DefaultButton"; import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; -import React, {useState} from "react"; - -export default function ProfilModalLogin() { +import React, { useState } from "react"; +import {useUserService} from "../../services/UserService"; +import HttpError from "../../services/HttpError"; +import {NavigationProp} from "@react-navigation/native"; +interface Props { + navigation: NavigationProp<any>; + closeModal: () => void; +} +export default function ProfilModalLogin({navigation, closeModal}: Props) { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); - const handleSubmitPressed = () => { + const [error, setError] = useState<string | null>(null); + const { loginUser } = useUserService(); - } + const handleSubmitPressed = async () => { + if (email === "" || password === "") { + setError("Please fill in all fields"); + return; + } + const login = await loginUser(email, password); + if(login instanceof HttpError){ + setError(login.message); + return; + } + setError(null); + navigation.navigate("Home"); + closeModal(); + }; return ( - <View> + <View style={styles.container}> + {error && <Text style={styles.errorText}>{error}</Text>} <View style={styles.containerForm}> - <InputPlayQuiz placeholder="EMAIL..." setText={setEmail} noTitle={true}/> - <InputPlayQuiz placeholder="PASSWORD..." setText={setPassword} noTitle={true}/> + <InputPlayQuiz placeholder="EMAIL..." setText={setEmail} noTitle={true} /> + <InputPlayQuiz placeholder="PASSWORD..." setText={setPassword} noTitle={true} /> </View> - <DefaultButton text="LOGIN" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> + <DefaultButton text="LOGIN" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText} /> </View> - ) + ); } const styles = StyleSheet.create({ @@ -29,8 +50,11 @@ const styles = StyleSheet.create({ display: 'flex', flexDirection: 'column', justifyContent: 'flex-start', - rowGap: '10%', - marginTop: 50, + rowGap: '7%', + marginTop: "3%", + }, + container:{ + paddingTop: '5%', }, textInput: { height: '20%', @@ -40,7 +64,7 @@ const styles = StyleSheet.create({ borderColor: '#1fa9ff', borderRadius: 10, color: '#DFDFDF', - fontWeight: 'bold' + fontWeight: 'bold', }, SubmitButton: { height: '15%', @@ -53,4 +77,10 @@ const styles = StyleSheet.create({ fontWeight: 'bold', color: '#FFFFFF', }, -}); \ No newline at end of file + errorText: { + color: 'red', + fontSize: 16, + marginBottom: 10, // Espacement entre le texte d'erreur et les inputs + textAlign: 'center', // Centrer le message d'erreur + }, +}); diff --git a/screens/Profil/ProfilModalSignup.tsx b/screens/Profil/ProfilModalSignup.tsx index 3b7b6de..e4becb4 100644 --- a/screens/Profil/ProfilModalSignup.tsx +++ b/screens/Profil/ProfilModalSignup.tsx @@ -1,20 +1,48 @@ -import { TextInput, View, StyleSheet } from "react-native"; +import {TextInput, View, StyleSheet, Text} from "react-native"; import DefaultButton from "../../components/DefaultButton"; import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; import React, {useState} from "react"; +import {useUserService} from "../../services/UserService"; +import HttpError from "../../services/HttpError"; +import {NavigationProp} from "@react-navigation/native"; -export default function ProfilModalSignup() { +interface Props { + navigation: NavigationProp<any>; + closeModal: () => void; +} +export default function ProfilModalSignup({navigation, closeModal}: Props) { const [email, setEmail] = useState(""); + const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [repeatPassword, setRepeatPassword] = useState(""); - const handleSubmitPressed = () => { + const [error, setError] = useState<string | null>(null); + const { registerUser } = useUserService(); + const handleSubmitPressed = () => { + if (email === "" || username === "" || password === "" || repeatPassword === "") { + setError("Please fill in all fields"); + return; + } + if(password !== repeatPassword){ + setError("Passwords do not match"); + return; + } + const register = registerUser(username, password, email); + if(register instanceof HttpError){ + setError(register.message); + return; + } + setError(null); + navigation.navigate("Home"); + closeModal(); } return ( - <View> + <View style={styles.container}> + {error && <Text style={styles.errorText}>{error}</Text>} <View style={styles.containerForm}> + <InputPlayQuiz placeholder="USERNAME..." setText={setUsername} noTitle={true}/> <InputPlayQuiz placeholder="EMAIL..." setText={setEmail} noTitle={true}/> <InputPlayQuiz placeholder="PASSWORD..." setText={setPassword} noTitle={true}/> <InputPlayQuiz placeholder="REPEAT PASSWORD..." setText={setRepeatPassword} noTitle={true}/> @@ -31,8 +59,11 @@ const styles = StyleSheet.create({ display: 'flex', flexDirection: 'column', justifyContent: 'flex-start', - rowGap: '10%', - marginTop: 50 + rowGap: '7%', + marginTop: "3%", + }, + container:{ + paddingTop: '5%', }, textInput: { height: '20%', @@ -55,4 +86,10 @@ const styles = StyleSheet.create({ fontWeight: 'bold', color: '#FFFFFF', }, + errorText: { + color: 'red', + fontSize: 16, + marginBottom: 10, // Espacement entre le texte d'erreur et les inputs + textAlign: 'center', // Centrer le message d'erreur + }, }); \ No newline at end of file -- GitLab From 425c54fe4b55246b62bce6abf9fe87b54290f8d1 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 6 Dec 2024 00:27:18 +0100 Subject: [PATCH 099/283] feat: correct function getRandomQuiz --- services/QuizService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/services/QuizService.ts b/services/QuizService.ts index c5fbe58..548dbf7 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -170,6 +170,7 @@ export const useQuizService = () => { generateQuiz: generateQuiz, getAnswerQuiz: getAnswerQuiz, remainingQuiz: remainingQuiz, + getRandomQuiz: getRandomQuiz, getQuizzesCommunity: getQuizzesCommunity } -- GitLab From 6e7c53acadc85e1d39a71980778b0f4f4225762e Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 6 Dec 2024 00:27:37 +0100 Subject: [PATCH 100/283] feat: set route for quick game --- screens/Home/HomeChild.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index cc792b7..dfe0df0 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -6,6 +6,7 @@ import { TextsStyles } from "../../styles/TextsStyles"; import ModalJoinQuiz from "../../components/ModalJoinQuiz"; import MenuButton from "../../components/buttons/MenuButton"; import {NavigationProp} from "@react-navigation/native"; +import {useQuizService} from "../../services/QuizService"; interface Props { navigation: NavigationProp<any>; @@ -18,9 +19,19 @@ interface Props { export default function HomeChild({navigation}: Props) { const [modalVisible, setModalVisible] = useState(false); const {t} = useTranslation(); + const {getRandomQuiz} = useQuizService(); - const handleButtonQuickGamePressed = () => { - navigation.navigate("GenerateQuiz"); + const handleButtonQuickGamePressed = async () => { + const quizData = await getRandomQuiz() + navigation.reset({ + index: 0, + routes: [ + { + name: "PlayingQuiz", + params: {quizRecovered: quizData}, + }, + ] + }); } const handleButtonMyQuizzesPressed = () => { navigation.navigate("MyQuizzes"); -- GitLab From 5db3818451834147259c98bd31cad37022eb3ca9 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 01:19:37 +0100 Subject: [PATCH 101/283] feat: user name can we see in the profil --- screens/Profil/ProfilChild.tsx | 27 +++++++++++++++++++++------ screens/Profil/ProfilModalLogin.tsx | 4 +++- screens/Profil/ProfilModalSignup.tsx | 4 +++- services/UserService.ts | 2 +- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index 53b0460..555f2de 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -1,9 +1,13 @@ import { NavigationProp } from "@react-navigation/native"; import { View, StyleSheet, Image, Text, Modal, Button, TouchableOpacity } from "react-native"; import ProfilModal from "./ProfilModal"; -import React, { useState } from "react"; +import React, {useEffect, useState} from "react"; import ProfilHeaderAccount from "./ProfilHeaderAccount"; import { ProfilSelectModeType } from "../../models/SelectModeType"; +import UserModel from "../../models/UserModel"; +import {useUserService} from "../../services/UserService"; +import HttpError from "../../services/HttpError"; +import BlueButton from "../../components/PlayQuiz/BlueButton"; interface Props { navigation: NavigationProp<any>; @@ -12,21 +16,32 @@ interface Props { export default function ProfilChild({navigation}: Props) { const [modalVisible, setModalVisible] = useState(false); const [mode, setMode] = useState<ProfilSelectModeType>("login"); + const [user, setUser] = useState<UserModel | null>(null); + const {getInformationsUser, logoutUser} = useUserService(); + + useEffect(() => { + getInformationsUser().then((user: UserModel | HttpError) => { + console.log(user); + if(user instanceof HttpError){ + return + } + setUser(user); + return + }); + }, []); return ( <View style={styles.containerGlobal}> <View style={styles.containerPlayerInfos}> <Image source={require('../../assets/TitleApp.png')} style={styles.titleImage}/> <Image source={require('../../assets/ProfilBaseImage.png')} style={styles.profilImage}/> - <Text style={styles.pseudoText}>_Pseudo_</Text> + <Text style={styles.pseudoText}>{user ? user.username : "?"}</Text> </View> <View style={styles.containerPlayerStats}> - <Text style={styles.statsText}>Played quiz : _123_</Text> - <Text style={styles.statsText}>right answer rate : _43%_</Text> - <Text style={styles.statsText}>Favorit category : _HISTORY AND CULTURE_</Text> + {/*<Text style={styles.statsText}>Played quiz : _123_</Text>*/} </View> - <ProfilHeaderAccount setModalVisible={setModalVisible} setMode={setMode}/> + {!user && <ProfilHeaderAccount setModalVisible={setModalVisible} setMode={setMode}/>} <ProfilModal modalVisible={modalVisible} setModalVisible={setModalVisible} mode={mode} setMode={setMode} navigation={navigation}/> </View> ) diff --git a/screens/Profil/ProfilModalLogin.tsx b/screens/Profil/ProfilModalLogin.tsx index 7a2e74c..aada3c7 100644 --- a/screens/Profil/ProfilModalLogin.tsx +++ b/screens/Profil/ProfilModalLogin.tsx @@ -27,8 +27,10 @@ export default function ProfilModalLogin({navigation, closeModal}: Props) { return; } setError(null); - navigation.navigate("Home"); closeModal(); + navigation.reset({ + routes: [{name: "TabNavigator"}], + }); }; return ( diff --git a/screens/Profil/ProfilModalSignup.tsx b/screens/Profil/ProfilModalSignup.tsx index e4becb4..8ebcc91 100644 --- a/screens/Profil/ProfilModalSignup.tsx +++ b/screens/Profil/ProfilModalSignup.tsx @@ -34,8 +34,10 @@ export default function ProfilModalSignup({navigation, closeModal}: Props) { return; } setError(null); - navigation.navigate("Home"); closeModal(); + navigation.reset({ + routes: [{name: "TabNavigator"}], + }); } return ( diff --git a/services/UserService.ts b/services/UserService.ts index 756189d..398feff 100644 --- a/services/UserService.ts +++ b/services/UserService.ts @@ -88,7 +88,7 @@ export const useUserService = () => { await axios.post(`${url}/user`, requestBody, { withCredentials: true, }); - + console.log("User registered"); // Connexion après enregistrement const loginResult = await loginUser(username, password); -- GitLab From 474e45cb44499352559a121aa9b5e3f09cd67a71 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 6 Dec 2024 01:20:15 +0100 Subject: [PATCH 102/283] =?UTF-8?q?feat:=20upgrade=20du=20style=20password?= =?UTF-8?q?=20=C3=A0=20l'input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/PlayQuiz/InputPlayQuiz.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/PlayQuiz/InputPlayQuiz.tsx b/components/PlayQuiz/InputPlayQuiz.tsx index 720f523..4a4d5cf 100644 --- a/components/PlayQuiz/InputPlayQuiz.tsx +++ b/components/PlayQuiz/InputPlayQuiz.tsx @@ -8,9 +8,10 @@ interface Props { placeholder?: string; numeric?: boolean; defaultValue?: string; + isSecure?: boolean; } -export default function InputPlayQuiz({ title, setText, noTitle, placeholder, numeric, defaultValue }: Props) { +export default function InputPlayQuiz({ title, setText, noTitle, placeholder, numeric, defaultValue, isSecure=false }: Props) { return ( <View style={styles.container}> {!noTitle && title && <Text style={styles.title}>{title}</Text>} @@ -23,6 +24,7 @@ export default function InputPlayQuiz({ title, setText, noTitle, placeholder, nu setText(text); }} defaultValue={defaultValue} + secureTextEntry={isSecure} /> </View> ); -- GitLab From e030495d04b6f9caec5740405af5cc36ca5d5cb0 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 6 Dec 2024 01:22:07 +0100 Subject: [PATCH 103/283] feat: add style on the great input --- screens/Profil/ProfilModalLogin.tsx | 2 +- screens/Profil/ProfilModalSignup.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/screens/Profil/ProfilModalLogin.tsx b/screens/Profil/ProfilModalLogin.tsx index 7a2e74c..4f58806 100644 --- a/screens/Profil/ProfilModalLogin.tsx +++ b/screens/Profil/ProfilModalLogin.tsx @@ -36,7 +36,7 @@ export default function ProfilModalLogin({navigation, closeModal}: Props) { {error && <Text style={styles.errorText}>{error}</Text>} <View style={styles.containerForm}> <InputPlayQuiz placeholder="EMAIL..." setText={setEmail} noTitle={true} /> - <InputPlayQuiz placeholder="PASSWORD..." setText={setPassword} noTitle={true} /> + <InputPlayQuiz placeholder="PASSWORD..." setText={setPassword} noTitle={true} isSecure={true}/> </View> <DefaultButton text="LOGIN" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText} /> </View> diff --git a/screens/Profil/ProfilModalSignup.tsx b/screens/Profil/ProfilModalSignup.tsx index e4becb4..f9e00fc 100644 --- a/screens/Profil/ProfilModalSignup.tsx +++ b/screens/Profil/ProfilModalSignup.tsx @@ -44,8 +44,8 @@ export default function ProfilModalSignup({navigation, closeModal}: Props) { <View style={styles.containerForm}> <InputPlayQuiz placeholder="USERNAME..." setText={setUsername} noTitle={true}/> <InputPlayQuiz placeholder="EMAIL..." setText={setEmail} noTitle={true}/> - <InputPlayQuiz placeholder="PASSWORD..." setText={setPassword} noTitle={true}/> - <InputPlayQuiz placeholder="REPEAT PASSWORD..." setText={setRepeatPassword} noTitle={true}/> + <InputPlayQuiz placeholder="PASSWORD..." setText={setPassword} noTitle={true} isSecure={true}/> + <InputPlayQuiz placeholder="REPEAT PASSWORD..." setText={setRepeatPassword} noTitle={true} isSecure={true}/> </View> <DefaultButton text="SIGNUP" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> </View> -- GitLab From 0a5b2eeed4e7b8456f77db170cefafa71635b035 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 01:26:51 +0100 Subject: [PATCH 104/283] feat: button logout --- screens/Profil/ProfilChild.tsx | 99 ++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 33 deletions(-) diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index 555f2de..8357747 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -1,85 +1,118 @@ import { NavigationProp } from "@react-navigation/native"; -import { View, StyleSheet, Image, Text, Modal, Button, TouchableOpacity } from "react-native"; +import { View, StyleSheet, Image, Text, TouchableOpacity } from "react-native"; import ProfilModal from "./ProfilModal"; -import React, {useEffect, useState} from "react"; +import React, { useEffect, useState } from "react"; import ProfilHeaderAccount from "./ProfilHeaderAccount"; import { ProfilSelectModeType } from "../../models/SelectModeType"; import UserModel from "../../models/UserModel"; -import {useUserService} from "../../services/UserService"; +import { useUserService } from "../../services/UserService"; import HttpError from "../../services/HttpError"; -import BlueButton from "../../components/PlayQuiz/BlueButton"; +import Icon from "react-native-vector-icons/MaterialIcons"; interface Props { navigation: NavigationProp<any>; } -export default function ProfilChild({navigation}: Props) { +export default function ProfilChild({ navigation }: Props) { const [modalVisible, setModalVisible] = useState(false); const [mode, setMode] = useState<ProfilSelectModeType>("login"); const [user, setUser] = useState<UserModel | null>(null); - const {getInformationsUser, logoutUser} = useUserService(); + const { getInformationsUser, logoutUser } = useUserService(); useEffect(() => { getInformationsUser().then((user: UserModel | HttpError) => { console.log(user); - if(user instanceof HttpError){ - return + if (user instanceof HttpError) { + return; } setUser(user); - return + return; }); }, []); + const handleLogout = async () => { + const result = await logoutUser(); + if (!(result instanceof HttpError)) { + setUser(null); // Réinitialise l'utilisateur après la déconnexion + } + }; + return ( <View style={styles.containerGlobal}> <View style={styles.containerPlayerInfos}> - <Image source={require('../../assets/TitleApp.png')} style={styles.titleImage}/> - <Image source={require('../../assets/ProfilBaseImage.png')} style={styles.profilImage}/> + <Image source={require('../../assets/TitleApp.png')} style={styles.titleImage} /> + <Image source={require('../../assets/ProfilBaseImage.png')} style={styles.profilImage} /> <Text style={styles.pseudoText}>{user ? user.username : "?"}</Text> </View> <View style={styles.containerPlayerStats}> {/*<Text style={styles.statsText}>Played quiz : _123_</Text>*/} </View> - {!user && <ProfilHeaderAccount setModalVisible={setModalVisible} setMode={setMode}/>} - <ProfilModal modalVisible={modalVisible} setModalVisible={setModalVisible} mode={mode} setMode={setMode} navigation={navigation}/> + {!user && ( + <ProfilHeaderAccount setModalVisible={setModalVisible} setMode={setMode} /> + )} + <ProfilModal + modalVisible={modalVisible} + setModalVisible={setModalVisible} + mode={mode} + setMode={setMode} + navigation={navigation} + /> + + {user && ( + <TouchableOpacity style={styles.logoutButton} onPress={handleLogout}> + <Icon name="logout" size={40} color="#FF0000" /> + </TouchableOpacity> + )} </View> - ) + ); } const styles = StyleSheet.create({ containerGlobal: { - display: 'flex', - width: '100%', - height: '100%', - justifyContent: 'flex-start', - alignItems: 'center', + display: "flex", + width: "100%", + height: "100%", + justifyContent: "flex-start", + alignItems: "center", }, containerPlayerInfos: { - width: '100%', - display: 'flex', - alignItems: 'center', + width: "100%", + display: "flex", + alignItems: "center", }, titleImage: { - width: '45%', - resizeMode: 'contain', + width: "45%", + resizeMode: "contain", }, profilImage: { - width: '45%', - aspectRatio: 1 + width: "45%", + aspectRatio: 1, }, pseudoText: { fontSize: 35, - fontWeight: 'bold', - color: '#00B3F4', + fontWeight: "bold", + color: "#00B3F4", }, containerPlayerStats: { - width: '100%', - padding: '5%', + width: "100%", + padding: "5%", }, statsText: { fontSize: 17, - fontWeight: 'bold', + fontWeight: "bold", + }, + logoutButton: { + position: "absolute", + bottom: 20, + right: 20, + backgroundColor: "#FFF", + borderRadius: 50, + padding: 10, + elevation: 5, // Ombre pour Android + shadowColor: "#000", // Ombre pour iOS + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 2, }, - -}); \ No newline at end of file +}); -- GitLab From 737edb07904946e52fe42d1e48988308e0751442 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 01:39:33 +0100 Subject: [PATCH 105/283] fix: pass quiz to endQuiz --- screens/EndQuiz/EndQuiz.tsx | 14 +++++++++++--- screens/EndQuiz/EndQuizChild.tsx | 11 +++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/screens/EndQuiz/EndQuiz.tsx b/screens/EndQuiz/EndQuiz.tsx index cb13d78..678fc88 100644 --- a/screens/EndQuiz/EndQuiz.tsx +++ b/screens/EndQuiz/EndQuiz.tsx @@ -1,18 +1,26 @@ -import { NavigationProp } from "@react-navigation/native"; +import {NavigationProp, RouteProp} from "@react-navigation/native"; import { View, StyleSheet, Image, Text } from "react-native"; import EndQuizChild from "./EndQuizChild"; import TemplateMenu from "../../templates/TemplateMenu"; +import QuizModel from "../../models/QuizModel"; +type RoutePropsType = { + EndQuizChild: { + quiz: QuizModel; + }; +}; interface Props { + route: RouteProp<RoutePropsType, "EndQuizChild">; navigation: NavigationProp<any>; } -export default function EndQuiz({navigation}: Props) { +export default function EndQuiz({route,navigation}: Props) { + const { quiz } = route.params; return ( <View style={styles.containerGlobal}> <TemplateMenu navigation={navigation} headerNavigation={false} buttonGoBack={false}> <View> - <EndQuizChild navigation={navigation}/> + <EndQuizChild navigation={navigation} quiz={quiz}/> </View> </TemplateMenu> </View> diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/EndQuiz/EndQuizChild.tsx index 936e00d..b284250 100644 --- a/screens/EndQuiz/EndQuizChild.tsx +++ b/screens/EndQuiz/EndQuizChild.tsx @@ -3,19 +3,14 @@ import DefaultButton from "../../components/DefaultButton"; import { NavigationProp, RouteProp } from "@react-navigation/native"; import QuizModel from "../../models/QuizModel"; -type RoutePropsType = { - EndQuiz: { - quiz: QuizModel; - }; -}; + interface Props { navigation: NavigationProp<any>; - route: RouteProp<RoutePropsType, "EndQuiz">; + quiz: QuizModel; } -export default function EndQuizChild({ navigation, route }: Props) { - const { quiz } = route.params; +export default function EndQuizChild({ navigation, quiz }: Props) { const handleRestartPress = () => { quiz.nbActualQuestion = 1; -- GitLab From 653e7ab6c41930a1e829eec6df3b221e82935bfc Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 01:57:10 +0100 Subject: [PATCH 106/283] fix: go tabnavigation from playingquiz --- screens/PlayingQuiz/PlayingQuizBody.tsx | 2 +- screens/PlayingQuiz/PlayingQuizHeader.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 7031a60..b3057b1 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -47,7 +47,7 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA const answers = await getAnswerQuiz(quiz.code, currentQuestion.id, currentQuestion.answers[selectedAnswerIndex].id); if (HttpError.isHttpError(answers)) { - navigation.navigate("Home"); + navigation.navigate("TabNavigator"); return; } diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index fa1747f..cb14415 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -22,7 +22,7 @@ export default function PlayingQuizHeader({navigation, quiz}: Props) { index: 0, routes: [ { - name: "Home", + name: "TabNavigator", }, ] }); -- GitLab From 48874518de291ace2389afc2b322c5dad7291002 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 02:07:49 +0100 Subject: [PATCH 107/283] fix: go tabnavigation from endquiz --- screens/EndQuiz/EndQuizChild.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/EndQuiz/EndQuizChild.tsx index b284250..2f85f5a 100644 --- a/screens/EndQuiz/EndQuizChild.tsx +++ b/screens/EndQuiz/EndQuizChild.tsx @@ -27,7 +27,7 @@ export default function EndQuizChild({ navigation, quiz }: Props) { }; const handleBackToMenu = () => { - navigation.navigate('Home'); + navigation.navigate('TabNavigator'); }; return ( -- GitLab From 0f79a0c1de3962b0887a2afb53dd344ea3a0ec63 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 09:20:03 +0100 Subject: [PATCH 108/283] feat: fetching all quiz for community --- components/lists/QuizList.tsx | 2 +- screens/Community/Community.tsx | 47 +++++++-------------------------- services/QuizService.ts | 24 ++++++++++++++++- templates/TemplateQuizList.tsx | 6 ++--- 4 files changed, 37 insertions(+), 42 deletions(-) diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index 13cc1e4..2f04155 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -51,7 +51,7 @@ const quizzes: QuizModel[] = [ */ interface Props{ navigation: NavigationProp<any>; - quizList: QuizModel[] | null + quizList: QuizModel[] | null | undefined } export default function QuizList({navigation,quizList}: Props) { const onQuizPressed = (quiz: QuizModel) => { diff --git a/screens/Community/Community.tsx b/screens/Community/Community.tsx index 7addc7d..0483528 100644 --- a/screens/Community/Community.tsx +++ b/screens/Community/Community.tsx @@ -10,48 +10,21 @@ interface Props { navigation: NavigationProp<any> } export default function Community({navigation}: Props) { - const {getQuizzesCommunity} = useQuizService(); + const {getAllQuizzes} = useQuizService(); const [input, setInput] = useState<string>("") + const [loading, setLoading] = useState<boolean>(false) - const [quizList, setQuizList] = useState<QuizModel[] | null>([ - { - code: "QUIZ123", - category: "General Knowledge", - questions: [], - nbQuestions: 3, - nbActualQuestion: 1, - score: 0, - }, - { - code: "QUIZ456", - category: "Science", - questions: [], - nbQuestions: 5, - nbActualQuestion: 1, - score: 0, - }, - { - code: "QUIZ789", - category: "Entertainment", - questions: [], - nbQuestions: 10, - nbActualQuestion: 1, - score: 0, - }, - ]) + const [quizList, setQuizList] = useState<QuizModel[] | null>() useEffect( () => { - console.log("useEffect"); - getQuizzesCommunity().then((quizzes) => { - if(quizzes instanceof HttpError){ - console.log(quizzes); - return; - } - if(quizzes instanceof Array) { - console.log("array"); - setQuizList(quizzes) + + getAllQuizzes().then((quizzes: QuizModel[] | HttpError) => { + if (quizzes instanceof HttpError) { + console.log("Error while fetching quizzes:", quizzes); + } else { + setQuizList(quizzes); } - }); + }) }, []) diff --git a/services/QuizService.ts b/services/QuizService.ts index 548dbf7..7877768 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -164,6 +164,27 @@ export const useQuizService = () => { } } + const getAllQuizzes = async (): Promise<QuizModel[] | HttpError> => { + try { + // Effectuer une requête GET pour récupérer les quizzes + const response = await axios.get(`${url}/quiz/all`, { + withCredentials: true, // Inclut les cookies pour une session authentifiée + }); + + // Transformation des données de chaque quiz en modèle QuizModel + const quizzes: QuizModel[] = response.data.map((quiz: any) => transformToQuizModel(quiz)); + + return quizzes; + } catch (error: any) { + console.error("Error while fetching all quizzes:", error); + + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + }; return { @@ -171,7 +192,8 @@ export const useQuizService = () => { getAnswerQuiz: getAnswerQuiz, remainingQuiz: remainingQuiz, getRandomQuiz: getRandomQuiz, - getQuizzesCommunity: getQuizzesCommunity + getQuizzesCommunity: getQuizzesCommunity, + getAllQuizzes: getAllQuizzes } } \ No newline at end of file diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index 5abce9e..3f719f4 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -10,7 +10,7 @@ interface Props { navigation: NavigationProp<any> title: string setTextInInput: (text: string) => void - quizList: QuizModel[] | null + quizList: QuizModel[] | null | undefined buttonGoBack: boolean } @@ -30,8 +30,8 @@ export default function TemplateQuizList({navigation, title, setTextInInput, qui <View style={styles.globalContainer}> <Text style={styles.title}>{title}</Text> <View style={styles.contentContainer}> - <InputPlayQuiz setText={setTextInInput} noTitle={true} /> - <QuizList quizList={quizList} navigation={navigation}/> + {/*<InputPlayQuiz setText={setTextInInput} noTitle={true} />*/} + <QuizList quizList={quizList} navigation={navigation}/> </View> </View> </TemplateMenu> -- GitLab From bc6a82c33d2dc79df92672bdcb054a157367ba9d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 09:43:19 +0100 Subject: [PATCH 109/283] fix: register work --- screens/Profil/ProfilModalSignup.tsx | 4 ++-- services/UserService.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/screens/Profil/ProfilModalSignup.tsx b/screens/Profil/ProfilModalSignup.tsx index 91434d4..3179c0c 100644 --- a/screens/Profil/ProfilModalSignup.tsx +++ b/screens/Profil/ProfilModalSignup.tsx @@ -19,7 +19,7 @@ export default function ProfilModalSignup({navigation, closeModal}: Props) { const [error, setError] = useState<string | null>(null); const { registerUser } = useUserService(); - const handleSubmitPressed = () => { + const handleSubmitPressed = async () => { if (email === "" || username === "" || password === "" || repeatPassword === "") { setError("Please fill in all fields"); return; @@ -28,7 +28,7 @@ export default function ProfilModalSignup({navigation, closeModal}: Props) { setError("Passwords do not match"); return; } - const register = registerUser(username, password, email); + const register = await registerUser(username, password, email); if(register instanceof HttpError){ setError(register.message); return; diff --git a/services/UserService.ts b/services/UserService.ts index 398feff..08399a9 100644 --- a/services/UserService.ts +++ b/services/UserService.ts @@ -88,7 +88,6 @@ export const useUserService = () => { await axios.post(`${url}/user`, requestBody, { withCredentials: true, }); - console.log("User registered"); // Connexion après enregistrement const loginResult = await loginUser(username, password); @@ -99,6 +98,9 @@ export const useUserService = () => { return true; // Succès } catch (error: any) { + if(error.response.status === 400) { + return new HttpError(400, "User already exists"); + } if (error.response) { return new HttpError(error.response.status, error.response.data?.message); } else { -- GitLab From 70d40251b47a602d6cc5f48aaf33246dea90e567 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 09:56:11 +0100 Subject: [PATCH 110/283] feat: loading icon in community screen --- components/lists/QuizList.tsx | 81 +++++++++++---------------------- screens/Community/Community.tsx | 5 +- templates/TemplateQuizList.tsx | 5 +- 3 files changed, 33 insertions(+), 58 deletions(-) diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index 2f04155..ef92c52 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -1,62 +1,19 @@ import React from "react"; -import { FlatList, View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import { FlatList, View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from "react-native"; import QuizModel from "../../models/QuizModel"; -import {NavigationProp} from "@react-navigation/native"; +import { NavigationProp } from "@react-navigation/native"; -const quizzes: QuizModel[] = [ - { - code: "QUIZ123", - category: "General Knowledge", - questions: [], - nbQuestions: 3, - nbActualQuestion: 1, - score: 0, - }, - { - code: "QUIZ456", - category: "Science", - questions: [], - nbQuestions: 5, - nbActualQuestion: 1, - score: 0, - }, - { - code: "QUIZ789", - category: "Entertainment", - questions: [], - nbQuestions: 10, - nbActualQuestion: 1, - score: 0, - }, -]; - -/** - * QuizList : Un composant affichant une liste de quiz avec leur titre, nombre de questions et catégorie. - * - * @component - * - * @example - * // Exemple d'utilisation : - * <QuizList /> - * - * @description - * - Ce composant utilise un `FlatList` pour afficher les quiz. - * - Chaque élément de la liste contient : - * - Le code du quiz comme titre. - * - Le nombre total de questions. - * - La catégorie du quiz, en majuscules. - * - Les quiz sont définis dans une liste statique en haut du fichier pour cet exemple. - * - * @returns Un composant React contenant une liste interactive de quiz. - */ -interface Props{ +interface Props { navigation: NavigationProp<any>; - quizList: QuizModel[] | null | undefined + quizList: QuizModel[] | null | undefined; + isLoadingData: boolean; } -export default function QuizList({navigation,quizList}: Props) { + +export default function QuizList({ navigation, quizList, isLoadingData }: Props) { const onQuizPressed = (quiz: QuizModel) => { - navigation.navigate('InformationsOfQuiz', {quiz: quiz}); + navigation.navigate("InformationsOfQuiz", { quiz: quiz }); }; + const renderQuizItem = ({ item }: { item: QuizModel }) => ( <TouchableOpacity style={styles.quizItem} onPress={() => onQuizPressed(item)}> <Text style={styles.quizTitle}>{item.code}</Text> @@ -68,7 +25,12 @@ export default function QuizList({navigation,quizList}: Props) { return ( <View style={styles.container}> - {quizList && quizList.length > 0 ? ( + {isLoadingData ? ( + <View style={styles.loadingContainer}> + <ActivityIndicator size="large" color="#2b73fe" /> + <Text style={styles.loadingText}>Loading quizzes...</Text> + </View> + ) : quizList && quizList.length > 0 ? ( <FlatList data={quizList} renderItem={renderQuizItem} @@ -77,7 +39,7 @@ export default function QuizList({navigation,quizList}: Props) { contentContainerStyle={styles.listContent} /> ) : ( - <Text style={styles.emptyMessage}>No quizzes available.</Text> // Message si la liste est vide + <Text style={styles.emptyMessage}>No quizzes available.</Text> )} </View> ); @@ -132,4 +94,15 @@ const styles = StyleSheet.create({ textAlign: "center", marginTop: 20, }, + loadingContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + loadingText: { + fontSize: 16, + color: "#555", + marginTop: 10, + textAlign: "center", + }, }); diff --git a/screens/Community/Community.tsx b/screens/Community/Community.tsx index 0483528..cee2a88 100644 --- a/screens/Community/Community.tsx +++ b/screens/Community/Community.tsx @@ -17,19 +17,20 @@ export default function Community({navigation}: Props) { const [quizList, setQuizList] = useState<QuizModel[] | null>() useEffect( () => { - + setLoading(true) getAllQuizzes().then((quizzes: QuizModel[] | HttpError) => { if (quizzes instanceof HttpError) { console.log("Error while fetching quizzes:", quizzes); } else { setQuizList(quizzes); } + setLoading(false) }) }, []) return ( - <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={quizList} navigation={navigation} buttonGoBack={false}/> + <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={quizList} navigation={navigation} buttonGoBack={false} isLoadingData={loading}/> ); } \ No newline at end of file diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index 3f719f4..c204506 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -12,6 +12,7 @@ interface Props { setTextInInput: (text: string) => void quizList: QuizModel[] | null | undefined buttonGoBack: boolean + isLoadingData: boolean } /** @@ -24,14 +25,14 @@ interface Props { * @param quizList - La liste des quiz à afficher * @param buttonGoBack - boolean qui dit si le bouton de retour doit être affiché */ -export default function TemplateQuizList({navigation, title, setTextInInput, quizList, buttonGoBack}: Props) { +export default function TemplateQuizList({navigation, title, setTextInInput, quizList, buttonGoBack, isLoadingData}: Props) { return ( <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={buttonGoBack}> <View style={styles.globalContainer}> <Text style={styles.title}>{title}</Text> <View style={styles.contentContainer}> {/*<InputPlayQuiz setText={setTextInInput} noTitle={true} />*/} - <QuizList quizList={quizList} navigation={navigation}/> + <QuizList quizList={quizList} navigation={navigation} isLoadingData={isLoadingData}/> </View> </View> </TemplateMenu> -- GitLab From 5a299f1c4cf341588121c3e296f8dcc3a556b54d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 09:57:12 +0100 Subject: [PATCH 111/283] feat: loading icon in My Quizzes --- screens/MyQuizzes/MyQuizzes.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/MyQuizzes/MyQuizzes.tsx b/screens/MyQuizzes/MyQuizzes.tsx index 7419d53..d7916f2 100644 --- a/screens/MyQuizzes/MyQuizzes.tsx +++ b/screens/MyQuizzes/MyQuizzes.tsx @@ -38,6 +38,6 @@ export default function MyQuizzes({navigation}: Props) { return ( - <TemplateQuizList title={"My quizzes"} setTextInInput={setInput} quizList={quizList} navigation={navigation} buttonGoBack={true}/> + <TemplateQuizList title={"My quizzes"} setTextInInput={setInput} quizList={quizList} navigation={navigation} buttonGoBack={true} isLoadingData={false}/> ); } \ No newline at end of file -- GitLab From 09812fabd6f406ce9205c71be41a5c5b2543d73c Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 10:00:37 +0100 Subject: [PATCH 112/283] style: comment --- components/lists/QuizList.tsx | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index ef92c52..4185602 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -9,6 +9,36 @@ interface Props { isLoadingData: boolean; } +/** + * QuizList : Un composant affichant une liste de quiz avec leur titre, nombre de questions, et catégorie. + * + * @component + * + * @param navigation - Propriété de navigation utilisée pour naviguer vers l'écran "InformationsOfQuiz". + * @param quizList - Liste des quiz à afficher. Peut être null ou undefined. + * @param isLoadingData - Boolean indiquant si les données sont en cours de chargement. + * + * @example + * <QuizList + * navigation={navigation} + * quizList={quizzes} + * isLoadingData={isLoading} + * /> + * + * @description + * - Ce composant gère trois états : + * 1. Chargement : Affiche un spinner si `isLoadingData` est true. + * 2. Liste des quiz : Affiche une liste interactive des quiz si `quizList` contient des éléments. + * 3. Liste vide : Affiche un message si `quizList` est vide. + * + * - Chaque quiz affiche : + * - Son code en tant que titre. + * - Son nombre total de questions. + * - Sa catégorie, affichée en majuscules. + * + * @returns Un composant React contenant une liste interactive de quiz ou des messages en fonction de l'état. + */ + export default function QuizList({ navigation, quizList, isLoadingData }: Props) { const onQuizPressed = (quiz: QuizModel) => { navigation.navigate("InformationsOfQuiz", { quiz: quiz }); -- GitLab From 2eb6221a6f5a776053a7d8f6835eaa8c2a161a08 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 14:54:38 +0100 Subject: [PATCH 113/283] fix: fix bug in playing quiz --- screens/PlayingQuiz/PlayingQuizBody.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index b3057b1..87b8ce4 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -24,9 +24,20 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA const [isCorrect, setIsCorrect] = useState<boolean | null>(null); const [correctAnswer, setCorrectAnswer] = useState<AnswerModel | null>(null); const [isAnswered, setIsAnswered] = useState<boolean>(false); + const [isDisabled, setIsDisabled] = useState<boolean>(true); const { getAnswerQuiz } = useQuizService(); + useEffect(() => { + if(selectedAnswerIndex !== null) { + setIsDisabled(false); + } + if(selectedAnswerIndex === null) { + setIsDisabled(true); + } + + }, [selectedAnswerIndex]); + useEffect(() => { if (quiz) { setSelectedAnswerIndex(null); @@ -42,6 +53,7 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA const handleConfirmAnswerPressed = async () => { if(selectedAnswerIndex === null) return; + setIsDisabled(true); setIsAnswered(true); const answers = await getAnswerQuiz(quiz.code, currentQuestion.id, currentQuestion.answers[selectedAnswerIndex].id); @@ -125,7 +137,7 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA ))} {/* Ajout du PlayButton à la fin */} <View style={styles.playButtonContainer}> - <BlueButton onPress={handleConfirmAnswerPressed} text="Confirm" buttonStyle={{borderRadius: 50}} isDisabled={selectedAnswerIndex===null}/> + <BlueButton onPress={handleConfirmAnswerPressed} text="Confirm" buttonStyle={{borderRadius: 50}} isDisabled={isDisabled}/> </View> </> ) : ( -- GitLab From 46dc1c0d7621783339e25c669361aa494dcea757 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 15:04:33 +0100 Subject: [PATCH 114/283] style: overlay removed --- components/PlayingQuiz/ComfirmModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/PlayingQuiz/ComfirmModal.tsx b/components/PlayingQuiz/ComfirmModal.tsx index f31241c..81af836 100644 --- a/components/PlayingQuiz/ComfirmModal.tsx +++ b/components/PlayingQuiz/ComfirmModal.tsx @@ -25,7 +25,7 @@ export default function ConfirmModal({ visible, onClose, onConfirm }: Props) { onRequestClose={onClose} // Ferme la modal en cas d'interaction système > <TouchableWithoutFeedback onPress={onClose}> - <View style={styles.overlay} /> + <View/> </TouchableWithoutFeedback> <View style={styles.modalContainer}> <TouchableOpacity style={styles.closeButton} onPress={onClose}> @@ -34,7 +34,7 @@ export default function ConfirmModal({ visible, onClose, onConfirm }: Props) { <Text style={styles.title}>WOULD YOU LIKE TO RETURN TO THE MENU ?</Text> <View style={styles.separator} /> <View style={styles.buttonContainer}> - <BlueButton text={"CONFIRM"} onPress={onConfirm}/> + <BlueButton text={"CONFIRM"} onPress={onConfirm} isDisabled={false}/> </View> </View> </Modal> -- GitLab From 68fbbc7af9c45861fb64cb64d691a3489320d24f Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 15:15:05 +0100 Subject: [PATCH 115/283] fix: button my quizzes removed --- screens/Home/HomeChild.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index dfe0df0..6e0460d 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -49,7 +49,7 @@ export default function HomeChild({navigation}: Props) { <View style={styles.buttonContainer}> <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonQuickGamePressed}/> <MenuButton text={"PLAY A QUIZ"} handleButtonPressed={handleButtonGeneratePressed}/> - <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/> + {/*<MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/>*/} </View> <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> </View> -- GitLab From dbe47065014c8d0da22251989be0e69bb19a548c Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 15:20:16 +0100 Subject: [PATCH 116/283] fix: font size question --- styles/TextsStyles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/TextsStyles.ts b/styles/TextsStyles.ts index 1f80ebd..1ec37f0 100644 --- a/styles/TextsStyles.ts +++ b/styles/TextsStyles.ts @@ -13,7 +13,7 @@ export const TextsStyles = StyleSheet.create( { fontWeight: "bold", }, subtitleText: { - fontSize: 24, + fontSize: 21, }, infoQuizText: { fontSize: 18, -- GitLab From 50ac745ed282af4b9ad4d7275f048836d074f30e Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 15:51:11 +0100 Subject: [PATCH 117/283] fix: Go playing quiz from community --- screens/InformationsOfQuiz/InformationsOfQuiz.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/InformationsOfQuiz/InformationsOfQuiz.tsx index 2fdfb96..ba5c5d0 100644 --- a/screens/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -18,7 +18,7 @@ export default function InformationsOfQuiz({ navigation, route }: Props) { const { quiz } = route.params; const onPlay = () => { - // navigation.navigate('PlayQuiz', {quiz: quiz}); + navigation.navigate('PlayingQuiz', {quizRecovered: quiz}); } return ( -- GitLab From 04c1e10399d276076ed671cab8921aaf0224e5f7 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Fri, 6 Dec 2024 15:55:08 +0100 Subject: [PATCH 118/283] fix: button restart removed in endQuiz --- screens/EndQuiz/EndQuizChild.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/EndQuiz/EndQuizChild.tsx index 2f85f5a..cdda848 100644 --- a/screens/EndQuiz/EndQuizChild.tsx +++ b/screens/EndQuiz/EndQuizChild.tsx @@ -39,7 +39,7 @@ export default function EndQuizChild({ navigation, quiz }: Props) { <Text style={styles.TextBodyTitle}>{((quiz.score/quiz.nbQuestions)*100 > 50 ) ? 'Victory' : 'Defeat'}</Text> <Text style={styles.TextBody}>you finish the test with a score of {`${quiz.score}/${quiz.nbQuestions}`} ! </Text> <Image source={require('../../assets/uWin.png')} /> - <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton> + {/*<DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton>*/} <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu} buttonStyle={styles.blueButton} buttonText={styles.blueButtonText}></DefaultButton> </View> </View> -- GitLab From 72b847c11bc47b8f3e4182f5f0e081cd6f3b66a3 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 8 Dec 2024 16:16:27 +0100 Subject: [PATCH 119/283] feat: generateQuiz with axios --- services/QuizService.ts | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index 7877768..2891048 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -9,7 +9,6 @@ export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API const generateQuiz = async (amount: number, category: number, difficulty: string) : Promise<QuizModel | HttpError> => { - // Création du corps de la requête avec les variables passées const requestBody = { amount, @@ -18,29 +17,24 @@ export const useQuizService = () => { }; try { - const response = await fetch(url + "/quiz/create", { - method: "POST", + const response = await axios.post(`${url}/quiz/create`, requestBody, { headers: { "Content-Type": "application/json", }, - body: JSON.stringify(requestBody), }); - if (!response.ok) { - throw new HttpError(response.status, "HTTP error! status: " + response.status); - } - // Récupération des données retournées par l'API - const data = await response.json(); - const quiz = transformToQuizModel(data); + const quiz = transformToQuizModel(response.data); return quiz; - } catch (error) { + } catch (error: any) { console.error("Error while generating quiz:", error); - if (HttpError.isHttpError(error)) { - return error; + if (error.response) { + // Gestion des erreurs HTTP + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); } else { - return new HttpError(500, "Unexpected error: " + error); + // Gestion des erreurs réseau ou autres + return new HttpError(500, "Unexpected error: " + error.message); } } } -- GitLab From d4e58f0f6355ed778bcf0534baa3627d744d8669 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 8 Dec 2024 16:17:53 +0100 Subject: [PATCH 120/283] feat: remainingQuiz with axios --- services/QuizService.ts | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index 2891048..de7d451 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -9,11 +9,10 @@ export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API const generateQuiz = async (amount: number, category: number, difficulty: string) : Promise<QuizModel | HttpError> => { - // Création du corps de la requête avec les variables passées const requestBody = { amount, category, - difficulty: difficulty.toLowerCase(), // Si EDifficulty est en uppercase et l'API attend lowercase + difficulty: difficulty.toLowerCase(), }; try { @@ -39,40 +38,33 @@ export const useQuizService = () => { } } - const remainingQuiz = async (id: string) : Promise<QuizModel | HttpError> => { - - // Création du corps de la requête avec les variables passées + const remainingQuiz = async (id: string): Promise<QuizModel | HttpError> => { const requestBody = { - id + id, }; try { - const response = await fetch(url + "/quiz/remaining", { - method: "POST", + const response = await axios.post(`${url}/quiz/remaining`, requestBody, { headers: { "Content-Type": "application/json", }, - body: JSON.stringify(requestBody), }); - if (!response.ok) { - throw new HttpError(response.status, "HTTP error! status: " + response.status); - } - // Récupération des données retournées par l'API - const data = await response.json(); - const quiz = transformToQuizModel(data); + const quiz = transformToQuizModel(response.data); return quiz; - } catch (error) { + } catch (error: any) { console.log("Error while remaining quiz:", error); - if (HttpError.isHttpError(error)) { - return error; + if (error.response) { + // Gestion des erreurs HTTP + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); } else { - return new HttpError(500, "Unexpected error: " + error); + // Gestion des erreurs réseau ou autres + return new HttpError(500, "Unexpected error: " + error.message); } } - } + }; const getAnswerQuiz = async (codeQuiz: string, questionID: number, answerID: number) : Promise<AnswerModel[] | HttpError> => { try { -- GitLab From 99c715d53fd0b0dfc5423115266934b8129e88d8 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 8 Dec 2024 16:31:54 +0100 Subject: [PATCH 121/283] feat: getAnswerQuiz with axios --- services/QuizService.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index de7d451..e7be26a 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -66,39 +66,39 @@ export const useQuizService = () => { } }; - const getAnswerQuiz = async (codeQuiz: string, questionID: number, answerID: number) : Promise<AnswerModel[] | HttpError> => { + const getAnswerQuiz = async ( + codeQuiz: string, + questionID: number, + answerID: number + ): Promise<AnswerModel[] | HttpError> => { try { const requestBody = { id: codeQuiz, questionID: questionID, - answerID: answerID + answerID: answerID, }; - const response = await fetch(url + "/quiz/answer", { - method: "POST", + const response = await axios.post(`${url}/quiz/answer`, requestBody, { headers: { "Content-Type": "application/json", }, - body: JSON.stringify(requestBody), }); - if (!response.ok) { - throw new HttpError(response.status, "HTTP error! status: " + response.status); - } - - const data = await response.json(); - const answers = transformToAnswerModel(data); + // Récupération des données retournées par l'API + const answers = transformToAnswerModel(response.data); return answers; - } catch (error) { + } catch (error: any) { console.log("Error while fetching answers:", error); - if (HttpError.isHttpError(error)) { - return error; + if (error.response) { + // Gestion des erreurs HTTP + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); } else { - return new HttpError(500, "Unexpected error: " + error); + // Gestion des erreurs réseau ou autres + return new HttpError(500, "Unexpected error: " + error.message); } } - } + }; const getRandomQuiz = async () : Promise<QuizModel | HttpError> => { -- GitLab From 3b13926c39a086c49bf0fc70d164f24d70bce107 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 8 Dec 2024 16:34:08 +0100 Subject: [PATCH 122/283] feat: getRandomQuiz with axios --- services/QuizService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index e7be26a..e4d8675 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -54,7 +54,7 @@ export const useQuizService = () => { const quiz = transformToQuizModel(response.data); return quiz; } catch (error: any) { - console.log("Error while remaining quiz:", error); + console.error("Error while remaining quiz:", error); if (error.response) { // Gestion des erreurs HTTP @@ -88,7 +88,7 @@ export const useQuizService = () => { const answers = transformToAnswerModel(response.data); return answers; } catch (error: any) { - console.log("Error while fetching answers:", error); + console.error("Error while fetching answers:", error); if (error.response) { // Gestion des erreurs HTTP -- GitLab From 5e0bb0fe33296f38c344f8be466767e4954cc3c3 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 8 Dec 2024 16:36:15 +0100 Subject: [PATCH 123/283] feat: getRandomQuiz with axios --- services/QuizService.ts | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index e4d8675..bf63bbf 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -100,40 +100,34 @@ export const useQuizService = () => { } }; - const getRandomQuiz = async () : Promise<QuizModel | HttpError> => { - - // Création du corps de la requête avec les variables passées - const requestBody = { - amount: 10, - }; - + const getRandomQuiz = async (): Promise<QuizModel | HttpError> => { try { - const response = await fetch(url + "/quiz/create", { - method: "POST", + const requestBody = { + amount: 10, + }; + + const response = await axios.post(`${url}/quiz/create`, requestBody, { headers: { "Content-Type": "application/json", }, - body: JSON.stringify(requestBody), }); - if (!response.ok) { - throw new HttpError(response.status, "HTTP error! status: " + response.status); - } - // Récupération des données retournées par l'API - const data = await response.json(); - const quiz = transformToQuizModel(data); + const quiz = transformToQuizModel(response.data); return quiz; - } catch (error) { + } catch (error: any) { console.error("Error while generating random quiz:", error); - if (HttpError.isHttpError(error)) { - return error; + if (error.response) { + // Gestion des erreurs HTTP + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); } else { - return new HttpError(500, "Unexpected error: " + error); + // Gestion des erreurs réseau ou autres + return new HttpError(500, "Unexpected error: " + error.message); } } - } + }; + const getQuizzesCommunity = async () : Promise<QuizModel[] | HttpError> => { try { -- GitLab From f1cbd27d051e557c1d9b5e88211f3539c7836ef8 Mon Sep 17 00:00:00 2001 From: HUSS THEOPHILE <theophile.huss@etu.unistra.fr> Date: Thu, 12 Dec 2024 09:51:57 +0000 Subject: [PATCH 124/283] feat: Add .gitlab-ci.yml in project instead of another repository --- .gitlab-ci.yml | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..139742d --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,53 @@ +image: node:latest + +stages: + - build + - test + - deploy + +build-job: + stage: build + script: + - echo "Building application..." +# - npm install -g eas-cli +# - npm ci +# - EXPO_TOKEN=$EAS_TOKEN eas build --platform android --platform ios --profile production --non-interactive + - echo "Application successfully built." + +test-job: + stage: test + script: + - echo "Running unit tests..." + - echo "Unit tests complete" + +deploy-job: + stage: deploy + environment: production + script: + - echo "Deploying application..." +# - curl -o builds/my-app.apk "$(eas build:status --json | jq -r '.artifacts.buildUrl')" + - echo "Application successfully deployed." +# artifacts: +# paths: +# - builds/my-app.apk + rules: + - if: '$CI_COMMIT_BRANCH == "main"' + dependencies: + - build-job + + +# # à supprimer à l'avenir +# # deploy-job-test: +# # stage: deploy +# # environment: production +# # script: +# # - echo "Deploying application..." +# # - curl -o builds/my-app.apk "$(eas build:status --json | jq -r '.artifacts.buildUrl')" +# # - echo "Application successfully deployed." +# # artifacts: +# # paths: +# # - builds/my-app.apk +# # rules: +# # - if: '$CI_COMMIT_BRANCH == "develop"' +# # dependencies: +# # - build-job \ No newline at end of file -- GitLab From 4d3d696430260a33fda0030eacac847c8225d33c Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Mon, 16 Dec 2024 22:08:17 +0100 Subject: [PATCH 125/283] feat: technical overhaul of playing quiz --- screens/PlayingQuiz/PlayingQuizBody.tsx | 198 ++++++++++++++---------- 1 file changed, 114 insertions(+), 84 deletions(-) diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 87b8ce4..d319ab7 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -19,108 +19,116 @@ interface Props { navigation: NavigationProp<any>; } -export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsAlreadyPlayed, navigation }: Props) { - const [selectedAnswerIndex, setSelectedAnswerIndex] = useState<number | null>(null); - const [isCorrect, setIsCorrect] = useState<boolean | null>(null); - const [correctAnswer, setCorrectAnswer] = useState<AnswerModel | null>(null); - const [isAnswered, setIsAnswered] = useState<boolean>(false); - const [isDisabled, setIsDisabled] = useState<boolean>(true); - - const { getAnswerQuiz } = useQuizService(); +enum QuizState { + ANSWERING= "Answering", + LOADING= "Loading", + SHOWING_RESULTS= "ShowingResults", +} - useEffect(() => { - if(selectedAnswerIndex !== null) { - setIsDisabled(false); - } - if(selectedAnswerIndex === null) { - setIsDisabled(true); - } +const getStyleOfAnswerButtonText = (answerId: number, selectedAnswerIndex: number | null, correctAnswerId: number | null, quizState: QuizState) => { + if(quizState === QuizState.LOADING) + { + return styles.loadingText; + } + if(quizState === QuizState.ANSWERING) + { + return styles.answerText; - }, [selectedAnswerIndex]); + } + if(quizState === QuizState.SHOWING_RESULTS) + { + if(answerId === correctAnswerId) return styles.correctText; + if(answerId !== correctAnswerId && selectedAnswerIndex === answerId) return styles.incorrectText; + } + return styles.answerText; +} - useEffect(() => { - if (quiz) { - setSelectedAnswerIndex(null); - setIsCorrect(null); - setCorrectAnswer(null); - setIsAnswered(false); - } - }, [quiz?.nbActualQuestion]); +const getStyleOfAnswerButton = (answerId: number, selectedAnswerIndex: number | null, correctAnswerId: number | null, quizState: QuizState) => { + if(quizState === QuizState.LOADING) + { + return styles.loadingAnswer; + } + if(quizState === QuizState.ANSWERING) + { + if(selectedAnswerIndex === null) return styles.answerButton; + if(selectedAnswerIndex === answerId) return styles.selectedAnswer; - if (!quiz) return null; + } + if(quizState === QuizState.SHOWING_RESULTS) + { + if(answerId === correctAnswerId) return styles.correctAnswer; + if(answerId !== correctAnswerId && selectedAnswerIndex === answerId) return styles.incorrectAnswer; + } + return styles.answerButton; +} - const currentQuestion: QuestionModel = quiz.questions[quiz.nbActualQuestion - 1]; +export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsAlreadyPlayed, navigation }: Props) { + const [currentQuestion, setCurrentQuestion] = useState<QuestionModel>(quiz.questions[quiz.nbActualQuestion - 1]); + const [questionIsFinished, setQuestionIsFinished] = useState(false); + const [selectedAnswerId, setSelectedAnswerId] = useState<number | null>(null); + const [validationButtonIsDisabled, setValidationButtonIsDisabled] = useState(true); + const [quizState, setQuizState] = useState(QuizState.ANSWERING); + const [correctAnswerId, setCorrectAnswerId] = useState<number | null>(null); - const handleConfirmAnswerPressed = async () => { - if(selectedAnswerIndex === null) return; - setIsDisabled(true); - setIsAnswered(true); + const {getAnswerQuiz} = useQuizService(); - const answers = await getAnswerQuiz(quiz.code, currentQuestion.id, currentQuestion.answers[selectedAnswerIndex].id); + useEffect(() => { + setCurrentQuestion(quiz.questions[quiz.nbActualQuestion - 1]); + setSelectedAnswerId(null); // Réinitialise la sélection + setQuestionIsFinished(false); // Réinitialise l'état de la question + setCorrectAnswerId(null); // Réinitialise l'id de la bonne réponse + setQuizState(QuizState.ANSWERING) + }, [quiz.nbActualQuestion]); + const getCorrectAnswer = (answers: AnswerModel[]): AnswerModel => { + const correctAnswer = answers.find((answer) => answer.isCorrect); + if (!correctAnswer) throw new Error("No correct answer found"); + return correctAnswer; + }; + const onValidation = async () => { + setQuizState(QuizState.LOADING); + const answerId = currentQuestion.answers.find((answer) => answer.id === selectedAnswerId)?.id; + if(!answerId) return; + const answers = await getAnswerQuiz(quiz.code, currentQuestion.id, answerId); if (HttpError.isHttpError(answers)) { - navigation.navigate("TabNavigator"); return; } + setQuizState(QuizState.SHOWING_RESULTS); + const correctAnswerIdFetched = getCorrectAnswer(answers).id; + if(selectedAnswerId === correctAnswerIdFetched) + setQuiz((prevQuiz) => ({ + ...prevQuiz, + score: prevQuiz.score + 1, + })); + setCorrectAnswerId(correctAnswerIdFetched); - const correctAnswer = getCorrectAnswer(answers); - const isAnswerCorrect = currentQuestion.answers[selectedAnswerIndex].id === correctAnswer.id; - - setSelectedAnswerIndex(selectedAnswerIndex); - setIsCorrect(isAnswerCorrect); - setCorrectAnswer(correctAnswer); - - await new Promise((resolve) => setTimeout(resolve, 2000)); + }; + const onContinue = () => { if (quiz.nbActualQuestion === quiz.questions.length) { navigation.navigate("EndQuiz", {quiz: quiz}); + return; } - setQuiz((prevQuiz) => ({ ...prevQuiz, nbActualQuestion: prevQuiz.nbActualQuestion + 1, - score: isAnswerCorrect ? prevQuiz.score + 1 : prevQuiz.score })); } - const handleAnswerPress = async (index: number) => { - if (isAnswered) return - setSelectedAnswerIndex(index); - }; - - const getCorrectAnswer = (answers: AnswerModel[]): AnswerModel => { - const correctAnswer = answers.find((answer) => answer.isCorrect); - if (!correctAnswer) throw new Error("No correct answer found"); - return correctAnswer; - }; - - const getButtonStyle = (index: number) => { - if (selectedAnswerIndex === null) return ButtonsStyles.answerButton; - - if(!isAnswered && index === selectedAnswerIndex && !isCorrect) return styles.selectedAnswer; - - if (index === selectedAnswerIndex && isCorrect !== null) { - return isCorrect ? styles.correctAnswer : styles.incorrectAnswer; - } - if (currentQuestion.answers[index].id === correctAnswer?.id) { - return styles.correctAnswer; + useEffect(() => { + if(selectedAnswerId !== null) { + setValidationButtonIsDisabled(false) + return; } - return ButtonsStyles.answerButton; - }; - - const getTextStyle = (index: number) => { - if (selectedAnswerIndex === null || !isAnswered && index === selectedAnswerIndex) return TextsStyles.defaultText; - + setValidationButtonIsDisabled(true) + }, [selectedAnswerId]); - - if (index === selectedAnswerIndex && isCorrect !== null) { - return isCorrect ? styles.correctText : styles.incorrectText; - } - if (currentQuestion.answers[index].id === correctAnswer?.id) { - return styles.correctText; + const onAnsweredButtonClicked = (answerId: number) => { + if(quizState === QuizState.SHOWING_RESULTS || quizState === QuizState.LOADING) { + return; } - return TextsStyles.defaultText; - }; + setSelectedAnswerId(answerId); + } return ( <View style={styles.buttonContainer}> @@ -130,14 +138,14 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA <DefaultButton key={index} text={answer.text} - handleButtonPressed={() => handleAnswerPress(index)} - buttonStyle={getButtonStyle(index)} - buttonText={getTextStyle(index)} + handleButtonPressed={() => onAnsweredButtonClicked(answer.id)} + buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)} + buttonText={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)} /> ))} {/* Ajout du PlayButton à la fin */} <View style={styles.playButtonContainer}> - <BlueButton onPress={handleConfirmAnswerPressed} text="Confirm" buttonStyle={{borderRadius: 50}} isDisabled={isDisabled}/> + <BlueButton onPress={() => quizState === QuizState.ANSWERING ? onValidation() : onContinue()} text={quizState === QuizState.ANSWERING ? "CONFIRM" : "CONTINUE"} buttonStyle={{borderRadius: 50}} isDisabled={false}/> </View> </> ) : ( @@ -147,6 +155,7 @@ export default function PlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsA ); } + const styles = StyleSheet.create({ buttonContainer: { display: 'flex', @@ -171,11 +180,20 @@ const styles = StyleSheet.create({ borderColor: 'red', borderWidth: 2, }, + loadingAnswer: { + ...ButtonsStyles.answerButton, + backgroundColor: 'grey', + borderWidth: 2, + }, selectedAnswer: { ...ButtonsStyles.answerButton, - borderColor: '#45128c', + borderColor: 'grey', borderWidth: 2, }, + answerText: { + color: 'black', + textAlign: 'center', + }, correctText: { fontWeight: 'bold', color: 'green', @@ -185,9 +203,21 @@ const styles = StyleSheet.create({ color: 'red', }, loadingText: { - fontSize: 18, - color: 'gray', + color: 'black', textAlign: 'center', }, + answerButton: { + backgroundColor: "#F3F3F3", + height: '15%', + alignItems: 'center', + width: '80%', + marginVertical: '3%', + justifyContent: "center", + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.25, + shadowRadius: 3.5, + elevation: 5, + }, }); -- GitLab From f76cd4bf39dadaef4ac5f3762eb7b91d179fbc76 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Mon, 16 Dec 2024 22:21:28 +0100 Subject: [PATCH 126/283] style: loading button style --- screens/PlayingQuiz/PlayingQuizBody.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index d319ab7..fff9677 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -182,7 +182,7 @@ const styles = StyleSheet.create({ }, loadingAnswer: { ...ButtonsStyles.answerButton, - backgroundColor: 'grey', + backgroundColor: '#4F6367', borderWidth: 2, }, selectedAnswer: { -- GitLab From 52657e8f807c28bbf8be772fe2742e3b67aa8f36 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 17 Dec 2024 21:11:38 +0100 Subject: [PATCH 127/283] feat: added community quiz service --- components/loading/LoadingIndicator.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 components/loading/LoadingIndicator.tsx diff --git a/components/loading/LoadingIndicator.tsx b/components/loading/LoadingIndicator.tsx new file mode 100644 index 0000000..e69de29 -- GitLab From fcc0500b3ef27f8511535d363b35a7ad9b40fc8b Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 17 Dec 2024 21:17:22 +0100 Subject: [PATCH 128/283] feat: added service for community quiz --- components/loading/LoadingIndicator.tsx | 22 +++++++++++++++++ screens/Home/HomeChild.tsx | 5 ++++ services/QuizService.ts | 32 +++++++++++++------------ 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/components/loading/LoadingIndicator.tsx b/components/loading/LoadingIndicator.tsx index e69de29..2520955 100644 --- a/components/loading/LoadingIndicator.tsx +++ b/components/loading/LoadingIndicator.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { View, StyleSheet, ActivityIndicator } from "react-native"; + +export default function LoadingIndicator() { + return ( + <View style={styles.container}> + <ActivityIndicator size="large" color="#2b73fe" /> + </View> + ); +} + + +const styles = StyleSheet.create({ + container: { + display: "flex", // Change le type de flex pour que la vue soit centrée + width: "100%", // Prend toute la largeur de l'écran + height: "100%", // Prend toute la hauteur de l'écran + ...StyleSheet.absoluteFillObject, // Prend toute la surface de l'écran + justifyContent: "center", // Centre verticalement + alignItems: "center", // Centre horizontalement + }, +}); diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 6e0460d..780947f 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -7,6 +7,7 @@ import ModalJoinQuiz from "../../components/ModalJoinQuiz"; import MenuButton from "../../components/buttons/MenuButton"; import {NavigationProp} from "@react-navigation/native"; import {useQuizService} from "../../services/QuizService"; +import HttpError from "../../services/HttpError"; interface Props { navigation: NavigationProp<any>; @@ -23,6 +24,10 @@ export default function HomeChild({navigation}: Props) { const handleButtonQuickGamePressed = async () => { const quizData = await getRandomQuiz() + if (quizData instanceof HttpError) { + console.error("Error while fetching random quiz:", quizData); + return; + } navigation.reset({ index: 0, routes: [ diff --git a/services/QuizService.ts b/services/QuizService.ts index bf63bbf..2c77eb5 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -128,32 +128,35 @@ export const useQuizService = () => { } }; - - const getQuizzesCommunity = async () : Promise<QuizModel[] | HttpError> => { + const getAllQuizzes = async (): Promise<QuizModel[] | HttpError> => { try { - // await axios.post(`${url}/quiz/community`, null, { - // withCredentials: true, // Inclut les cookies dans la requête - // }); - return []; + // Effectuer une requête GET pour récupérer les quizzes + const response = await axios.get(`${url}/quiz/all`, { + withCredentials: true, // Inclut les cookies pour une session authentifiée + }); + + // Transformation des données de chaque quiz en modèle QuizModel + const quizzes: QuizModel[] = response.data.map((quiz: any) => transformToQuizModel(quiz)); + + return quizzes; } catch (error: any) { + console.error("Error while fetching all quizzes:", error); + if (error.response) { return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); } else { return new HttpError(500, "Unexpected error: " + error.message); } } - } + }; - const getAllQuizzes = async (): Promise<QuizModel[] | HttpError> => { + const getCommunityQuiz = async (skip: number, take: number) => { try { - // Effectuer une requête GET pour récupérer les quizzes - const response = await axios.get(`${url}/quiz/all`, { + const response = await axios.get(`${url}/quiz/paginated?skip=${skip}&take=${take}`, { withCredentials: true, // Inclut les cookies pour une session authentifiée }); - // Transformation des données de chaque quiz en modèle QuizModel const quizzes: QuizModel[] = response.data.map((quiz: any) => transformToQuizModel(quiz)); - return quizzes; } catch (error: any) { console.error("Error while fetching all quizzes:", error); @@ -172,8 +175,7 @@ export const useQuizService = () => { getAnswerQuiz: getAnswerQuiz, remainingQuiz: remainingQuiz, getRandomQuiz: getRandomQuiz, - getQuizzesCommunity: getQuizzesCommunity, - getAllQuizzes: getAllQuizzes - + getAllQuizzes: getAllQuizzes, + getCommunityQuiz: getCommunityQuiz } } \ No newline at end of file -- GitLab From 7fae578aa8129d3387378294f5a4884ec39f7a2a Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 17 Dec 2024 22:47:56 +0100 Subject: [PATCH 129/283] chore: added package for infinite scroll --- package-lock.json | 186 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 5 +- 2 files changed, 185 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index f808720..34cf915 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,11 @@ "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", + "@tanstack/react-query": "^5.62.8", + "@tanstack/react-query-devtools": "^5.62.8", "axios": "^1.7.9", "expo": "~52.0.14", + "expo-splash-screen": "^0.29.13", "expo-status-bar": "~2.0.0", "he": "^1.2.0", "i18n-js": "^4.5.0", @@ -26,7 +29,8 @@ "react-native-dropdown-select-list": "^2.0.5", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", - "react-native-vector-icons": "^10.2.0" + "react-native-vector-icons": "^10.2.0", + "react-query": "^3.39.3" }, "devDependencies": { "@babel/core": "^7.20.0", @@ -2883,9 +2887,9 @@ } }, "node_modules/@expo/prebuild-config": { - "version": "8.0.21", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.0.21.tgz", - "integrity": "sha512-PVvt7+2dLzmf1X4HaoibnTtfoxnor0YEdu396eLv1SG+KacmN5lMz81yO/2MXvv0SDA6THomgBNvA/uzWV5twA==", + "version": "8.0.23", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.0.23.tgz", + "integrity": "sha512-Zf01kFiN2PISmLb0DhIAJh76v3J2oYUKSjiAtGZLOH0HUz59by/qdyU4mGHWdeyRdCCrLUA21Rct2MBykvRMsg==", "license": "MIT", "dependencies": { "@expo/config": "~10.0.4", @@ -2893,7 +2897,7 @@ "@expo/config-types": "^52.0.0", "@expo/image-utils": "^0.6.0", "@expo/json-file": "^9.0.0", - "@react-native/normalize-colors": "0.76.3", + "@react-native/normalize-colors": "0.76.5", "debug": "^4.3.1", "fs-extra": "^9.0.0", "resolve-from": "^5.0.0", @@ -2901,6 +2905,12 @@ "xml2js": "0.6.0" } }, + "node_modules/@expo/prebuild-config/node_modules/@react-native/normalize-colors": { + "version": "0.76.5", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.76.5.tgz", + "integrity": "sha512-6QRLEok1r55gLqj+94mEWUENuU5A6wsr2OoXpyq/CgQ7THWowbHtru/kRGRr6o3AQXrVnZheR60JNgFcpNYIug==", + "license": "MIT" + }, "node_modules/@expo/prebuild-config/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -3960,6 +3970,59 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@tanstack/query-core": { + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.8.tgz", + "integrity": "sha512-4fV31vDsUyvNGrKIOUNPrZztoyL187bThnoQOvAXEVlZbSiuPONpfx53634MKKdvsDir5NyOGm80ShFaoHS/mw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.61.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.61.4.tgz", + "integrity": "sha512-21Tw+u8E3IJJj4A/Bct4H0uBaDTEu7zBrR79FeSyY+mS2gx5/m316oDtJiKkILc819VSTYt+sFzODoJNcpPqZQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.8.tgz", + "integrity": "sha512-8TUstKxF/fysHonZsWg/hnlDVgasTdHx6Q+f1/s/oPKJBJbKUWPZEHwLTMOZgrZuroLMiqYKJ9w69Abm8mWP0Q==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.62.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.62.8.tgz", + "integrity": "sha512-SwjXjQTRONd9WPeKVQQ9framG7YNqPV8PS+EGNVNXAyz2XThulMRCvZnh2+3DggnjcYM7YcpnuoZ4RH7q13p0g==", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.61.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.62.8", + "react": "^18 || ^19" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4698,6 +4761,22 @@ "node": ">=8" } }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "node_modules/browserslist": { "version": "4.24.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", @@ -5517,6 +5596,12 @@ "node": ">=0.10" } }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -5968,6 +6053,18 @@ "invariant": "^2.2.4" } }, + "node_modules/expo-splash-screen": { + "version": "0.29.18", + "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.29.18.tgz", + "integrity": "sha512-bTBY+LF6YtYen2j60yGNh2SX/tG4UXZAyBCMMriOSiZZ7LSCs3ARyEufaSiWk+ckWShTeMqItOnaAN/CAF8MJA==", + "license": "MIT", + "dependencies": { + "@expo/prebuild-config": "^8.0.23" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-status-bar": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-2.0.0.tgz", @@ -7114,6 +7211,12 @@ "integrity": "sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==", "license": "MIT" }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7683,6 +7786,16 @@ "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", "license": "Apache-2.0" }, + "node_modules/match-sorter": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", + "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, "node_modules/md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", @@ -8142,6 +8255,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==", + "license": "MIT" + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -8346,6 +8465,15 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "license": "ISC", + "dependencies": { + "big-integer": "^1.6.16" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -8534,6 +8662,12 @@ "node": ">=0.10.0" } }, + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==", + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -9543,6 +9677,32 @@ "async-limiter": "~1.0.0" } }, + "node_modules/react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -9671,6 +9831,12 @@ "regjsparser": "bin/parser" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", + "license": "MIT" + }, "node_modules/remove-trailing-slash": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz", @@ -11061,6 +11227,16 @@ "node": ">= 4.0.0" } }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 0b14e67..b58c2ac 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", + "@tanstack/react-query": "^5.62.8", + "@tanstack/react-query-devtools": "^5.62.8", "axios": "^1.7.9", "expo": "~52.0.14", "expo-splash-screen": "^0.29.13", @@ -28,7 +30,8 @@ "react-native-dropdown-select-list": "^2.0.5", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", - "react-native-vector-icons": "^10.2.0" + "react-native-vector-icons": "^10.2.0", + "react-query": "^3.39.3" }, "devDependencies": { "@babel/core": "^7.20.0", -- GitLab From e314acd592e719db02e2ecb3bdb4081c35abaae5 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 17 Dec 2024 22:48:18 +0100 Subject: [PATCH 130/283] chore: added QueryClientProvider in app.tsx --- App.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/App.tsx b/App.tsx index 41a0a91..4b3b1c0 100644 --- a/App.tsx +++ b/App.tsx @@ -5,6 +5,8 @@ import { SafeAreaProvider } from "react-native-safe-area-context"; import StackNavigator from "./routes/StackNavigator"; import { useEffect } from 'react'; import * as SplashScreen from 'expo-splash-screen'; +import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; +const queryClient = new QueryClient(); export default function App() { // LogBox.ignoreAllLogs(true); @@ -26,11 +28,13 @@ export default function App() { const { t } = useTranslation(); return ( - <SafeAreaProvider> - <I18nextProvider i18n={i18n}> - <StackNavigator/> - </I18nextProvider> - </SafeAreaProvider> + <QueryClientProvider client={queryClient}> + <SafeAreaProvider> + <I18nextProvider i18n={i18n}> + <StackNavigator/> + </I18nextProvider> + </SafeAreaProvider> + </QueryClientProvider> ); } -- GitLab From 8c110da1edefe984e5128388c91949333616dd91 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 17 Dec 2024 22:56:05 +0100 Subject: [PATCH 131/283] refactor: added props --- templates/TemplateQuizList.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index c204506..7782d71 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -25,14 +25,24 @@ interface Props { * @param quizList - La liste des quiz à afficher * @param buttonGoBack - boolean qui dit si le bouton de retour doit être affiché */ -export default function TemplateQuizList({navigation, title, setTextInInput, quizList, buttonGoBack, isLoadingData}: Props) { + +interface Props { + navigation: NavigationProp<any> + title: string + setTextInInput: (text: string) => void + quizList: QuizModel[] | null | undefined + buttonGoBack: boolean + isLoadingData: boolean + nextPage?: () => void; +} +export default function TemplateQuizList({navigation, title, setTextInInput, quizList, buttonGoBack, isLoadingData, nextPage}: Props) { return ( <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={buttonGoBack}> <View style={styles.globalContainer}> <Text style={styles.title}>{title}</Text> <View style={styles.contentContainer}> {/*<InputPlayQuiz setText={setTextInInput} noTitle={true} />*/} - <QuizList quizList={quizList} navigation={navigation} isLoadingData={isLoadingData}/> + <QuizList quizList={quizList} navigation={navigation} isLoadingData={isLoadingData} nextPage={nextPage}/> </View> </View> </TemplateMenu> -- GitLab From 7a5e6ef6a342064ee08509d43360e5639444bd7c Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 17 Dec 2024 22:56:25 +0100 Subject: [PATCH 132/283] feat: added infinite scroll --- components/lists/QuizList.tsx | 15 ++++++++++--- screens/Community/Community.tsx | 39 +++++++++++++++++++++------------ services/QuizService.ts | 2 +- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index 4185602..57146b9 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -7,6 +7,7 @@ interface Props { navigation: NavigationProp<any>; quizList: QuizModel[] | null | undefined; isLoadingData: boolean; + nextPage?: () => void; } /** @@ -39,7 +40,7 @@ interface Props { * @returns Un composant React contenant une liste interactive de quiz ou des messages en fonction de l'état. */ -export default function QuizList({ navigation, quizList, isLoadingData }: Props) { +export default function QuizList({ navigation, quizList, isLoadingData, nextPage }: Props) { const onQuizPressed = (quiz: QuizModel) => { navigation.navigate("InformationsOfQuiz", { quiz: quiz }); }; @@ -48,7 +49,7 @@ export default function QuizList({ navigation, quizList, isLoadingData }: Props) <TouchableOpacity style={styles.quizItem} onPress={() => onQuizPressed(item)}> <Text style={styles.quizTitle}>{item.code}</Text> <Text style={styles.quizDetails}> - {item.nbQuestions} QUESTIONS | {item.category.toUpperCase()} + {item.nbQuestions} QUESTIONS | {item.category ? item.category.toUpperCase() : ""} </Text> </TouchableOpacity> ); @@ -67,6 +68,15 @@ export default function QuizList({ navigation, quizList, isLoadingData }: Props) keyExtractor={(item) => item.code} showsVerticalScrollIndicator={true} contentContainerStyle={styles.listContent} + onEndReached={() => { + console.log("onEndReached triggered"); + if (nextPage) { + console.log("Calling nextPage..."); + nextPage(); + } + }} + + onEndReachedThreshold={0.5} /> ) : ( <Text style={styles.emptyMessage}>No quizzes available.</Text> @@ -91,7 +101,6 @@ const styles = StyleSheet.create({ width: "100%", // Assurez-vous que le conteneur occupe toute la largeur }, listContent: { - paddingVertical: 10, }, quizItem: { backgroundColor: "#f3f3f3", diff --git a/screens/Community/Community.tsx b/screens/Community/Community.tsx index cee2a88..f9ece3c 100644 --- a/screens/Community/Community.tsx +++ b/screens/Community/Community.tsx @@ -5,32 +5,43 @@ import {useEffect, useState} from "react"; import QuizModel from "../../models/QuizModel"; import {useQuizService} from "../../services/QuizService"; import HttpError from "../../services/HttpError"; +import {useInfiniteQuery} from "@tanstack/react-query"; interface Props { navigation: NavigationProp<any> } export default function Community({navigation}: Props) { - const {getAllQuizzes} = useQuizService(); + const {getCommunityQuiz} = useQuizService(); const [input, setInput] = useState<string>("") const [loading, setLoading] = useState<boolean>(false) - const [quizList, setQuizList] = useState<QuizModel[] | null>() + const fetchCommunityQuizzes = async ({pageParam = 1}) => { + console.log("fetchCommunityQuizzes"); + const quizzes = await getCommunityQuiz(pageParam,7); + if (HttpError.isHttpError(quizzes)) { + console.error("Error while fetching community quizzes:", quizzes); + return []; + } + return quizzes; + } - useEffect( () => { - setLoading(true) - getAllQuizzes().then((quizzes: QuizModel[] | HttpError) => { - if (quizzes instanceof HttpError) { - console.log("Error while fetching quizzes:", quizzes); - } else { - setQuizList(quizzes); - } - setLoading(false) - }) - }, []) + const { + data, + fetchNextPage, + } = useInfiniteQuery({ + queryKey: ["communityQuizzes"], + queryFn: fetchCommunityQuizzes, + initialPageParam: 0, + getNextPageParam: (lastPage, allPages) => { + // Si la dernière page a moins de 7 quizzes, il n'y a plus de page suivante + if (lastPage.length < 7) return undefined; + // On augmente skip de 7 pour la prochaine requête + return allPages.length * 7; + }}); return ( - <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={quizList} navigation={navigation} buttonGoBack={false} isLoadingData={loading}/> + <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage}/> ); } \ No newline at end of file diff --git a/services/QuizService.ts b/services/QuizService.ts index 2c77eb5..499ba54 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -156,10 +156,10 @@ export const useQuizService = () => { withCredentials: true, // Inclut les cookies pour une session authentifiée }); + const quizzes: QuizModel[] = response.data.map((quiz: any) => transformToQuizModel(quiz)); return quizzes; } catch (error: any) { - console.error("Error while fetching all quizzes:", error); if (error.response) { return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); -- GitLab From 7e1098e77b87151d44fab453859d4c36d796bc25 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 17 Dec 2024 22:58:27 +0100 Subject: [PATCH 133/283] refactor: removed console.log --- components/lists/QuizList.tsx | 2 -- hooks/UseUser.ts | 2 -- screens/Community/Community.tsx | 1 - screens/Profil/ProfilChild.tsx | 1 - 4 files changed, 6 deletions(-) diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index 57146b9..decffdc 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -69,9 +69,7 @@ export default function QuizList({ navigation, quizList, isLoadingData, nextPage showsVerticalScrollIndicator={true} contentContainerStyle={styles.listContent} onEndReached={() => { - console.log("onEndReached triggered"); if (nextPage) { - console.log("Calling nextPage..."); nextPage(); } }} diff --git a/hooks/UseUser.ts b/hooks/UseUser.ts index 810fc17..7d30040 100644 --- a/hooks/UseUser.ts +++ b/hooks/UseUser.ts @@ -6,7 +6,6 @@ export const useUser = () => { if (!item) { return undefined; } - console.log("User informations loaded"); return JSON.parse(item); } catch (error) { console.error("Erreur lors de la récupération de la donnée des informations utilisateur", error); @@ -17,7 +16,6 @@ export const useUser = () => { const setUser = async <T>(key: string, newValue: T) => { try { await AsyncStorage.setItem(key, JSON.stringify(newValue)); - console.log("User informations saved"); } catch (error) { console.error("Erreur lors de l'enregistrement de la donnée", error); } diff --git a/screens/Community/Community.tsx b/screens/Community/Community.tsx index f9ece3c..4b3e111 100644 --- a/screens/Community/Community.tsx +++ b/screens/Community/Community.tsx @@ -16,7 +16,6 @@ export default function Community({navigation}: Props) { const [loading, setLoading] = useState<boolean>(false) const fetchCommunityQuizzes = async ({pageParam = 1}) => { - console.log("fetchCommunityQuizzes"); const quizzes = await getCommunityQuiz(pageParam,7); if (HttpError.isHttpError(quizzes)) { console.error("Error while fetching community quizzes:", quizzes); diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index 8357747..b3dee69 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -21,7 +21,6 @@ export default function ProfilChild({ navigation }: Props) { useEffect(() => { getInformationsUser().then((user: UserModel | HttpError) => { - console.log(user); if (user instanceof HttpError) { return; } -- GitLab From f4cb0e2b603107a614ea1dd9a578a7d15d618c66 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 17 Dec 2024 23:46:29 +0100 Subject: [PATCH 134/283] refactor: changing structure of models --- components/lists/QuizList.tsx | 26 +++++++++---------- helper/QuizHelper.ts | 8 +++--- models/QuestionModel.ts | 2 +- models/QuizModel.ts | 44 +++++++++++++++++++++++++++++++-- screens/Community/Community.tsx | 2 +- services/QuizService.ts | 12 ++++++--- templates/TemplateQuizList.tsx | 6 ++--- 7 files changed, 73 insertions(+), 27 deletions(-) diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index decffdc..45ff335 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -1,14 +1,9 @@ import React from "react"; import { FlatList, View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from "react-native"; -import QuizModel from "../../models/QuizModel"; +import {QuizModel, Quiz} from "../../models/QuizModel"; import { NavigationProp } from "@react-navigation/native"; -interface Props { - navigation: NavigationProp<any>; - quizList: QuizModel[] | null | undefined; - isLoadingData: boolean; - nextPage?: () => void; -} + /** * QuizList : Un composant affichant une liste de quiz avec leur titre, nombre de questions, et catégorie. @@ -39,17 +34,22 @@ interface Props { * * @returns Un composant React contenant une liste interactive de quiz ou des messages en fonction de l'état. */ - +interface Props { + navigation: NavigationProp<any>; + quizList: Quiz[] | null | undefined; + isLoadingData: boolean; + nextPage?: () => void; +} export default function QuizList({ navigation, quizList, isLoadingData, nextPage }: Props) { - const onQuizPressed = (quiz: QuizModel) => { + const onQuizPressed = (quiz: Quiz) => { navigation.navigate("InformationsOfQuiz", { quiz: quiz }); }; - const renderQuizItem = ({ item }: { item: QuizModel }) => ( + const renderQuizItem = ({ item }: { item: Quiz }) => ( <TouchableOpacity style={styles.quizItem} onPress={() => onQuizPressed(item)}> - <Text style={styles.quizTitle}>{item.code}</Text> + <Text style={styles.quizTitle}>{item.id}</Text> <Text style={styles.quizDetails}> - {item.nbQuestions} QUESTIONS | {item.category ? item.category.toUpperCase() : ""} + {item.questionCount} QUESTIONS | {item.category ? item.category.name.toUpperCase() : ""} </Text> </TouchableOpacity> ); @@ -65,7 +65,7 @@ export default function QuizList({ navigation, quizList, isLoadingData, nextPage <FlatList data={quizList} renderItem={renderQuizItem} - keyExtractor={(item) => item.code} + keyExtractor={(item) => item.id} showsVerticalScrollIndicator={true} contentContainerStyle={styles.listContent} onEndReached={() => { diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index 6456903..e201c6f 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -1,6 +1,6 @@ // Fonction utilitaire pour mélanger les réponses -import QuizModel from "../models/QuizModel"; +import {QuizModel} from "../models/QuizModel"; import QuestionModel from "../models/QuestionModel"; import AnswerModel from "../models/AnswerModel"; @@ -55,7 +55,8 @@ export const transformToQuizModel = (data: any): QuizModel => { return { id: question.id, question: question.text, // Texte de la question - category: question.category.name, + category: "aa", + // category: question.category.name ? question.category.name : "Community Quizz", // Extraction du nom de la catégorie answers: answers, // Réponses associées multiple: question.type === "multiple", // Détermine si c'est une question multiple nbOfTheQuestion: question.order, // Numéro de la question @@ -65,7 +66,8 @@ export const transformToQuizModel = (data: any): QuizModel => { // Création de l'objet QuizModel const quiz: QuizModel = { code: data.id, // Mapping de codeQuiz vers code - category: data.category.name, // Extraction du nom de la catégorie + category: "aa", + // category: data.category.name ? data.category.name : "Community Quizz", // Extraction du nom de la catégorie questions: questions, // Questions transformées nbQuestions: data.questionCount, // Nombre total de questions nbActualQuestion: data.questionIndex, // Index actuel de la question diff --git a/models/QuestionModel.ts b/models/QuestionModel.ts index 527e1fc..a823f5b 100644 --- a/models/QuestionModel.ts +++ b/models/QuestionModel.ts @@ -3,7 +3,7 @@ import AnswerModel from "./AnswerModel"; export default interface QuestionModel { id: number; question: string; - category: string; + category?: string; answers: AnswerModel[]; multiple: boolean; nbOfTheQuestion: number; diff --git a/models/QuizModel.ts b/models/QuizModel.ts index 6e1cbb9..38d6cda 100644 --- a/models/QuizModel.ts +++ b/models/QuizModel.ts @@ -1,10 +1,50 @@ import QuestionModel from "./QuestionModel"; -export default interface QuizModel { +export interface QuizModel { code: string; - category: string; + category?: string; questions: QuestionModel[]; nbQuestions: number; nbActualQuestion: number; score: number; } + +interface Difficulty { + id: number; + name: string; +} + +interface Category { + id: number; + name: string; +} + +export interface Answer { + id: number; + text: string; + isCorrect?: boolean; + questionId: number; +} + +export interface Question { + id: number; + text: string; + categoryId: number; + quizId: string; + order: number; + answers: Answer[]; +} +export interface Quiz { + id: string; + name: string; + description: string; + score: number; + questionIndex: number; + questionCount: number; + categoryId: number; + difficultyId: number; + authorId: number, + category?: Category, + difficulty: Difficulty, + questions: Question[], +} \ No newline at end of file diff --git a/screens/Community/Community.tsx b/screens/Community/Community.tsx index 4b3e111..772faf2 100644 --- a/screens/Community/Community.tsx +++ b/screens/Community/Community.tsx @@ -2,7 +2,7 @@ import {View, Text} from "react-native"; import TemplateQuizList from "../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; import {useEffect, useState} from "react"; -import QuizModel from "../../models/QuizModel"; +import {QuizModel} from "../../models/QuizModel"; import {useQuizService} from "../../services/QuizService"; import HttpError from "../../services/HttpError"; import {useInfiniteQuery} from "@tanstack/react-query"; diff --git a/services/QuizService.ts b/services/QuizService.ts index 499ba54..f83ab83 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -2,8 +2,9 @@ import {EDifficulty} from "../models/EDifficulty"; import {transformToAnswerModel, transformToQuizModel} from "../helper/QuizHelper"; import AnswerModel from "../models/AnswerModel"; import HttpError from "./HttpError"; -import QuizModel from "../models/QuizModel"; +import {Quiz, QuizModel} from "../models/QuizModel"; import axios from "axios"; +import QuestionModel from "../models/QuestionModel"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API @@ -150,15 +151,18 @@ export const useQuizService = () => { } }; + + + + const getCommunityQuiz = async (skip: number, take: number) => { try { - const response = await axios.get(`${url}/quiz/paginated?skip=${skip}&take=${take}`, { + const response = await axios.get<Quiz[]>(`${url}/quiz/paginated?skip=${skip}&take=${take}`, { withCredentials: true, // Inclut les cookies pour une session authentifiée }); - const quizzes: QuizModel[] = response.data.map((quiz: any) => transformToQuizModel(quiz)); - return quizzes; + return response.data; } catch (error: any) { if (error.response) { diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index 7782d71..1ec5fec 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -4,13 +4,13 @@ import TemplateMenu from "./TemplateMenu"; import {NavigationProp} from "@react-navigation/native"; import InputPlayQuiz from "../components/PlayQuiz/InputPlayQuiz"; import QuizList from "../components/lists/QuizList"; -import QuizModel from "../models/QuizModel"; +import {QuizModel,Quiz} from "../models/QuizModel"; interface Props { navigation: NavigationProp<any> title: string setTextInInput: (text: string) => void - quizList: QuizModel[] | null | undefined + quizList: Quiz[] | null | undefined buttonGoBack: boolean isLoadingData: boolean } @@ -30,7 +30,7 @@ interface Props { navigation: NavigationProp<any> title: string setTextInInput: (text: string) => void - quizList: QuizModel[] | null | undefined + quizList: Quiz[] | null | undefined buttonGoBack: boolean isLoadingData: boolean nextPage?: () => void; -- GitLab From 1526a4acd7c8aba20d5293a7bfbd8e5147d0bcc5 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 18 Dec 2024 00:09:15 +0100 Subject: [PATCH 135/283] refactor: changing structure of models --- components/PlayQuiz/AboutAQuiz.tsx | 8 +++--- screens/EndQuiz/EndQuizChild.tsx | 10 +++---- screens/PlayQuiz/PlayQuizJoinQuiz.tsx | 4 +-- screens/PlayingQuiz/PlayingQuiz.tsx | 6 ++--- screens/PlayingQuiz/PlayingQuizBody.tsx | 18 ++++++------- screens/PlayingQuiz/PlayingQuizHeader.tsx | 14 +++++----- services/QuizService.ts | 32 +++++++++-------------- 7 files changed, 43 insertions(+), 49 deletions(-) diff --git a/components/PlayQuiz/AboutAQuiz.tsx b/components/PlayQuiz/AboutAQuiz.tsx index 1e2249a..e396031 100644 --- a/components/PlayQuiz/AboutAQuiz.tsx +++ b/components/PlayQuiz/AboutAQuiz.tsx @@ -1,9 +1,9 @@ import React from "react"; import { View, Text, StyleSheet } from "react-native"; -import QuizModel from "../../models/QuizModel"; +import {Quiz} from "../../models/QuizModel"; interface Props { - quiz?: QuizModel; + quiz?: Quiz; } export default function AboutAQuiz({quiz} : Props) { return ( @@ -11,8 +11,8 @@ export default function AboutAQuiz({quiz} : Props) { <Text style={styles.title}>About this quiz</Text> <View style={styles.separator} /> <View style={styles.infoContainer}> - <Text style={styles.text}>Category : {quiz ? quiz.category : "?"}</Text> - <Text style={styles.text}>Question : {quiz ? quiz.nbActualQuestion : "?"}/{quiz ? quiz.nbQuestions : "?"}</Text> + <Text style={styles.text}>Category : {quiz ? quiz.category?.name : "?"}</Text> + <Text style={styles.text}>Question : {quiz ? quiz.questionIndex : "?"}/{quiz ? quiz.questionCount : "?"}</Text> <Text style={styles.text}>Score : {quiz ? quiz.score : "?"}</Text> </View> diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/EndQuiz/EndQuizChild.tsx index cdda848..39eb6ad 100644 --- a/screens/EndQuiz/EndQuizChild.tsx +++ b/screens/EndQuiz/EndQuizChild.tsx @@ -1,19 +1,19 @@ import { View, Image, Text, StyleSheet } from "react-native"; import DefaultButton from "../../components/DefaultButton"; import { NavigationProp, RouteProp } from "@react-navigation/native"; -import QuizModel from "../../models/QuizModel"; +import {Quiz} from "../../models/QuizModel"; interface Props { navigation: NavigationProp<any>; - quiz: QuizModel; + quiz: Quiz; } export default function EndQuizChild({ navigation, quiz }: Props) { const handleRestartPress = () => { - quiz.nbActualQuestion = 1; + quiz.questionIndex = 1; quiz.score = 0; navigation.reset({ index: 0, @@ -36,8 +36,8 @@ export default function EndQuizChild({ navigation, quiz }: Props) { <Image source={require('../../assets/TitleApp.png')}/> </View> <View style={styles.containerBody}> - <Text style={styles.TextBodyTitle}>{((quiz.score/quiz.nbQuestions)*100 > 50 ) ? 'Victory' : 'Defeat'}</Text> - <Text style={styles.TextBody}>you finish the test with a score of {`${quiz.score}/${quiz.nbQuestions}`} ! </Text> + <Text style={styles.TextBodyTitle}>{((quiz.score/quiz.questionCount)*100 > 50 ) ? 'Victory' : 'Defeat'}</Text> + <Text style={styles.TextBody}>you finish the test with a score of {`${quiz.score}/${quiz.questionCount}`} ! </Text> <Image source={require('../../assets/uWin.png')} /> {/*<DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton>*/} <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu} buttonStyle={styles.blueButton} buttonText={styles.blueButtonText}></DefaultButton> diff --git a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx index a1d2523..eaa763f 100644 --- a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx +++ b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx @@ -3,7 +3,7 @@ import AboutAQuiz from "../../components/PlayQuiz/AboutAQuiz"; import BlueButton from "../../components/PlayQuiz/BlueButton"; import React, { useEffect } from "react"; import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; -import QuizModel from "../../models/QuizModel"; +import {Quiz} from "../../models/QuizModel"; import { useQuizService } from "../../services/QuizService"; import HttpError from "../../services/HttpError"; import {getIdOfCategory} from "../../helper/QuizHelper"; @@ -14,7 +14,7 @@ interface Props { } export default function PlayQuizJoinQuiz({navigation}: Props) { const [codeQuiz, setCodeQuiz] = React.useState(""); - const [quiz, setQuiz] = React.useState<QuizModel>(); + const [quiz, setQuiz] = React.useState<Quiz>(); const [error, setError] = React.useState<string>(""); const { remainingQuiz } = useQuizService(); diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index e16dd84..7f413d9 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -3,7 +3,7 @@ import PlayingQuizHeader from "./PlayingQuizHeader"; import {useEffect, useState} from "react"; import TemplateDuo from "../../templates/TemplateDuo"; import QuestionModel from "../../models/QuestionModel"; -import QuizModel from "../../models/QuizModel"; +import {Quiz} from "../../models/QuizModel"; import {NavigationProp, RouteProp} from "@react-navigation/native"; import ConfirmModal from "../../components/PlayingQuiz/ComfirmModal"; @@ -25,7 +25,7 @@ const optionsTheme = [ type RoutePropsType = { PlayingQuiz: { - quizRecovered: QuizModel; + quizRecovered: Quiz; }; }; @@ -37,7 +37,7 @@ interface Props { export default function PlayingQuiz({route, navigation}:Props) { const {quizRecovered} = route.params; const [isAlreadyPlayed, setIsAlreadyPlayed] = useState(false); - const [quiz, setQuiz] = useState<QuizModel>(quizRecovered); + const [quiz, setQuiz] = useState<Quiz>(quizRecovered); return ( <TemplateDuo diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index fff9677..fa256d4 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -2,7 +2,7 @@ import { View, StyleSheet, Text } from "react-native"; import DefaultButton from "../../components/DefaultButton"; import { ButtonsStyles } from "../../styles/ButtonsStyles"; import { TextsStyles } from "../../styles/TextsStyles"; -import QuizModel from "../../models/QuizModel"; +import {Question, Quiz} from "../../models/QuizModel"; import React, { useState, useEffect, SetStateAction, Dispatch } from "react"; import { useQuizService } from "../../services/QuizService"; import QuestionModel from "../../models/QuestionModel"; @@ -12,8 +12,8 @@ import HttpError from "../../services/HttpError"; import BlueButton from "../../components/PlayQuiz/BlueButton"; interface Props { - quiz: QuizModel; - setQuiz: Dispatch<SetStateAction<QuizModel>>; + quiz: Quiz; + setQuiz: Dispatch<SetStateAction<Quiz>>; isAlreadyPlayed: boolean; setIsAlreadyPlayed: (isAlreadyPlayed: boolean) => void; navigation: NavigationProp<any>; @@ -63,7 +63,7 @@ const getStyleOfAnswerButton = (answerId: number, selectedAnswerIndex: number | } export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsAlreadyPlayed, navigation }: Props) { - const [currentQuestion, setCurrentQuestion] = useState<QuestionModel>(quiz.questions[quiz.nbActualQuestion - 1]); + const [currentQuestion, setCurrentQuestion] = useState<Question>(quiz.questions[quiz.questionIndex - 1]); const [questionIsFinished, setQuestionIsFinished] = useState(false); const [selectedAnswerId, setSelectedAnswerId] = useState<number | null>(null); const [validationButtonIsDisabled, setValidationButtonIsDisabled] = useState(true); @@ -73,12 +73,12 @@ export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIs const {getAnswerQuiz} = useQuizService(); useEffect(() => { - setCurrentQuestion(quiz.questions[quiz.nbActualQuestion - 1]); + setCurrentQuestion(quiz.questions[quiz.questionIndex - 1]); setSelectedAnswerId(null); // Réinitialise la sélection setQuestionIsFinished(false); // Réinitialise l'état de la question setCorrectAnswerId(null); // Réinitialise l'id de la bonne réponse setQuizState(QuizState.ANSWERING) - }, [quiz.nbActualQuestion]); + }, [quiz.questionIndex]); const getCorrectAnswer = (answers: AnswerModel[]): AnswerModel => { const correctAnswer = answers.find((answer) => answer.isCorrect); @@ -89,7 +89,7 @@ export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIs setQuizState(QuizState.LOADING); const answerId = currentQuestion.answers.find((answer) => answer.id === selectedAnswerId)?.id; if(!answerId) return; - const answers = await getAnswerQuiz(quiz.code, currentQuestion.id, answerId); + const answers = await getAnswerQuiz(quiz.id, currentQuestion.id, answerId); if (HttpError.isHttpError(answers)) { return; } @@ -105,13 +105,13 @@ export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIs }; const onContinue = () => { - if (quiz.nbActualQuestion === quiz.questions.length) { + if (quiz.questionIndex === quiz.questions.length) { navigation.navigate("EndQuiz", {quiz: quiz}); return; } setQuiz((prevQuiz) => ({ ...prevQuiz, - nbActualQuestion: prevQuiz.nbActualQuestion + 1, + questionIndex: prevQuiz.questionIndex + 1, })); } diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index cb14415..581c895 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -1,6 +1,6 @@ import { View, StyleSheet, Text } from "react-native"; import { TextsStyles } from "../../styles/TextsStyles"; -import QuizModel from "../../models/QuizModel"; +import {Quiz} from "../../models/QuizModel"; import {useTranslation} from "react-i18next"; import DefaultButton from "../../components/DefaultButton"; import GoHomeButton from "../../components/PlayingQuiz/GoHomeButton"; @@ -10,7 +10,7 @@ import {useState} from "react"; interface Props { navigation: NavigationProp<any> - quiz: QuizModel; + quiz: Quiz; } export default function PlayingQuizHeader({navigation, quiz}: Props) { @@ -28,26 +28,26 @@ export default function PlayingQuizHeader({navigation, quiz}: Props) { }); } - if (quiz.nbQuestions <= quiz.nbActualQuestion - 1) return; + if (quiz.questionCount <= quiz.questionIndex - 1) return; return ( <View style={styles.container}> <View style={styles.header}> <GoHomeButton navigation={navigation} onPress={setIsConfirmModalVisible}/> - <Text style={styles.codeQuizText}>ID QUIZZ : {quiz ? quiz.code : "?"}</Text> + <Text style={styles.codeQuizText}>ID QUIZZ : {quiz ? quiz.id : "?"}</Text> </View> <View style={styles.questionAndScoreContainer}> <View style={styles.InformationsContainer}> <Text style={TextsStyles.titleText}>{t("app.screens.question.question")}</Text> - <Text style={TextsStyles.titleText}>{quiz ? quiz.nbActualQuestion : "?"}/{quiz ? quiz.nbQuestions : "?"}</Text> + <Text style={TextsStyles.titleText}>{quiz ? quiz.questionIndex : "?"}/{quiz ? quiz.questionCount : "?"}</Text> </View> <View style={styles.InformationsContainer}> <Text style={TextsStyles.titleText}>{t("app.screens.question.score")}</Text> - <Text style={TextsStyles.titleText}>{quiz ? quiz.score : 0}/{quiz ? quiz.nbQuestions : "?"}</Text> + <Text style={TextsStyles.titleText}>{quiz ? quiz.score : 0}/{quiz ? quiz.questionCount : "?"}</Text> </View> </View> <View style={styles.QuizQuestionContainer}> - <Text style={TextsStyles.subtitleText}>{quiz.questions[quiz.nbActualQuestion-1].question ? quiz.questions[quiz.nbActualQuestion-1].question : "Loading..."}</Text> + <Text style={TextsStyles.subtitleText}>{quiz.questions[quiz.questionIndex-1].text ? quiz.questions[quiz.questionIndex-1].text : "Loading..."}</Text> </View> <ConfirmModal visible={isConfirmModalVisible} onClose={()=>setIsConfirmModalVisible(false)} onConfirm={handleConfirmModalConfirm}/> diff --git a/services/QuizService.ts b/services/QuizService.ts index f83ab83..10405c1 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -2,14 +2,14 @@ import {EDifficulty} from "../models/EDifficulty"; import {transformToAnswerModel, transformToQuizModel} from "../helper/QuizHelper"; import AnswerModel from "../models/AnswerModel"; import HttpError from "./HttpError"; -import {Quiz, QuizModel} from "../models/QuizModel"; +import {Answer, Quiz, QuizModel} from "../models/QuizModel"; import axios from "axios"; import QuestionModel from "../models/QuestionModel"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API - const generateQuiz = async (amount: number, category: number, difficulty: string) : Promise<QuizModel | HttpError> => { + const generateQuiz = async (amount: number, category: number, difficulty: string) : Promise<Quiz | HttpError> => { const requestBody = { amount, category, @@ -17,15 +17,14 @@ export const useQuizService = () => { }; try { - const response = await axios.post(`${url}/quiz/create`, requestBody, { + const response = await axios.post<Quiz>(`${url}/quiz/create`, requestBody, { headers: { "Content-Type": "application/json", }, }); // Récupération des données retournées par l'API - const quiz = transformToQuizModel(response.data); - return quiz; + return response.data; } catch (error: any) { console.error("Error while generating quiz:", error); @@ -39,21 +38,20 @@ export const useQuizService = () => { } } - const remainingQuiz = async (id: string): Promise<QuizModel | HttpError> => { + const remainingQuiz = async (id: string): Promise<Quiz | HttpError> => { const requestBody = { id, }; try { - const response = await axios.post(`${url}/quiz/remaining`, requestBody, { + const response = await axios.post<Quiz>(`${url}/quiz/remaining`, requestBody, { headers: { "Content-Type": "application/json", }, }); // Récupération des données retournées par l'API - const quiz = transformToQuizModel(response.data); - return quiz; + return response.data; } catch (error: any) { console.error("Error while remaining quiz:", error); @@ -71,7 +69,7 @@ export const useQuizService = () => { codeQuiz: string, questionID: number, answerID: number - ): Promise<AnswerModel[] | HttpError> => { + ): Promise<Answer[] | HttpError> => { try { const requestBody = { id: codeQuiz, @@ -79,15 +77,13 @@ export const useQuizService = () => { answerID: answerID, }; - const response = await axios.post(`${url}/quiz/answer`, requestBody, { + const response = await axios.post<Answer[]>(`${url}/quiz/answer`, requestBody, { headers: { "Content-Type": "application/json", }, }); - // Récupération des données retournées par l'API - const answers = transformToAnswerModel(response.data); - return answers; + return response.data; } catch (error: any) { console.error("Error while fetching answers:", error); @@ -101,21 +97,19 @@ export const useQuizService = () => { } }; - const getRandomQuiz = async (): Promise<QuizModel | HttpError> => { + const getRandomQuiz = async (): Promise<Quiz | HttpError> => { try { const requestBody = { amount: 10, }; - const response = await axios.post(`${url}/quiz/create`, requestBody, { + const response = await axios.post<Quiz>(`${url}/quiz/create`, requestBody, { headers: { "Content-Type": "application/json", }, }); - // Récupération des données retournées par l'API - const quiz = transformToQuizModel(response.data); - return quiz; + return response.data; } catch (error: any) { console.error("Error while generating random quiz:", error); -- GitLab From d871734991b0dc36e45ebb45db0f34978f57cf74 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 18 Dec 2024 00:19:56 +0100 Subject: [PATCH 136/283] refactor: changing structure of models --- components/lists/QuizList.tsx | 2 +- screens/Community/Community.tsx | 1 - screens/EndQuiz/EndQuiz.tsx | 4 +-- .../InformationsOfQuiz/InformationsOfQuiz.tsx | 10 +++--- screens/MyQuizzes/MyQuizzes.tsx | 30 ++--------------- screens/PlayingQuiz/PlayingQuiz.tsx | 4 +-- screens/PlayingQuiz/PlayingQuizBody.tsx | 7 ++-- services/QuizService.ts | 33 +------------------ templates/TemplateQuizList.tsx | 2 +- 9 files changed, 15 insertions(+), 78 deletions(-) diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index 45ff335..66743eb 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -1,6 +1,6 @@ import React from "react"; import { FlatList, View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from "react-native"; -import {QuizModel, Quiz} from "../../models/QuizModel"; +import {Quiz} from "../../models/QuizModel"; import { NavigationProp } from "@react-navigation/native"; diff --git a/screens/Community/Community.tsx b/screens/Community/Community.tsx index 772faf2..152b7f3 100644 --- a/screens/Community/Community.tsx +++ b/screens/Community/Community.tsx @@ -2,7 +2,6 @@ import {View, Text} from "react-native"; import TemplateQuizList from "../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; import {useEffect, useState} from "react"; -import {QuizModel} from "../../models/QuizModel"; import {useQuizService} from "../../services/QuizService"; import HttpError from "../../services/HttpError"; import {useInfiniteQuery} from "@tanstack/react-query"; diff --git a/screens/EndQuiz/EndQuiz.tsx b/screens/EndQuiz/EndQuiz.tsx index 678fc88..6f997e3 100644 --- a/screens/EndQuiz/EndQuiz.tsx +++ b/screens/EndQuiz/EndQuiz.tsx @@ -2,11 +2,11 @@ import {NavigationProp, RouteProp} from "@react-navigation/native"; import { View, StyleSheet, Image, Text } from "react-native"; import EndQuizChild from "./EndQuizChild"; import TemplateMenu from "../../templates/TemplateMenu"; -import QuizModel from "../../models/QuizModel"; +import {Quiz} from "../../models/QuizModel"; type RoutePropsType = { EndQuizChild: { - quiz: QuizModel; + quiz: Quiz; }; }; interface Props { diff --git a/screens/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/InformationsOfQuiz/InformationsOfQuiz.tsx index ba5c5d0..faa6e90 100644 --- a/screens/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -2,11 +2,11 @@ import React from "react"; import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; import TemplateMenu from "../../templates/TemplateMenu"; import {NavigationProp, RouteProp} from "@react-navigation/native"; -import QuizModel from "../../models/QuizModel"; +import {Quiz} from "../../models/QuizModel"; type RoutePropsType = { InformationsOfQuiz: { - quiz: QuizModel; + quiz: Quiz; }; }; interface Props { @@ -24,12 +24,12 @@ export default function InformationsOfQuiz({ navigation, route }: Props) { return ( <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> <View style={styles.container}> - <Text style={styles.quizTitle}>{quiz.category}</Text> - <Text style={styles.quizDescription}>{quiz.category}</Text> + <Text style={styles.quizTitle}>{quiz.name}</Text> + <Text style={styles.quizDescription}>{quiz.category?.name}</Text> <View style={styles.aboutContainer}> <Text style={styles.aboutTitle}>About this quiz :</Text> - <Text style={styles.aboutDetails}>{quiz.nbQuestions} questions</Text> + <Text style={styles.aboutDetails}>{quiz.questionCount} questions</Text> <Text style={styles.aboutDetails}>By : Unknown</Text> </View> diff --git a/screens/MyQuizzes/MyQuizzes.tsx b/screens/MyQuizzes/MyQuizzes.tsx index d7916f2..02612ee 100644 --- a/screens/MyQuizzes/MyQuizzes.tsx +++ b/screens/MyQuizzes/MyQuizzes.tsx @@ -1,39 +1,13 @@ -import {View, Text} from "react-native"; import TemplateQuizList from "../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; import {useState} from "react"; -import QuizModel from "../../models/QuizModel"; +import {Quiz} from "../../models/QuizModel"; interface Props { navigation: NavigationProp<any> } export default function MyQuizzes({navigation}: Props) { - const [quizList, setQuizList] = useState<QuizModel[] | null>([ - { - code: "QUIZ123", - category: "General Knowledge", - questions: [], - nbQuestions: 3, - nbActualQuestion: 1, - score: 0, - }, - { - code: "QUIZ456", - category: "Science", - questions: [], - nbQuestions: 5, - nbActualQuestion: 1, - score: 0, - }, - { - code: "QUIZ789", - category: "Entertainment", - questions: [], - nbQuestions: 10, - nbActualQuestion: 1, - score: 0, - }, - ]) + const [quizList, setQuizList] = useState<Quiz[] | null>([]); const [input, setInput] = useState<string>("") diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 7f413d9..5749ba1 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -1,11 +1,9 @@ import PlayingQuizBody from "./PlayingQuizBody"; import PlayingQuizHeader from "./PlayingQuizHeader"; -import {useEffect, useState} from "react"; +import {useState} from "react"; import TemplateDuo from "../../templates/TemplateDuo"; -import QuestionModel from "../../models/QuestionModel"; import {Quiz} from "../../models/QuizModel"; import {NavigationProp, RouteProp} from "@react-navigation/native"; -import ConfirmModal from "../../components/PlayingQuiz/ComfirmModal"; const optionsTheme = [ "General Knowledge", diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index fa256d4..b65ac63 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -1,12 +1,9 @@ import { View, StyleSheet, Text } from "react-native"; import DefaultButton from "../../components/DefaultButton"; import { ButtonsStyles } from "../../styles/ButtonsStyles"; -import { TextsStyles } from "../../styles/TextsStyles"; -import {Question, Quiz} from "../../models/QuizModel"; +import {Answer, Question, Quiz} from "../../models/QuizModel"; import React, { useState, useEffect, SetStateAction, Dispatch } from "react"; import { useQuizService } from "../../services/QuizService"; -import QuestionModel from "../../models/QuestionModel"; -import AnswerModel from "../../models/AnswerModel"; import {NavigationProp} from "@react-navigation/native"; import HttpError from "../../services/HttpError"; import BlueButton from "../../components/PlayQuiz/BlueButton"; @@ -80,7 +77,7 @@ export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIs setQuizState(QuizState.ANSWERING) }, [quiz.questionIndex]); - const getCorrectAnswer = (answers: AnswerModel[]): AnswerModel => { + const getCorrectAnswer = (answers: Answer[]): Answer => { const correctAnswer = answers.find((answer) => answer.isCorrect); if (!correctAnswer) throw new Error("No correct answer found"); return correctAnswer; diff --git a/services/QuizService.ts b/services/QuizService.ts index 10405c1..37e1110 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -1,10 +1,6 @@ -import {EDifficulty} from "../models/EDifficulty"; -import {transformToAnswerModel, transformToQuizModel} from "../helper/QuizHelper"; -import AnswerModel from "../models/AnswerModel"; import HttpError from "./HttpError"; -import {Answer, Quiz, QuizModel} from "../models/QuizModel"; +import {Answer, Quiz} from "../models/QuizModel"; import axios from "axios"; -import QuestionModel from "../models/QuestionModel"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API @@ -123,32 +119,6 @@ export const useQuizService = () => { } }; - const getAllQuizzes = async (): Promise<QuizModel[] | HttpError> => { - try { - // Effectuer une requête GET pour récupérer les quizzes - const response = await axios.get(`${url}/quiz/all`, { - withCredentials: true, // Inclut les cookies pour une session authentifiée - }); - - // Transformation des données de chaque quiz en modèle QuizModel - const quizzes: QuizModel[] = response.data.map((quiz: any) => transformToQuizModel(quiz)); - - return quizzes; - } catch (error: any) { - console.error("Error while fetching all quizzes:", error); - - if (error.response) { - return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); - } else { - return new HttpError(500, "Unexpected error: " + error.message); - } - } - }; - - - - - const getCommunityQuiz = async (skip: number, take: number) => { try { const response = await axios.get<Quiz[]>(`${url}/quiz/paginated?skip=${skip}&take=${take}`, { @@ -173,7 +143,6 @@ export const useQuizService = () => { getAnswerQuiz: getAnswerQuiz, remainingQuiz: remainingQuiz, getRandomQuiz: getRandomQuiz, - getAllQuizzes: getAllQuizzes, getCommunityQuiz: getCommunityQuiz } } \ No newline at end of file diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index 1ec5fec..a96db4c 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -4,7 +4,7 @@ import TemplateMenu from "./TemplateMenu"; import {NavigationProp} from "@react-navigation/native"; import InputPlayQuiz from "../components/PlayQuiz/InputPlayQuiz"; import QuizList from "../components/lists/QuizList"; -import {QuizModel,Quiz} from "../models/QuizModel"; +import {Quiz} from "../models/QuizModel"; interface Props { navigation: NavigationProp<any> -- GitLab From 74b67abed3a93c3b7e02b963dd8fb5bc6969c3c8 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 18 Dec 2024 00:25:22 +0100 Subject: [PATCH 137/283] refactor: rename models --- components/PlayQuiz/AboutAQuiz.tsx | 2 +- components/lists/QuizList.tsx | 2 +- helper/QuizHelper.ts | 2 +- models/Answer.ts | 6 +++ models/AnswerModel.ts | 6 --- models/Question.ts | 10 ++++ models/QuestionModel.ts | 10 ---- models/Quiz.ts | 26 ++++++++++ models/QuizModel.ts | 50 ------------------- screens/EndQuiz/EndQuiz.tsx | 2 +- screens/EndQuiz/EndQuizChild.tsx | 2 +- .../InformationsOfQuiz/InformationsOfQuiz.tsx | 2 +- screens/MyQuizzes/MyQuizzes.tsx | 2 +- screens/PlayQuiz/PlayQuizJoinQuiz.tsx | 2 +- screens/PlayingQuiz/PlayingQuiz.tsx | 2 +- screens/PlayingQuiz/PlayingQuizBody.tsx | 4 +- screens/PlayingQuiz/PlayingQuizHeader.tsx | 2 +- services/QuizService.ts | 2 +- templates/TemplateQuizList.tsx | 2 +- 19 files changed, 57 insertions(+), 79 deletions(-) create mode 100644 models/Answer.ts delete mode 100644 models/AnswerModel.ts create mode 100644 models/Question.ts delete mode 100644 models/QuestionModel.ts create mode 100644 models/Quiz.ts delete mode 100644 models/QuizModel.ts diff --git a/components/PlayQuiz/AboutAQuiz.tsx b/components/PlayQuiz/AboutAQuiz.tsx index e396031..94ae96e 100644 --- a/components/PlayQuiz/AboutAQuiz.tsx +++ b/components/PlayQuiz/AboutAQuiz.tsx @@ -1,6 +1,6 @@ import React from "react"; import { View, Text, StyleSheet } from "react-native"; -import {Quiz} from "../../models/QuizModel"; +import {Quiz} from "../../models/Quiz"; interface Props { quiz?: Quiz; diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index 66743eb..5f5502b 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -1,6 +1,6 @@ import React from "react"; import { FlatList, View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from "react-native"; -import {Quiz} from "../../models/QuizModel"; +import {Quiz} from "../../models/Quiz"; import { NavigationProp } from "@react-navigation/native"; diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index e201c6f..9285f26 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -1,6 +1,6 @@ // Fonction utilitaire pour mélanger les réponses -import {QuizModel} from "../models/QuizModel"; +import {QuizModel} from "../models/Quiz"; import QuestionModel from "../models/QuestionModel"; import AnswerModel from "../models/AnswerModel"; diff --git a/models/Answer.ts b/models/Answer.ts new file mode 100644 index 0000000..9bc373e --- /dev/null +++ b/models/Answer.ts @@ -0,0 +1,6 @@ +export interface Answer { + id: number; + text: string; + isCorrect?: boolean; + questionId: number; +} \ No newline at end of file diff --git a/models/AnswerModel.ts b/models/AnswerModel.ts deleted file mode 100644 index 4e66a15..0000000 --- a/models/AnswerModel.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface AnswerModel { - id: number; - text: string; - isCorrect?: boolean; - questionId?: number; -} \ No newline at end of file diff --git a/models/Question.ts b/models/Question.ts new file mode 100644 index 0000000..a474b8a --- /dev/null +++ b/models/Question.ts @@ -0,0 +1,10 @@ +import {Answer} from "./Answer"; + +export interface Question { + id: number; + text: string; + categoryId: number; + quizId: string; + order: number; + answers: Answer[]; +} \ No newline at end of file diff --git a/models/QuestionModel.ts b/models/QuestionModel.ts deleted file mode 100644 index a823f5b..0000000 --- a/models/QuestionModel.ts +++ /dev/null @@ -1,10 +0,0 @@ -import AnswerModel from "./AnswerModel"; - -export default interface QuestionModel { - id: number; - question: string; - category?: string; - answers: AnswerModel[]; - multiple: boolean; - nbOfTheQuestion: number; -} \ No newline at end of file diff --git a/models/Quiz.ts b/models/Quiz.ts new file mode 100644 index 0000000..d02f93a --- /dev/null +++ b/models/Quiz.ts @@ -0,0 +1,26 @@ +import {Question} from "./Question"; + +interface Difficulty { + id: number; + name: string; +} + +interface Category { + id: number; + name: string; +} + +export interface Quiz { + id: string; + name: string; + description: string; + score: number; + questionIndex: number; + questionCount: number; + categoryId: number; + difficultyId: number; + authorId: number, + category?: Category, + difficulty: Difficulty, + questions: Question[], +} \ No newline at end of file diff --git a/models/QuizModel.ts b/models/QuizModel.ts deleted file mode 100644 index 38d6cda..0000000 --- a/models/QuizModel.ts +++ /dev/null @@ -1,50 +0,0 @@ -import QuestionModel from "./QuestionModel"; - -export interface QuizModel { - code: string; - category?: string; - questions: QuestionModel[]; - nbQuestions: number; - nbActualQuestion: number; - score: number; -} - -interface Difficulty { - id: number; - name: string; -} - -interface Category { - id: number; - name: string; -} - -export interface Answer { - id: number; - text: string; - isCorrect?: boolean; - questionId: number; -} - -export interface Question { - id: number; - text: string; - categoryId: number; - quizId: string; - order: number; - answers: Answer[]; -} -export interface Quiz { - id: string; - name: string; - description: string; - score: number; - questionIndex: number; - questionCount: number; - categoryId: number; - difficultyId: number; - authorId: number, - category?: Category, - difficulty: Difficulty, - questions: Question[], -} \ No newline at end of file diff --git a/screens/EndQuiz/EndQuiz.tsx b/screens/EndQuiz/EndQuiz.tsx index 6f997e3..ee5c5c5 100644 --- a/screens/EndQuiz/EndQuiz.tsx +++ b/screens/EndQuiz/EndQuiz.tsx @@ -2,7 +2,7 @@ import {NavigationProp, RouteProp} from "@react-navigation/native"; import { View, StyleSheet, Image, Text } from "react-native"; import EndQuizChild from "./EndQuizChild"; import TemplateMenu from "../../templates/TemplateMenu"; -import {Quiz} from "../../models/QuizModel"; +import {Quiz} from "../../models/Quiz"; type RoutePropsType = { EndQuizChild: { diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/EndQuiz/EndQuizChild.tsx index 39eb6ad..a1450a0 100644 --- a/screens/EndQuiz/EndQuizChild.tsx +++ b/screens/EndQuiz/EndQuizChild.tsx @@ -1,7 +1,7 @@ import { View, Image, Text, StyleSheet } from "react-native"; import DefaultButton from "../../components/DefaultButton"; import { NavigationProp, RouteProp } from "@react-navigation/native"; -import {Quiz} from "../../models/QuizModel"; +import {Quiz} from "../../models/Quiz"; diff --git a/screens/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/InformationsOfQuiz/InformationsOfQuiz.tsx index faa6e90..442776a 100644 --- a/screens/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -2,7 +2,7 @@ import React from "react"; import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; import TemplateMenu from "../../templates/TemplateMenu"; import {NavigationProp, RouteProp} from "@react-navigation/native"; -import {Quiz} from "../../models/QuizModel"; +import {Quiz} from "../../models/Quiz"; type RoutePropsType = { InformationsOfQuiz: { diff --git a/screens/MyQuizzes/MyQuizzes.tsx b/screens/MyQuizzes/MyQuizzes.tsx index 02612ee..eb61204 100644 --- a/screens/MyQuizzes/MyQuizzes.tsx +++ b/screens/MyQuizzes/MyQuizzes.tsx @@ -1,7 +1,7 @@ import TemplateQuizList from "../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; import {useState} from "react"; -import {Quiz} from "../../models/QuizModel"; +import {Quiz} from "../../models/Quiz"; interface Props { navigation: NavigationProp<any> diff --git a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx index eaa763f..4493ce4 100644 --- a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx +++ b/screens/PlayQuiz/PlayQuizJoinQuiz.tsx @@ -3,7 +3,7 @@ import AboutAQuiz from "../../components/PlayQuiz/AboutAQuiz"; import BlueButton from "../../components/PlayQuiz/BlueButton"; import React, { useEffect } from "react"; import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; -import {Quiz} from "../../models/QuizModel"; +import {Quiz} from "../../models/Quiz"; import { useQuizService } from "../../services/QuizService"; import HttpError from "../../services/HttpError"; import {getIdOfCategory} from "../../helper/QuizHelper"; diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 5749ba1..bcfe263 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -2,7 +2,7 @@ import PlayingQuizBody from "./PlayingQuizBody"; import PlayingQuizHeader from "./PlayingQuizHeader"; import {useState} from "react"; import TemplateDuo from "../../templates/TemplateDuo"; -import {Quiz} from "../../models/QuizModel"; +import {Quiz} from "../../models/Quiz"; import {NavigationProp, RouteProp} from "@react-navigation/native"; const optionsTheme = [ diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index b65ac63..4d3a8d9 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -1,12 +1,14 @@ import { View, StyleSheet, Text } from "react-native"; import DefaultButton from "../../components/DefaultButton"; import { ButtonsStyles } from "../../styles/ButtonsStyles"; -import {Answer, Question, Quiz} from "../../models/QuizModel"; import React, { useState, useEffect, SetStateAction, Dispatch } from "react"; import { useQuizService } from "../../services/QuizService"; import {NavigationProp} from "@react-navigation/native"; import HttpError from "../../services/HttpError"; import BlueButton from "../../components/PlayQuiz/BlueButton"; +import {Question} from "../../models/Question"; +import {Answer} from "../../models/Answer"; +import {Quiz} from "../../models/Quiz"; interface Props { quiz: Quiz; diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index 581c895..e2c5634 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -1,6 +1,6 @@ import { View, StyleSheet, Text } from "react-native"; import { TextsStyles } from "../../styles/TextsStyles"; -import {Quiz} from "../../models/QuizModel"; +import {Quiz} from "../../models/Quiz"; import {useTranslation} from "react-i18next"; import DefaultButton from "../../components/DefaultButton"; import GoHomeButton from "../../components/PlayingQuiz/GoHomeButton"; diff --git a/services/QuizService.ts b/services/QuizService.ts index 37e1110..11c81f8 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -1,5 +1,5 @@ import HttpError from "./HttpError"; -import {Answer, Quiz} from "../models/QuizModel"; +import {Answer, Quiz} from "../models/Quiz"; import axios from "axios"; export const useQuizService = () => { diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index a96db4c..61f2d8f 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -4,7 +4,7 @@ import TemplateMenu from "./TemplateMenu"; import {NavigationProp} from "@react-navigation/native"; import InputPlayQuiz from "../components/PlayQuiz/InputPlayQuiz"; import QuizList from "../components/lists/QuizList"; -import {Quiz} from "../models/QuizModel"; +import {Quiz} from "../models/Quiz"; interface Props { navigation: NavigationProp<any> -- GitLab From 2fea68ad9a265591921d0b6d6e6b542ab88b4114 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 18 Dec 2024 17:15:37 +0100 Subject: [PATCH 138/283] fix: answering question --- screens/PlayingQuiz/PlayingQuizBody.tsx | 1 + services/QuizService.ts | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 4d3a8d9..ad504db 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -85,6 +85,7 @@ export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIs return correctAnswer; }; const onValidation = async () => { + if(selectedAnswerId === null) return; setQuizState(QuizState.LOADING); const answerId = currentQuestion.answers.find((answer) => answer.id === selectedAnswerId)?.id; if(!answerId) return; diff --git a/services/QuizService.ts b/services/QuizService.ts index 11c81f8..34beecd 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -1,6 +1,7 @@ import HttpError from "./HttpError"; -import {Answer, Quiz} from "../models/Quiz"; +import {Quiz} from "../models/Quiz"; import axios from "axios"; +import {Answer} from "../models/Answer"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API @@ -73,13 +74,20 @@ export const useQuizService = () => { answerID: answerID, }; - const response = await axios.post<Answer[]>(`${url}/quiz/answer`, requestBody, { + const response = await axios.post(`${url}/quiz/answer`, requestBody, { headers: { "Content-Type": "application/json", }, }); - return response.data; + const answers: Answer[] = response.data.answers.map((answer: any) => ({ + id: answer.id, + text: answer.text, + isCorrect: answer.isCorrect, + questionId: answer.questionId, + })); + + return answers; } catch (error: any) { console.error("Error while fetching answers:", error); -- GitLab From 5f4012e3c963535c3860342a9d2ade6395ca4b23 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 18 Dec 2024 22:58:59 +0100 Subject: [PATCH 139/283] feat: restart quiz in end quiz screen --- screens/EndQuiz/EndQuizChild.tsx | 26 +++++++++++++++++++++----- services/QuizService.ts | 26 ++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/EndQuiz/EndQuizChild.tsx index a1450a0..9d31713 100644 --- a/screens/EndQuiz/EndQuizChild.tsx +++ b/screens/EndQuiz/EndQuizChild.tsx @@ -2,6 +2,8 @@ import { View, Image, Text, StyleSheet } from "react-native"; import DefaultButton from "../../components/DefaultButton"; import { NavigationProp, RouteProp } from "@react-navigation/native"; import {Quiz} from "../../models/Quiz"; +import {useQuizService} from "../../services/QuizService"; +import HttpError from "../../services/HttpError"; @@ -12,15 +14,29 @@ interface Props { export default function EndQuizChild({ navigation, quiz }: Props) { - const handleRestartPress = () => { - quiz.questionIndex = 1; - quiz.score = 0; + const {restartQuiz, remainingQuiz} = useQuizService() + + const handleRestartPress = async () => { + const isRestarted = await restartQuiz(quiz.id); + if(HttpError.isHttpError(isRestarted)){ + console.log(isRestarted.message); + return + } + if(!isRestarted) { + return; + } + const quizRestarted = await remainingQuiz(quiz.id); + if(HttpError.isHttpError(quizRestarted)){ + console.log(quizRestarted.message); + return + } + navigation.reset({ index: 0, routes: [ { name: "PlayingQuiz", - params: {quiz: quiz}, + params: {quizRecovered: quizRestarted}, }, ] }); @@ -39,7 +55,7 @@ export default function EndQuizChild({ navigation, quiz }: Props) { <Text style={styles.TextBodyTitle}>{((quiz.score/quiz.questionCount)*100 > 50 ) ? 'Victory' : 'Defeat'}</Text> <Text style={styles.TextBody}>you finish the test with a score of {`${quiz.score}/${quiz.questionCount}`} ! </Text> <Image source={require('../../assets/uWin.png')} /> - {/*<DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton>*/} + <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton> <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu} buttonStyle={styles.blueButton} buttonText={styles.blueButtonText}></DefaultButton> </View> </View> diff --git a/services/QuizService.ts b/services/QuizService.ts index 11c81f8..f7aa4a2 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -1,6 +1,7 @@ import HttpError from "./HttpError"; -import {Answer, Quiz} from "../models/Quiz"; +import {Quiz} from "../models/Quiz"; import axios from "axios"; +import {Answer} from "../models/Answer"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API @@ -137,12 +138,33 @@ export const useQuizService = () => { } }; + const restartQuiz = async (id: string) : Promise<boolean | HttpError> => { + try { + const requestBody = { + id + }; + + await axios.patch(`${url}/quiz/restart`, requestBody, { + withCredentials: true, + }); + + return true; + } catch (error: any) { + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + } + return { generateQuiz: generateQuiz, getAnswerQuiz: getAnswerQuiz, remainingQuiz: remainingQuiz, getRandomQuiz: getRandomQuiz, - getCommunityQuiz: getCommunityQuiz + getCommunityQuiz: getCommunityQuiz, + restartQuiz: restartQuiz } } \ No newline at end of file -- GitLab From c1ffdee65390f9e8af49c6a677c07dd0a4abd69a Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 19 Dec 2024 14:51:29 +0100 Subject: [PATCH 140/283] feat: restart quiz in end quiz screen --- helper/QuizHelper.ts | 103 +++++++--------------------------------- services/QuizService.ts | 7 +-- 2 files changed, 22 insertions(+), 88 deletions(-) diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index 9285f26..e2ee3aa 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -1,88 +1,21 @@ - -// Fonction utilitaire pour mélanger les réponses -import {QuizModel} from "../models/Quiz"; -import QuestionModel from "../models/QuestionModel"; -import AnswerModel from "../models/AnswerModel"; - -const optionsTheme = [ - "General Knowledge", - "Entertainment: Books", - "Entertainment: Film", - "Entertainment: Music", - "Entertainment: Musicals & Theatres", - "Entertainment: Television", - "Entertainment: Video Games", - "Entertainment: Board Games", - "Science & Nature", - "Science: Computers", - "Science: Mathematics", - "Mythology", - "Sports", - "Geography"] - -const shuffleAnswers = (answers: string[]): string[] => { - for (let i = answers.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [answers[i], answers[j]] = [answers[j], answers[i]]; - } - return answers; -}; - -// Cette méthode est utilisée quand on récupère les réponses d'une question -export const transformToAnswerModel = (data: any): AnswerModel[] => { - const answers: AnswerModel[] = data.answers.map((answer: any) => { - return { - id: answer.id, - text: answer.text, - isCorrect: answer.isCorrect, - questionId: answer.questionId, - }; - }); - return answers; -}; - -export const transformToQuizModel = (data: any): QuizModel => { - // Transformation des questions - const questions: QuestionModel[] = data.questions.map((question: any) => { - // Transformation des réponses pour cette question - const answers: AnswerModel[] = question.answers.map((answer: any) => { - return { - id: answer.id, - text: answer.text, - }; - }); - - return { - id: question.id, - question: question.text, // Texte de la question - category: "aa", - // category: question.category.name ? question.category.name : "Community Quizz", // Extraction du nom de la catégorie - answers: answers, // Réponses associées - multiple: question.type === "multiple", // Détermine si c'est une question multiple - nbOfTheQuestion: question.order, // Numéro de la question - }; - }); - - // Création de l'objet QuizModel - const quiz: QuizModel = { - code: data.id, // Mapping de codeQuiz vers code - category: "aa", - // category: data.category.name ? data.category.name : "Community Quizz", // Extraction du nom de la catégorie - questions: questions, // Questions transformées - nbQuestions: data.questionCount, // Nombre total de questions - nbActualQuestion: data.questionIndex, // Index actuel de la question - score: data.score, // Score actuel +import {Quiz} from "../models/Quiz"; +export const shuffleAnswersOfQuiz = (quiz: Quiz): Quiz => { + // Clone le quiz pour éviter de modifier l'original + const shuffledQuiz: Quiz = { + ...quiz, + questions: quiz.questions.map((question) => ({ + ...question, + answers: [...question.answers], // Clone les réponses pour éviter de modifier l'original + })), }; - return quiz; -}; - - -export const getIdOfCategory = (category: string) => { - for (let i = 0; i < optionsTheme.length; i++) { - if (optionsTheme[i] === category) { - return i; + // Mélange les réponses de chaque question + shuffledQuiz.questions.forEach((question) => { + for (let i = question.answers.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [question.answers[i], question.answers[j]] = [question.answers[j], question.answers[i]]; } - } - return 0; -} + }); + + return shuffledQuiz; +}; \ No newline at end of file diff --git a/services/QuizService.ts b/services/QuizService.ts index 85573fd..17a2475 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -2,6 +2,7 @@ import HttpError from "./HttpError"; import {Quiz} from "../models/Quiz"; import axios from "axios"; import {Answer} from "../models/Answer"; +import {shuffleAnswersOfQuiz} from "../helper/QuizHelper"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API @@ -21,7 +22,7 @@ export const useQuizService = () => { }); // Récupération des données retournées par l'API - return response.data; + return shuffleAnswersOfQuiz(response.data); } catch (error: any) { console.error("Error while generating quiz:", error); @@ -48,7 +49,7 @@ export const useQuizService = () => { }); // Récupération des données retournées par l'API - return response.data; + return shuffleAnswersOfQuiz(response.data); } catch (error: any) { console.error("Error while remaining quiz:", error); @@ -113,7 +114,7 @@ export const useQuizService = () => { }, }); - return response.data; + return shuffleAnswersOfQuiz(response.data); } catch (error: any) { console.error("Error while generating random quiz:", error); -- GitLab From 63dbab457c318f23df5311f2bc4d78e49bb6cdb3 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 27 Dec 2024 19:12:29 +0100 Subject: [PATCH 141/283] feat: add multiplayer to tab nav --- routes/TabNavigation.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/routes/TabNavigation.tsx b/routes/TabNavigation.tsx index 834245c..65ddcdc 100644 --- a/routes/TabNavigation.tsx +++ b/routes/TabNavigation.tsx @@ -4,6 +4,7 @@ import Home from "../screens/Home/Home"; import Icon from "react-native-vector-icons/MaterialIcons"; import Profil from "../screens/Profil/Profil"; import Community from "../screens/Community/Community"; +import Multiplayer from "../screens/Multiplayer/Multiplayer"; const Tab = createBottomTabNavigator(); @@ -36,6 +37,9 @@ export default function TabNavigator() { } else if (route.name === "Community") { iconName = "people"; } + else if (route.name === "Multiplayer") { + iconName = "diversity-2"; + } return <Icon name={iconName} size={64} color={color} />; }, })} @@ -48,7 +52,10 @@ export default function TabNavigator() { name="Community" component={Community} /> - + <Tab.Screen + name="Multiplayer" + component={Multiplayer} + /> <Tab.Screen name="Profil" component={Profil} -- GitLab From b6e0b2b26f1edf083b9a8fd10c3aeb521bbedd68 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 27 Dec 2024 19:14:27 +0100 Subject: [PATCH 142/283] fix: error with name property of icon --- routes/TabNavigation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/TabNavigation.tsx b/routes/TabNavigation.tsx index 65ddcdc..332a6cb 100644 --- a/routes/TabNavigation.tsx +++ b/routes/TabNavigation.tsx @@ -40,7 +40,7 @@ export default function TabNavigator() { else if (route.name === "Multiplayer") { iconName = "diversity-2"; } - return <Icon name={iconName} size={64} color={color} />; + return <Icon name={iconName ?? ''} size={64} color={color} />; }, })} > -- GitLab From 5aa2121f54193a2615379ddab54e364249432c23 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 27 Dec 2024 19:24:42 +0100 Subject: [PATCH 143/283] feat: add multiplayer to stack nav --- routes/StackNavigator.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index cbfdc43..3c3c037 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -8,6 +8,7 @@ import TabNavigator from "./TabNavigation"; import PlayQuiz from "../screens/PlayQuiz/PlayQuiz"; import MyQuizzes from "../screens/MyQuizzes/MyQuizzes"; import InformationsOfQuiz from "../screens/InformationsOfQuiz/InformationsOfQuiz"; +import Multiplayer from "../screens/Multiplayer/Multiplayer"; const Stack = createNativeStackNavigator(); @@ -23,6 +24,8 @@ export default function StackNavigator() { <Stack.Screen name="EndQuiz" component={EndQuiz} options={{ headerShown: false }}/> <Stack.Screen name="MyQuizzes" component={MyQuizzes} options={{ headerShown: false }}/> <Stack.Screen name="InformationsOfQuiz" component={InformationsOfQuiz} options={{ headerShown: false }}/> + + <Stack.Screen name="Multiplayer" component={Multiplayer} options={{ headerShown: false }}/> </Stack.Navigator> </NavigationContainer> ); -- GitLab From 6fa822cb6514a4f56cfc7a3b96265479343658a5 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 27 Dec 2024 19:41:59 +0100 Subject: [PATCH 144/283] feat: multiplayer screen --- screens/Multiplayer/Multiplayer.tsx | 29 +++++++++ screens/Multiplayer/MultiplayerChild.tsx | 79 ++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 screens/Multiplayer/Multiplayer.tsx create mode 100644 screens/Multiplayer/MultiplayerChild.tsx diff --git a/screens/Multiplayer/Multiplayer.tsx b/screens/Multiplayer/Multiplayer.tsx new file mode 100644 index 0000000..4b40687 --- /dev/null +++ b/screens/Multiplayer/Multiplayer.tsx @@ -0,0 +1,29 @@ +import { NavigationProp } from "@react-navigation/native"; +import { View, StyleSheet } from "react-native"; +import TemplateMenu from "../../templates/TemplateMenu"; +import MultiplayerChild from "./MultiplayerChild"; + +interface Props { + navigation: NavigationProp<any>; +} + +export default function Home({navigation}: Props) { + return ( + <View style={styles.containerGlobal}> + <TemplateMenu navigation={navigation} headerNavigation={false} buttonGoBack={false}> + <View> + <MultiplayerChild navigation={navigation}/> + </View> + </TemplateMenu> + </View> + ); +} + +const styles = StyleSheet.create({ + containerGlobal: { + display: 'flex', + width: '100%', + height: '100%', + flexDirection: 'column', + }, +}); \ No newline at end of file diff --git a/screens/Multiplayer/MultiplayerChild.tsx b/screens/Multiplayer/MultiplayerChild.tsx new file mode 100644 index 0000000..be9843a --- /dev/null +++ b/screens/Multiplayer/MultiplayerChild.tsx @@ -0,0 +1,79 @@ +import { View, StyleSheet, Image, Modal, TextInput } from "react-native"; +import DefaultButton from "../../components/DefaultButton"; +import {useTranslation} from "react-i18next"; +import { useState } from "react"; +import { TextsStyles } from "../../styles/TextsStyles"; +import ModalJoinQuiz from "../../components/ModalJoinQuiz"; +import MenuButton from "../../components/buttons/MenuButton"; +import {NavigationProp} from "@react-navigation/native"; +import {useQuizService} from "../../services/QuizService"; +import HttpError from "../../services/HttpError"; + +interface Props { + navigation: NavigationProp<any>; +} + +/** + * HomeChild : Enfant de l'écran Home, il contient le menu principal + * @param navigation - navigation + */ +export default function MultiplayerChild({navigation}: Props) { + const [modalVisible, setModalVisible] = useState(false); + const {t} = useTranslation(); + + const handleButtonCommunityPressed = async () => { + navigation.navigate("MultiplayerCommunity"); + } + + const handleOnlineQuizPressed = () => { + navigation.navigate("OnlineQuiz"); + } + + const handleButtonOngoingQuizzesPressed = () => { + navigation.navigate("OngoingQuizzes"); + } + + return ( + <View style={styles.containerGlobal}> + <View style={styles.imageContainer}> + <Image source={require('../../assets/TitleApp.png')} style={styles.image} /> + </View> + <View style={styles.buttonContainer}> + <MenuButton text={"COMMUNITY"} handleButtonPressed={handleButtonCommunityPressed}/> + <MenuButton text={"PLAY AN ONLINE QUIZ"} handleButtonPressed={handleOnlineQuizPressed}/> + <MenuButton text={"ONGOING QUIZZES"} handleButtonPressed={handleButtonOngoingQuizzesPressed}/> + </View> + <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> + </View> + ); +} + +const styles = StyleSheet.create({ + containerGlobal: { + display: 'flex', + width: '100%', + height: '100%', + justifyContent: 'flex-start', + alignItems: 'center', + }, + image: { + }, + button: { + borderRadius: 40, + height: '35%', + marginTop: '5%' + }, + imageContainer: { + display: 'flex', + alignItems: 'center', + marginTop: '15%', + marginBottom: '25%', + }, + buttonContainer: { + width: '100%', + display: 'flex', + flexDirection: 'column', + paddingLeft: '8%', + gap: '9%', + } +}); \ No newline at end of file -- GitLab From 65560ce941027d62c949afd46baf3d4d461a31e6 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 27 Dec 2024 20:05:48 +0100 Subject: [PATCH 145/283] feat: add multiplayer community to stack nav --- routes/StackNavigator.tsx | 7 +++++++ .../MultiplayerCommunity.tsx | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 screens/MultiplayerCommunity/MultiplayerCommunity.tsx diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index 3c3c037..bb4c2a2 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -9,6 +9,9 @@ import PlayQuiz from "../screens/PlayQuiz/PlayQuiz"; import MyQuizzes from "../screens/MyQuizzes/MyQuizzes"; import InformationsOfQuiz from "../screens/InformationsOfQuiz/InformationsOfQuiz"; import Multiplayer from "../screens/Multiplayer/Multiplayer"; +import MultiplayerCommunity from "../screens/MultiplayerCommunity/MultiplayerCommunity"; +import OnlineQuiz from "../screens/OnlineQuiz/OnlineQuiz"; +import OngoingQuizzes from "../screens/OngoingQuizzes/OngoingQuizzes"; const Stack = createNativeStackNavigator(); @@ -26,6 +29,10 @@ export default function StackNavigator() { <Stack.Screen name="InformationsOfQuiz" component={InformationsOfQuiz} options={{ headerShown: false }}/> <Stack.Screen name="Multiplayer" component={Multiplayer} options={{ headerShown: false }}/> + <Stack.Screen name="MultiplayerCommunity" component={MultiplayerCommunity} options={{ headerShown: false }}/> + <Stack.Screen name="OnlineQuiz" component={OnlineQuiz} options={{ headerShown: false }}/> + <Stack.Screen name="OngoingQuizzes" component={OngoingQuizzes} options={{ headerShown: false }}/> + </Stack.Navigator> </NavigationContainer> ); diff --git a/screens/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/MultiplayerCommunity/MultiplayerCommunity.tsx new file mode 100644 index 0000000..21d8772 --- /dev/null +++ b/screens/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -0,0 +1,17 @@ +import { View, Text, StyleSheet } from "react-native"; + +export default function MultiplayerCommunity() { + return ( + <View style={styles.container}> + <Text>MultiplayerCommunity</Text> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + } +}); \ No newline at end of file -- GitLab From 0b49513a7491ef35d922c269d1105a32fb442509 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 27 Dec 2024 20:06:19 +0100 Subject: [PATCH 146/283] feat: add onlinequiz to staack nav --- routes/StackNavigator.tsx | 1 - screens/OnlineQuiz/OnlineQuiz.tsx | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 screens/OnlineQuiz/OnlineQuiz.tsx diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index bb4c2a2..6531506 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -31,7 +31,6 @@ export default function StackNavigator() { <Stack.Screen name="Multiplayer" component={Multiplayer} options={{ headerShown: false }}/> <Stack.Screen name="MultiplayerCommunity" component={MultiplayerCommunity} options={{ headerShown: false }}/> <Stack.Screen name="OnlineQuiz" component={OnlineQuiz} options={{ headerShown: false }}/> - <Stack.Screen name="OngoingQuizzes" component={OngoingQuizzes} options={{ headerShown: false }}/> </Stack.Navigator> </NavigationContainer> diff --git a/screens/OnlineQuiz/OnlineQuiz.tsx b/screens/OnlineQuiz/OnlineQuiz.tsx new file mode 100644 index 0000000..cd2dab7 --- /dev/null +++ b/screens/OnlineQuiz/OnlineQuiz.tsx @@ -0,0 +1,17 @@ +import { View, Text, StyleSheet } from "react-native"; + +export default function OnlineQuiz() { + return ( + <View style={styles.container}> + <Text>OnlineQuiz</Text> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + } +}); \ No newline at end of file -- GitLab From 3fabb386c3f8cde1ea818d2cfa0d6c9a3ecfe39b Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 27 Dec 2024 20:06:38 +0100 Subject: [PATCH 147/283] feat: add ongoingquizzes to nav --- routes/StackNavigator.tsx | 1 + screens/OngoingQuizzes/OngoingQuizzes.tsx | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 screens/OngoingQuizzes/OngoingQuizzes.tsx diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index 6531506..bb4c2a2 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -31,6 +31,7 @@ export default function StackNavigator() { <Stack.Screen name="Multiplayer" component={Multiplayer} options={{ headerShown: false }}/> <Stack.Screen name="MultiplayerCommunity" component={MultiplayerCommunity} options={{ headerShown: false }}/> <Stack.Screen name="OnlineQuiz" component={OnlineQuiz} options={{ headerShown: false }}/> + <Stack.Screen name="OngoingQuizzes" component={OngoingQuizzes} options={{ headerShown: false }}/> </Stack.Navigator> </NavigationContainer> diff --git a/screens/OngoingQuizzes/OngoingQuizzes.tsx b/screens/OngoingQuizzes/OngoingQuizzes.tsx new file mode 100644 index 0000000..191ae5f --- /dev/null +++ b/screens/OngoingQuizzes/OngoingQuizzes.tsx @@ -0,0 +1,17 @@ +import { View, StyleSheet, Text } from "react-native"; + +export default function OngoingQuizzes() { + return ( + <View style={styles.container}> + <Text>OngoingQuizzes</Text> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + } +}); \ No newline at end of file -- GitLab From b6007e12aade976a2eec833be83eec671b12ade2 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 27 Dec 2024 21:08:32 +0100 Subject: [PATCH 148/283] feat: modify onlineQuiz to use templateMenu --- screens/OnlineQuiz/OnlineQuiz.tsx | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/screens/OnlineQuiz/OnlineQuiz.tsx b/screens/OnlineQuiz/OnlineQuiz.tsx index cd2dab7..76c45c3 100644 --- a/screens/OnlineQuiz/OnlineQuiz.tsx +++ b/screens/OnlineQuiz/OnlineQuiz.tsx @@ -1,17 +1,28 @@ -import { View, Text, StyleSheet } from "react-native"; +import { NavigationProp } from "@react-navigation/native"; +import { View, StyleSheet } from "react-native"; +import TemplateMenu from "../../templates/TemplateMenu"; +import OnlineQuizChild from "./OnlineQuizChild"; -export default function OnlineQuiz() { +interface Props{ + navigation: NavigationProp<any> +} +export default function OnlineQuiz({navigation}: Props) { return ( - <View style={styles.container}> - <Text>OnlineQuiz</Text> + <View style={styles.containerGlobal}> + <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> + <View> + <OnlineQuizChild navigation={navigation}/> + </View> + </TemplateMenu> </View> - ); + ) } const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - } + containerGlobal: { + display: 'flex', + width: '100%', + height: '100%', + flexDirection: 'column', + }, }); \ No newline at end of file -- GitLab From 8f65af87d283da5a3c7df5613e4e34b3deeb492c Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Fri, 27 Dec 2024 21:25:42 +0100 Subject: [PATCH 149/283] feat: create onlineQuizChild in copy of PlayQuizChild --- screens/OnlineQuiz/OnlineQuizChild.tsx | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 screens/OnlineQuiz/OnlineQuizChild.tsx diff --git a/screens/OnlineQuiz/OnlineQuizChild.tsx b/screens/OnlineQuiz/OnlineQuizChild.tsx new file mode 100644 index 0000000..d131af2 --- /dev/null +++ b/screens/OnlineQuiz/OnlineQuizChild.tsx @@ -0,0 +1,44 @@ +import { NavigationProp } from "@react-navigation/native"; +import { useState } from "react"; +import { SelectModeType } from "../../models/SelectModeType"; +import { TouchableWithoutFeedback, View, StyleSheet, Keyboard } from "react-native"; +import SelectMode from "../../components/PlayQuiz/SelectMode"; +import PlayQuizGenerateQuiz from "../PlayQuiz/PlayQuizGenerateQuiz"; +import PlayQuizJoinQuiz from "../PlayQuiz/PlayQuizJoinQuiz"; + +interface Props { + navigation: NavigationProp<any> +} + +export default function OnlineQuizChild({navigation}: Props){ + const [mode, setMode] = useState<SelectModeType>("play"); + + return ( + <TouchableWithoutFeedback onPress={Keyboard.dismiss}> + <View style={styles.containerGlobal}> + <View style={styles.contentContainer}> + <SelectMode mode={mode} setMode={setMode}/> + {mode === "play" ? ( + <PlayQuizGenerateQuiz navigation={navigation}/> + ) : ( + <PlayQuizJoinQuiz navigation={navigation}/> + )} + </View> + </View> + </TouchableWithoutFeedback> + ) +} +const styles = StyleSheet.create({ + containerGlobal: { + width: '100%', + height: '100%', + alignItems: 'center', + justifyContent: 'center', + }, + contentContainer: { + display: 'flex', + width: '95%', + height: '100%', + flexDirection: 'column', + }, +}); \ No newline at end of file -- GitLab From c00baa1e60a371a21e446088da2742c8dd0588cf Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 28 Dec 2024 18:21:35 +0100 Subject: [PATCH 150/283] feat: add onlineSelectMode --- models/SelectModeType.ts | 3 +- screens/OnlineQuiz/OnlineSelectMode.tsx | 74 +++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 screens/OnlineQuiz/OnlineSelectMode.tsx diff --git a/models/SelectModeType.ts b/models/SelectModeType.ts index 8c155fa..826e0b8 100644 --- a/models/SelectModeType.ts +++ b/models/SelectModeType.ts @@ -1,2 +1,3 @@ export type SelectModeType = "play" | "join"; -export type ProfilSelectModeType = "login" | "signup"; \ No newline at end of file +export type ProfilSelectModeType = "login" | "signup"; +export type OnlineSelectModeType = "create" | "join"; \ No newline at end of file diff --git a/screens/OnlineQuiz/OnlineSelectMode.tsx b/screens/OnlineQuiz/OnlineSelectMode.tsx new file mode 100644 index 0000000..8275f69 --- /dev/null +++ b/screens/OnlineQuiz/OnlineSelectMode.tsx @@ -0,0 +1,74 @@ +import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import { OnlineSelectModeType } from "../../models/SelectModeType"; + +interface Props { + mode: OnlineSelectModeType; + setMode: (mode: OnlineSelectModeType) => void; +} + +export default function OnlineSelectMode({ mode, setMode }: Props) { + return ( + <View style={styles.container}> + <View style={styles.buttonContainer}> + <TouchableOpacity onPress={() => setMode("create")} style={{marginLeft: '10%'}}> + <Text style={[styles.text, mode === "create" && styles.activeText]}>CREATE A LOBBY</Text> + </TouchableOpacity> + <View style={[styles.bar, mode === "create" ? styles.activeBar : styles.inactiveBar]} /> + </View> + + <View style={styles.buttonContainer}> + <TouchableOpacity onPress={() => setMode("join")} style={{marginRight: '10%'}}> + <Text style={[styles.text, mode === "join" && styles.activeText]}>JOIN A QUIZ</Text> + </TouchableOpacity> + <View style={[styles.bar, mode === "join" ? styles.activeBar : styles.inactiveBar]} /> + </View> + + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + display: 'flex', + width: '100%', + height: '15%', + flexDirection: 'row', + backgroundColor: '#f3f3f3', + borderRadius: 10, + justifyContent: 'space-around', + alignItems: 'center', + // Ombre pour Android + elevation: 5, + // Ombre pour iOS + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, // Décalage de l'ombre en bas + shadowOpacity: 0.25, // Opacité de l'ombre + shadowRadius: 3.84, // Flou de l'ombre + }, + buttonContainer: { + display: 'flex', + flexDirection: 'column', + width: '40%', + alignItems: 'center', + gap: "5%", + }, + text: { + fontSize: 25, + color: '#bebebe', + }, + activeText: { + color: '#2b73fe', + }, + bar: { + width: '100%', + height: '4%', + marginTop: '4%', + borderRadius: 10, + }, + activeBar: { + backgroundColor: '#2b73fe', // Couleur pour le bouton actif + }, + inactiveBar: { + backgroundColor: '#bebebe', // Couleur grise pour les boutons inactifs + }, +}); -- GitLab From e1103c2abfa4f9d37993d6be860fb3df96f215d5 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 28 Dec 2024 18:22:05 +0100 Subject: [PATCH 151/283] feat: add select mode and onlineCreateLobby --- screens/OnlineQuiz/OnlineCreateLobby.tsx | 107 +++++++++++++++++++++++ screens/OnlineQuiz/OnlineQuizChild.tsx | 14 +-- 2 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 screens/OnlineQuiz/OnlineCreateLobby.tsx diff --git a/screens/OnlineQuiz/OnlineCreateLobby.tsx b/screens/OnlineQuiz/OnlineCreateLobby.tsx new file mode 100644 index 0000000..95f7a94 --- /dev/null +++ b/screens/OnlineQuiz/OnlineCreateLobby.tsx @@ -0,0 +1,107 @@ +import { Text,View, StyleSheet } from "react-native"; +import SelectListBox from "../../components/PlayQuiz/SelectListBox"; +import React, {useEffect} from "react"; +import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; +import BlueButton from "../../components/PlayQuiz/BlueButton"; +import {getIdOfCategory} from "../../helper/QuizHelper"; +import {useQuizService} from "../../services/QuizService"; +import {NavigationProp} from "@react-navigation/native"; + +const difficulty = [ + { key: "easy", value: "easy" }, + { key: "medium", value: "medium" }, + { key: "hard", value: "hard" }, +]; + +const categories = [ + { key: "9", value: "General Knowledge"}, + { key: "10", value: "Entertainment: Books" }, + { key: "11", value: "Entertainment: Film" }, + { key: "12", value: "Entertainment: Music"}, + { key: "13", value: "Entertainment: Musicals & Theatres" }, + { key: "14", value: "Entertainment: Television" }, + { key: "15", value: "Entertainment: Video Games" }, + { key: "16", value: "Entertainment: Board Games" }, + { key: "17", value: "Science & Nature" }, + { key: "18", value: "Science: Computers" }, + { key: "19", value: "Science: Mathematics" }, + { key: "20", value: "Mythology" }, + { key: "21", value: "Sports" }, + { key: "22", value: "Geography" }, +]; +interface Props { + navigation: NavigationProp<any>; +} + +export default function OnlineCreateLobby({navigation}: Props) { + const [difficutlyChoose, setDifficutlyChoose] = React.useState(""); + const [categoryChoose, setCategoryChoose] = React.useState(""); + const [nbQuestions, setNbQuestions] = React.useState("10"); + const [buttonIsDisabled, setButtonIsDisabled] = React.useState(true); + const {generateQuiz} = useQuizService(); + + useEffect(() => { + if(nbQuestions === "" || difficutlyChoose === "" || categoryChoose === "") + { + setButtonIsDisabled(true); + } + else + { + setButtonIsDisabled(false); + } + + }, [difficutlyChoose,categoryChoose,nbQuestions]); + + const handlePlayButtonPress = async () => { + const quizGenerated = await generateQuiz(parseInt(nbQuestions), getIdOfCategory(categoryChoose), difficutlyChoose); + navigation.reset({ + index: 0, + routes: [ + { + name: "PlayingQuiz", + params: {quizRecovered: quizGenerated}, + }, + ] + }); + }; + + return ( + <View style={styles.container}> + <View style={styles.contentContainer}> + <View style={styles.fieldContainer}> + <SelectListBox title="CHOOSE A DIFFICULTY" data={difficulty} setSelected={setDifficutlyChoose} /> + <SelectListBox title="CHOOSE A CATEGORY" data={categories} setSelected={setCategoryChoose} /> + <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={setNbQuestions} numeric={true} defaultValue={nbQuestions}/> + </View> + <View style={styles.buttonPlayContainer}> + <BlueButton onPress={handlePlayButtonPress} text={"CREATE LOBBY"} isDisabled={buttonIsDisabled}/> + </View> + </View> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + display: "flex", + width: "100%", + height: "75%", + justifyContent: "space-between", + }, + contentContainer: { + flex: 1, // Prend toute la hauteur disponible + flexDirection: "column", + justifyContent: "space-between", + }, + fieldContainer: { + width: "100%", + flexDirection: "column", + justifyContent: "space-between", + marginTop: 20, + gap: 20, + }, + buttonPlayContainer: { + alignItems: "center", + paddingVertical: 20, + }, +}); diff --git a/screens/OnlineQuiz/OnlineQuizChild.tsx b/screens/OnlineQuiz/OnlineQuizChild.tsx index d131af2..f77ad6d 100644 --- a/screens/OnlineQuiz/OnlineQuizChild.tsx +++ b/screens/OnlineQuiz/OnlineQuizChild.tsx @@ -1,25 +1,25 @@ import { NavigationProp } from "@react-navigation/native"; import { useState } from "react"; -import { SelectModeType } from "../../models/SelectModeType"; +import { OnlineSelectModeType, SelectModeType } from "../../models/SelectModeType"; import { TouchableWithoutFeedback, View, StyleSheet, Keyboard } from "react-native"; -import SelectMode from "../../components/PlayQuiz/SelectMode"; -import PlayQuizGenerateQuiz from "../PlayQuiz/PlayQuizGenerateQuiz"; import PlayQuizJoinQuiz from "../PlayQuiz/PlayQuizJoinQuiz"; +import OnlineSelectMode from "./OnlineSelectMode"; +import OnlineCreateLobby from "./OnlineCreateLobby"; interface Props { navigation: NavigationProp<any> } export default function OnlineQuizChild({navigation}: Props){ - const [mode, setMode] = useState<SelectModeType>("play"); + const [mode, setMode] = useState<OnlineSelectModeType>("create"); return ( <TouchableWithoutFeedback onPress={Keyboard.dismiss}> <View style={styles.containerGlobal}> <View style={styles.contentContainer}> - <SelectMode mode={mode} setMode={setMode}/> - {mode === "play" ? ( - <PlayQuizGenerateQuiz navigation={navigation}/> + <OnlineSelectMode mode={mode} setMode={setMode}/> + {mode === "create" ? ( + <OnlineCreateLobby navigation={navigation}/> ) : ( <PlayQuizJoinQuiz navigation={navigation}/> )} -- GitLab From 6af1638a00b41c065e4d5c08d6242ffa49f00962 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 28 Dec 2024 18:27:35 +0100 Subject: [PATCH 152/283] feat: add online play quiz --- screens/OnlineQuiz/OnlinePlayQuiz.tsx | 97 ++++++++++++++++++++++++++ screens/OnlineQuiz/OnlineQuizChild.tsx | 4 +- 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 screens/OnlineQuiz/OnlinePlayQuiz.tsx diff --git a/screens/OnlineQuiz/OnlinePlayQuiz.tsx b/screens/OnlineQuiz/OnlinePlayQuiz.tsx new file mode 100644 index 0000000..48f212f --- /dev/null +++ b/screens/OnlineQuiz/OnlinePlayQuiz.tsx @@ -0,0 +1,97 @@ +import { StyleSheet, View, Text } from "react-native"; +import AboutAQuiz from "../../components/PlayQuiz/AboutAQuiz"; +import BlueButton from "../../components/PlayQuiz/BlueButton"; +import React, { useEffect } from "react"; +import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; +import {Quiz} from "../../models/Quiz"; +import { useQuizService } from "../../services/QuizService"; +import HttpError from "../../services/HttpError"; +import {getIdOfCategory} from "../../helper/QuizHelper"; +import {NavigationProp} from "@react-navigation/native"; + +interface Props { + navigation: NavigationProp<any>; +} +export default function OnlinePlayQuiz({navigation}: Props) { + const [codeQuiz, setCodeQuiz] = React.useState(""); + const [quiz, setQuiz] = React.useState<Quiz>(); + const [error, setError] = React.useState<string>(""); + + const { remainingQuiz } = useQuizService(); + + useEffect(() => { + fetchQuiz(); + }, [codeQuiz]); + + const fetchQuiz = async () => { + if(codeQuiz === "") + { + return; + } + const quizFetched = await remainingQuiz(codeQuiz); + if (quizFetched instanceof HttpError) { + setError("The quiz does not exist"); + return; + } + setError(""); + setQuiz(quizFetched); + }; + + const handlePlayButtonPress = () => { + navigation.reset({ + index: 0, + routes: [ + { + name: "PlayingQuiz", + params: {quizRecovered: quiz}, + }, + ] + }); + }; + + return ( + <View style={styles.container}> + <View style={styles.contentContainer}> + <View style={styles.fieldContainer}> + {error ? <Text style={styles.errorText}>{error}</Text> : null} + <InputPlayQuiz placeholder={"Enter quiz ID"} setText={setCodeQuiz} noTitle={true} /> + <AboutAQuiz quiz={quiz} /> + </View> + <View style={styles.buttonPlayContainer}> + <BlueButton onPress={handlePlayButtonPress} text={"JOIN"} isDisabled={codeQuiz==="" || error!==""}/> + </View> + </View> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + display: "flex", + width: "100%", + height: "75%", + justifyContent: "space-between", + }, + contentContainer: { + flex: 1, // Prend toute la hauteur disponible + flexDirection: "column", + justifyContent: "space-between", + }, + fieldContainer: { + width: "100%", + flexDirection: "column", + justifyContent: "space-between", + marginTop: 20, + gap: 20, + }, + buttonPlayContainer: { + alignItems: "center", + paddingVertical: 20, + }, + errorText: { + color: "red", + fontSize: 16, + marginBottom: 5, + textAlign: "left", // Aligne le texte à gauche + }, +}); diff --git a/screens/OnlineQuiz/OnlineQuizChild.tsx b/screens/OnlineQuiz/OnlineQuizChild.tsx index f77ad6d..15c561b 100644 --- a/screens/OnlineQuiz/OnlineQuizChild.tsx +++ b/screens/OnlineQuiz/OnlineQuizChild.tsx @@ -2,9 +2,9 @@ import { NavigationProp } from "@react-navigation/native"; import { useState } from "react"; import { OnlineSelectModeType, SelectModeType } from "../../models/SelectModeType"; import { TouchableWithoutFeedback, View, StyleSheet, Keyboard } from "react-native"; -import PlayQuizJoinQuiz from "../PlayQuiz/PlayQuizJoinQuiz"; import OnlineSelectMode from "./OnlineSelectMode"; import OnlineCreateLobby from "./OnlineCreateLobby"; +import OnlinePlayQuiz from "./OnlinePlayQuiz"; interface Props { navigation: NavigationProp<any> @@ -21,7 +21,7 @@ export default function OnlineQuizChild({navigation}: Props){ {mode === "create" ? ( <OnlineCreateLobby navigation={navigation}/> ) : ( - <PlayQuizJoinQuiz navigation={navigation}/> + <OnlinePlayQuiz navigation={navigation}/> )} </View> </View> -- GitLab From 2e3fdc7a0059561df6bccabf31440c0d0bdaa9de Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 5 Jan 2025 16:23:00 +0100 Subject: [PATCH 153/283] feat: add action when click on quiz in to the param --- components/lists/QuizList.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index 5f5502b..6cb9f80 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -35,16 +35,12 @@ import { NavigationProp } from "@react-navigation/native"; * @returns Un composant React contenant une liste interactive de quiz ou des messages en fonction de l'état. */ interface Props { - navigation: NavigationProp<any>; quizList: Quiz[] | null | undefined; isLoadingData: boolean; nextPage?: () => void; + onQuizPressed:(quiz: Quiz) => void; } -export default function QuizList({ navigation, quizList, isLoadingData, nextPage }: Props) { - const onQuizPressed = (quiz: Quiz) => { - navigation.navigate("InformationsOfQuiz", { quiz: quiz }); - }; - +export default function QuizList({ quizList, isLoadingData, nextPage, onQuizPressed }: Props) { const renderQuizItem = ({ item }: { item: Quiz }) => ( <TouchableOpacity style={styles.quizItem} onPress={() => onQuizPressed(item)}> <Text style={styles.quizTitle}>{item.id}</Text> -- GitLab From d692d25bb048638479effe5702a505bc4aeda89d Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 5 Jan 2025 16:24:45 +0100 Subject: [PATCH 154/283] feat: add the action when quiz is clicked to the param --- templates/TemplateQuizList.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/templates/TemplateQuizList.tsx b/templates/TemplateQuizList.tsx index 61f2d8f..3b07229 100644 --- a/templates/TemplateQuizList.tsx +++ b/templates/TemplateQuizList.tsx @@ -34,15 +34,17 @@ interface Props { buttonGoBack: boolean isLoadingData: boolean nextPage?: () => void; + onQuizPressed:(quiz: Quiz) => void; } -export default function TemplateQuizList({navigation, title, setTextInInput, quizList, buttonGoBack, isLoadingData, nextPage}: Props) { + +export default function TemplateQuizList({navigation, title, setTextInInput, quizList, buttonGoBack, isLoadingData, nextPage, onQuizPressed }: Props) { return ( <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={buttonGoBack}> <View style={styles.globalContainer}> <Text style={styles.title}>{title}</Text> <View style={styles.contentContainer}> - {/*<InputPlayQuiz setText={setTextInInput} noTitle={true} />*/} - <QuizList quizList={quizList} navigation={navigation} isLoadingData={isLoadingData} nextPage={nextPage}/> + <InputPlayQuiz setText={setTextInInput} noTitle={true} /> + <QuizList quizList={quizList} isLoadingData={isLoadingData} nextPage={nextPage} onQuizPressed={onQuizPressed}/> </View> </View> </TemplateMenu> -- GitLab From 5b6de82a278ae21c930b3f0262b80e5742210251 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 5 Jan 2025 16:28:20 +0100 Subject: [PATCH 155/283] feat: add the action when quiz is clicked --- screens/Community/Community.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/screens/Community/Community.tsx b/screens/Community/Community.tsx index 152b7f3..73afe99 100644 --- a/screens/Community/Community.tsx +++ b/screens/Community/Community.tsx @@ -5,6 +5,7 @@ import {useEffect, useState} from "react"; import {useQuizService} from "../../services/QuizService"; import HttpError from "../../services/HttpError"; import {useInfiniteQuery} from "@tanstack/react-query"; +import { Quiz } from "../../models/Quiz"; interface Props { navigation: NavigationProp<any> @@ -23,6 +24,10 @@ export default function Community({navigation}: Props) { return quizzes; } + const onQuizPressed = (quiz: Quiz) => { + navigation.navigate("InformationsOfQuiz", { quiz: quiz }); + }; + const { data, @@ -40,6 +45,6 @@ export default function Community({navigation}: Props) { }}); return ( - <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage}/> + <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> ); } \ No newline at end of file -- GitLab From b02f5dc670cb0f3cbfcda8b569c2c5f7ba4096c4 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 5 Jan 2025 16:29:38 +0100 Subject: [PATCH 156/283] feat: add the same action when a quiz is clicked than the community screen --- .../MultiplayerCommunity.tsx | 58 ++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/screens/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/MultiplayerCommunity/MultiplayerCommunity.tsx index 21d8772..01bec0b 100644 --- a/screens/MultiplayerCommunity/MultiplayerCommunity.tsx +++ b/screens/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -1,17 +1,49 @@ -import { View, Text, StyleSheet } from "react-native"; +import {View, Text} from "react-native"; +import TemplateQuizList from "../../templates/TemplateQuizList"; +import {NavigationProp} from "@react-navigation/native"; +import {useEffect, useState} from "react"; +import {useQuizService} from "../../services/QuizService"; +import HttpError from "../../services/HttpError"; +import {useInfiniteQuery} from "@tanstack/react-query"; +import { Quiz } from "../../models/Quiz"; -export default function MultiplayerCommunity() { - return ( - <View style={styles.container}> - <Text>MultiplayerCommunity</Text> - </View> - ); +interface Props { + navigation: NavigationProp<any> } +export default function MultiplayerCommunity({navigation}: Props) { + const {getCommunityQuiz} = useQuizService(); + const [input, setInput] = useState<string>("") + const [loading, setLoading] = useState<boolean>(false) -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', + const fetchCommunityQuizzes = async ({pageParam = 1}) => { + const quizzes = await getCommunityQuiz(pageParam,7); + if (HttpError.isHttpError(quizzes)) { + console.error("Error while fetching community quizzes:", quizzes); + return []; + } + return quizzes; } -}); \ No newline at end of file + + const onQuizPressed = (quiz: Quiz) => { + navigation.navigate("InformationsOfQuiz", { quiz: quiz }); + }; + + const { + data, + fetchNextPage, + } = useInfiniteQuery({ + queryKey: ["communityQuizzes"], + queryFn: fetchCommunityQuizzes, + initialPageParam: 0, + getNextPageParam: (lastPage, allPages) => { + // Si la dernière page a moins de 7 quizzes, il n'y a plus de page suivante + if (lastPage.length < 7) return undefined; + + // On augmente skip de 7 pour la prochaine requête + return allPages.length * 7; + }}); + + return ( + <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> + ); +} \ No newline at end of file -- GitLab From ae53e4311fc052646ac083b5900221584f084e11 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 5 Jan 2025 16:46:15 +0100 Subject: [PATCH 157/283] feat: add modal to create a lobby --- .../MultiplayerCommunity.tsx | 47 +++++++++++++++++-- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/screens/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/MultiplayerCommunity/MultiplayerCommunity.tsx index 01bec0b..cb9dcc8 100644 --- a/screens/MultiplayerCommunity/MultiplayerCommunity.tsx +++ b/screens/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -1,19 +1,21 @@ -import {View, Text} from "react-native"; +import {View, Text, Modal, TextInput} from "react-native"; import TemplateQuizList from "../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; -import {useEffect, useState} from "react"; +import React, {useState} from "react"; import {useQuizService} from "../../services/QuizService"; import HttpError from "../../services/HttpError"; import {useInfiniteQuery} from "@tanstack/react-query"; import { Quiz } from "../../models/Quiz"; +import DefaultButton from "../../components/DefaultButton"; interface Props { navigation: NavigationProp<any> } export default function MultiplayerCommunity({navigation}: Props) { - const {getCommunityQuiz} = useQuizService(); + const {getCommunityQuiz, getRandomQuiz} = useQuizService(); const [input, setInput] = useState<string>("") const [loading, setLoading] = useState<boolean>(false) + const [showModal, setShowModal] = useState<boolean>(false); const fetchCommunityQuizzes = async ({pageParam = 1}) => { const quizzes = await getCommunityQuiz(pageParam,7); @@ -25,7 +27,29 @@ export default function MultiplayerCommunity({navigation}: Props) { } const onQuizPressed = (quiz: Quiz) => { - navigation.navigate("InformationsOfQuiz", { quiz: quiz }); + setShowModal(true); + }; + + const onClosePressed = () => { + setShowModal(false); + }; + + const onPlayPressed = async () => { + setShowModal(false); + const quizData = await getRandomQuiz() //TODO: change the quiz who's getting played + if (quizData instanceof HttpError) { + console.error("Error while fetching random quiz:", quizData); + return; + } + navigation.reset({ + index: 0, + routes: [ + { + name: "PlayingQuiz", + params: {quizRecovered: quizData}, + }, + ] + }); }; const { @@ -44,6 +68,19 @@ export default function MultiplayerCommunity({navigation}: Props) { }}); return ( - <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> + <View> + <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> + <Modal visible={showModal}> + <View> + <View> + <Text>CREATE A LOBBY</Text> + <Text>NUMBER OF PLAYER</Text> + <TextInput></TextInput> + <DefaultButton text="CLOSE" handleButtonPressed={onClosePressed}/> + <DefaultButton text="PLAY" handleButtonPressed={onPlayPressed}/> + </View> + </View> + </Modal> + </View> ); } \ No newline at end of file -- GitLab From 3ef5087e8950ae0dd3d0449a3b4d291f9a4a9213 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 5 Jan 2025 16:58:22 +0100 Subject: [PATCH 158/283] feat: add ongoingQuizzes --- screens/OngoingQuizzes/OngoingQuizzes.tsx | 68 ++++++++++++++++++----- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/screens/OngoingQuizzes/OngoingQuizzes.tsx b/screens/OngoingQuizzes/OngoingQuizzes.tsx index 191ae5f..64502b9 100644 --- a/screens/OngoingQuizzes/OngoingQuizzes.tsx +++ b/screens/OngoingQuizzes/OngoingQuizzes.tsx @@ -1,17 +1,59 @@ -import { View, StyleSheet, Text } from "react-native"; +import {View, Text} from "react-native"; +import TemplateQuizList from "../../templates/TemplateQuizList"; +import {NavigationProp} from "@react-navigation/native"; +import {useEffect, useState} from "react"; +import {useQuizService} from "../../services/QuizService"; +import HttpError from "../../services/HttpError"; +import {useInfiniteQuery} from "@tanstack/react-query"; +import { Quiz } from "../../models/Quiz"; +import DefaultButton from "../../components/DefaultButton"; -export default function OngoingQuizzes() { - return ( - <View style={styles.container}> - <Text>OngoingQuizzes</Text> - </View> - ); +interface Props { + navigation: NavigationProp<any> } +export default function Community({navigation}: Props) { + const {getCommunityQuiz} = useQuizService(); + const [input, setInput] = useState<string>("") + const [loading, setLoading] = useState<boolean>(false) + const [CurrentQuiz, setCurrentQuiz] = useState<Quiz | null>(null); -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', + const fetchCommunityQuizzes = async ({pageParam = 1}) => { + const quizzes = await getCommunityQuiz(pageParam,7); + if (HttpError.isHttpError(quizzes)) { + console.error("Error while fetching community quizzes:", quizzes); + return []; + } + return quizzes; } -}); \ No newline at end of file + + const onQuizPressed = (quiz: Quiz) => { + setCurrentQuiz(quiz); + }; + + const onJoinPressed = () => { + navigation.goBack(); + }; + + + const { + data, + fetchNextPage, + } = useInfiniteQuery({ + queryKey: ["communityQuizzes"], + queryFn: fetchCommunityQuizzes, + initialPageParam: 0, + getNextPageParam: (lastPage, allPages) => { + // Si la dernière page a moins de 7 quizzes, il n'y a plus de page suivante + if (lastPage.length < 7) return undefined; + + // On augmente skip de 7 pour la prochaine requête + return allPages.length * 7; + }}); + + return ( + <View> + <TemplateQuizList title={"Ongoing Quizzes"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> + <DefaultButton text="JOIN" handleButtonPressed={onJoinPressed}/> + </View> + ); +} \ No newline at end of file -- GitLab From 5ff525c04a48b4d29b68662889a9bf043633e2c0 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 5 Jan 2025 17:06:33 +0100 Subject: [PATCH 159/283] feat: add lobby to the stack Navigation --- routes/StackNavigator.tsx | 3 ++- screens/Lobby/Lobby.tsx | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 screens/Lobby/Lobby.tsx diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index bb4c2a2..ad94af9 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -12,6 +12,7 @@ import Multiplayer from "../screens/Multiplayer/Multiplayer"; import MultiplayerCommunity from "../screens/MultiplayerCommunity/MultiplayerCommunity"; import OnlineQuiz from "../screens/OnlineQuiz/OnlineQuiz"; import OngoingQuizzes from "../screens/OngoingQuizzes/OngoingQuizzes"; +import Lobby from "../screens/Lobby/Lobby"; const Stack = createNativeStackNavigator(); @@ -32,7 +33,7 @@ export default function StackNavigator() { <Stack.Screen name="MultiplayerCommunity" component={MultiplayerCommunity} options={{ headerShown: false }}/> <Stack.Screen name="OnlineQuiz" component={OnlineQuiz} options={{ headerShown: false }}/> <Stack.Screen name="OngoingQuizzes" component={OngoingQuizzes} options={{ headerShown: false }}/> - + <Stack.Screen name="Lobby" component={Lobby} options={{ headerShown: false }}/> </Stack.Navigator> </NavigationContainer> ); diff --git a/screens/Lobby/Lobby.tsx b/screens/Lobby/Lobby.tsx new file mode 100644 index 0000000..541bd56 --- /dev/null +++ b/screens/Lobby/Lobby.tsx @@ -0,0 +1,22 @@ +import { View, Text, StyleSheet } from "react-native"; + +export default function Lobby() { + return ( + <View style={styles.container}> + <Text style={styles.title}>Lobby</Text> + </View> + ) +} + +const styles = StyleSheet.create({ + container: { + display: "flex", + height: "100%", + width: "100%", + justifyContent: "center", + alignItems: "center", + }, + title: { + fontSize: 40, + } +}); \ No newline at end of file -- GitLab From 5421df6d29ccb21bb2979e28cbf9e0d6afa22fc1 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 17:40:43 +0100 Subject: [PATCH 160/283] refactor: removed useless hook --- hooks/UseUser.ts | 58 ------------------------------------------------ 1 file changed, 58 deletions(-) delete mode 100644 hooks/UseUser.ts diff --git a/hooks/UseUser.ts b/hooks/UseUser.ts deleted file mode 100644 index 7d30040..0000000 --- a/hooks/UseUser.ts +++ /dev/null @@ -1,58 +0,0 @@ -import AsyncStorage from "@react-native-async-storage/async-storage"; -export const useUser = () => { - const getUser = async <T>(key: string): Promise<T | undefined> => { - try { - const item = await AsyncStorage.getItem(key); - if (!item) { - return undefined; - } - return JSON.parse(item); - } catch (error) { - console.error("Erreur lors de la récupération de la donnée des informations utilisateur", error); - return undefined; - } - }; - - const setUser = async <T>(key: string, newValue: T) => { - try { - await AsyncStorage.setItem(key, JSON.stringify(newValue)); - } catch (error) { - console.error("Erreur lors de l'enregistrement de la donnée", error); - } - }; - - const getAnonymousUser = async <T>(): Promise<T | undefined> => { - return getUser("anonymousUser"); - } - - const setAnonymousUser = async <T>(newValue: T) => { - return setUser("anonymousUser", newValue); - } - - const getRealUser = async <T>(): Promise<T | undefined> => { - return getUser("realUser"); - } - - const setRealUser = async <T>(newValue: T) => { - return setUser("realUser", newValue); - } - - const connectUser = async <T>(user: T) => { - // TODO: Implement - } - - const logOutUser = async () => { - // TODO: Implement - } - - return { - getAnonymousUser: getAnonymousUser, - setAnonymousUser: setAnonymousUser, - getRealUser: getRealUser, - setRealUser: setRealUser, - connectUser: connectUser, - logOutUser: logOutUser, - }; -} - - -- GitLab From 8d8af2b0bb46bbc1b61ec109f5e9344c6f181cd1 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 17:53:15 +0100 Subject: [PATCH 161/283] refactor(files archi): moved GenerateQuiz --- screens/GenerateQuiz/GenerateQuizHeader.tsx | 0 .../{ => Home/PlayQuiz}/GenerateQuiz/GenerateQuiz.tsx | 2 +- .../PlayQuiz}/GenerateQuiz/GenerateQuizBody.tsx | 10 +++++----- 3 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 screens/GenerateQuiz/GenerateQuizHeader.tsx rename screens/{ => Home/PlayQuiz}/GenerateQuiz/GenerateQuiz.tsx (78%) rename screens/{ => Home/PlayQuiz}/GenerateQuiz/GenerateQuizBody.tsx (89%) diff --git a/screens/GenerateQuiz/GenerateQuizHeader.tsx b/screens/GenerateQuiz/GenerateQuizHeader.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/screens/GenerateQuiz/GenerateQuiz.tsx b/screens/Home/PlayQuiz/GenerateQuiz/GenerateQuiz.tsx similarity index 78% rename from screens/GenerateQuiz/GenerateQuiz.tsx rename to screens/Home/PlayQuiz/GenerateQuiz/GenerateQuiz.tsx index 014f469..0d95d50 100644 --- a/screens/GenerateQuiz/GenerateQuiz.tsx +++ b/screens/Home/PlayQuiz/GenerateQuiz/GenerateQuiz.tsx @@ -1,4 +1,4 @@ -import TemplateScreen from "../../templates/TemplateScreen"; +import TemplateScreen from "../../../../templates/TemplateScreen"; import GenerateQuizBody from "./GenerateQuizBody"; export default function GenerateQuiz({navigation}: any) { diff --git a/screens/GenerateQuiz/GenerateQuizBody.tsx b/screens/Home/PlayQuiz/GenerateQuiz/GenerateQuizBody.tsx similarity index 89% rename from screens/GenerateQuiz/GenerateQuizBody.tsx rename to screens/Home/PlayQuiz/GenerateQuiz/GenerateQuizBody.tsx index 198f438..854806a 100644 --- a/screens/GenerateQuiz/GenerateQuizBody.tsx +++ b/screens/Home/PlayQuiz/GenerateQuiz/GenerateQuizBody.tsx @@ -1,11 +1,11 @@ -import ComboBox from "../../components/comboBox/ComboBox"; -import DefaultButton from "../../components/DefaultButton"; +import ComboBox from "../../../../components/comboBox/ComboBox"; +import DefaultButton from "../../../../components/DefaultButton"; import {StyleSheet, View} from "react-native"; import {useState} from "react"; import {useTranslation} from "react-i18next"; -import {useQuizService} from "../../services/QuizService"; -import {EDifficulty} from "../../models/EDifficulty"; -import {getIdOfCategory} from "../../helper/QuizHelper"; +import {useQuizService} from "../../../../services/QuizService"; +import {EDifficulty} from "../../../../models/EDifficulty"; +import {getIdOfCategory} from "../../../../helper/QuizHelper"; const optionsDifficulty = [ 'easy', 'medium', 'hard']; const optionsQuestions = ['1', '50']; -- GitLab From c4e36b136b7db6523225d0f3551c18ad7fd06e3e Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 17:53:46 +0100 Subject: [PATCH 162/283] refactor(files archi): moved Profile screen and componenents --- screens/Profil/{ => Modals}/ProfilModal.tsx | 4 ++-- screens/Profil/{ => Modals}/ProfilModalLogin.tsx | 8 ++++---- screens/Profil/{ => Modals}/ProfilModalSignup.tsx | 8 ++++---- screens/Profil/ProfilChild.tsx | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) rename screens/Profil/{ => Modals}/ProfilModal.tsx (95%) rename screens/Profil/{ => Modals}/ProfilModalLogin.tsx (91%) rename screens/Profil/{ => Modals}/ProfilModalSignup.tsx (92%) diff --git a/screens/Profil/ProfilModal.tsx b/screens/Profil/Modals/ProfilModal.tsx similarity index 95% rename from screens/Profil/ProfilModal.tsx rename to screens/Profil/Modals/ProfilModal.tsx index f0227d7..fb5afbd 100644 --- a/screens/Profil/ProfilModal.tsx +++ b/screens/Profil/Modals/ProfilModal.tsx @@ -8,8 +8,8 @@ import { Keyboard, TouchableWithoutFeedback, } from "react-native"; -import ProfilSelectMode from "./ProfilSelectMode"; -import { ProfilSelectModeType } from "../../models/SelectModeType"; +import ProfilSelectMode from "../ProfilSelectMode"; +import { ProfilSelectModeType } from "../../../models/SelectModeType"; import ProfilModalLogin from "./ProfilModalLogin"; import ProfilModalSignup from "./ProfilModalSignup"; import Icon from "react-native-vector-icons/MaterialIcons"; diff --git a/screens/Profil/ProfilModalLogin.tsx b/screens/Profil/Modals/ProfilModalLogin.tsx similarity index 91% rename from screens/Profil/ProfilModalLogin.tsx rename to screens/Profil/Modals/ProfilModalLogin.tsx index 0d0a373..48b38b2 100644 --- a/screens/Profil/ProfilModalLogin.tsx +++ b/screens/Profil/Modals/ProfilModalLogin.tsx @@ -1,9 +1,9 @@ import { TextInput, View, StyleSheet, Text } from "react-native"; -import DefaultButton from "../../components/DefaultButton"; -import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; +import DefaultButton from "../../../components/DefaultButton"; +import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import React, { useState } from "react"; -import {useUserService} from "../../services/UserService"; -import HttpError from "../../services/HttpError"; +import {useUserService} from "../../../services/UserService"; +import HttpError from "../../../services/HttpError"; import {NavigationProp} from "@react-navigation/native"; interface Props { diff --git a/screens/Profil/ProfilModalSignup.tsx b/screens/Profil/Modals/ProfilModalSignup.tsx similarity index 92% rename from screens/Profil/ProfilModalSignup.tsx rename to screens/Profil/Modals/ProfilModalSignup.tsx index 3179c0c..db63d11 100644 --- a/screens/Profil/ProfilModalSignup.tsx +++ b/screens/Profil/Modals/ProfilModalSignup.tsx @@ -1,9 +1,9 @@ import {TextInput, View, StyleSheet, Text} from "react-native"; -import DefaultButton from "../../components/DefaultButton"; -import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; +import DefaultButton from "../../../components/DefaultButton"; +import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import React, {useState} from "react"; -import {useUserService} from "../../services/UserService"; -import HttpError from "../../services/HttpError"; +import {useUserService} from "../../../services/UserService"; +import HttpError from "../../../services/HttpError"; import {NavigationProp} from "@react-navigation/native"; interface Props { diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index b3dee69..81208fc 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -1,6 +1,6 @@ import { NavigationProp } from "@react-navigation/native"; import { View, StyleSheet, Image, Text, TouchableOpacity } from "react-native"; -import ProfilModal from "./ProfilModal"; +import ProfilModal from "./Modals/ProfilModal"; import React, { useEffect, useState } from "react"; import ProfilHeaderAccount from "./ProfilHeaderAccount"; import { ProfilSelectModeType } from "../../models/SelectModeType"; -- GitLab From e6382a98db24cbb923ee225b1c02935a51173a92 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 17:54:23 +0100 Subject: [PATCH 163/283] refactor(files archi): moved PlayQuiz screen and components --- screens/{ => Home}/PlayQuiz/PlayQuiz.tsx | 4 ++-- screens/{ => Home}/PlayQuiz/PlayQuizChild.tsx | 4 ++-- .../{ => Home}/PlayQuiz/PlayQuizGenerateQuiz.tsx | 10 +++++----- screens/{ => Home}/PlayQuiz/PlayQuizJoinQuiz.tsx | 14 +++++++------- 4 files changed, 16 insertions(+), 16 deletions(-) rename screens/{ => Home}/PlayQuiz/PlayQuiz.tsx (88%) rename screens/{ => Home}/PlayQuiz/PlayQuizChild.tsx (91%) rename screens/{ => Home}/PlayQuiz/PlayQuizGenerateQuiz.tsx (91%) rename screens/{ => Home}/PlayQuiz/PlayQuizJoinQuiz.tsx (85%) diff --git a/screens/PlayQuiz/PlayQuiz.tsx b/screens/Home/PlayQuiz/PlayQuiz.tsx similarity index 88% rename from screens/PlayQuiz/PlayQuiz.tsx rename to screens/Home/PlayQuiz/PlayQuiz.tsx index 991c6ba..02c949f 100644 --- a/screens/PlayQuiz/PlayQuiz.tsx +++ b/screens/Home/PlayQuiz/PlayQuiz.tsx @@ -1,7 +1,7 @@ import {NavigationProp} from "@react-navigation/native"; -import TemplateMenu from "../../templates/TemplateMenu"; +import TemplateMenu from "../../../templates/TemplateMenu"; import {StyleSheet, View} from "react-native"; -import HomeChild from "../Home/HomeChild"; +import HomeChild from "../HomeChild"; import React from "react"; import PlayQuizChild from "./PlayQuizChild"; diff --git a/screens/PlayQuiz/PlayQuizChild.tsx b/screens/Home/PlayQuiz/PlayQuizChild.tsx similarity index 91% rename from screens/PlayQuiz/PlayQuizChild.tsx rename to screens/Home/PlayQuiz/PlayQuizChild.tsx index ad6e515..4c982b3 100644 --- a/screens/PlayQuiz/PlayQuizChild.tsx +++ b/screens/Home/PlayQuiz/PlayQuizChild.tsx @@ -1,8 +1,8 @@ import {Text, View, StyleSheet, TouchableWithoutFeedback, Keyboard} from "react-native"; import {NavigationProp} from "@react-navigation/native"; -import SelectMode from "../../components/PlayQuiz/SelectMode"; +import SelectMode from "../../../components/PlayQuiz/SelectMode"; import React, {useState} from "react"; -import {SelectModeType} from "../../models/SelectModeType"; +import {SelectModeType} from "../../../models/SelectModeType"; import PlayQuizGenerateQuiz from "./PlayQuizGenerateQuiz"; import PlayQuizJoinQuiz from "./PlayQuizJoinQuiz"; diff --git a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx b/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx similarity index 91% rename from screens/PlayQuiz/PlayQuizGenerateQuiz.tsx rename to screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx index 4b9bc60..bfcb144 100644 --- a/screens/PlayQuiz/PlayQuizGenerateQuiz.tsx +++ b/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx @@ -1,10 +1,10 @@ import { Text,View, StyleSheet } from "react-native"; -import SelectListBox from "../../components/PlayQuiz/SelectListBox"; +import SelectListBox from "../../../components/PlayQuiz/SelectListBox"; import React, {useEffect} from "react"; -import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; -import BlueButton from "../../components/PlayQuiz/BlueButton"; -import {getIdOfCategory} from "../../helper/QuizHelper"; -import {useQuizService} from "../../services/QuizService"; +import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; +import BlueButton from "../../../components/PlayQuiz/BlueButton"; +import {getIdOfCategory} from "../../../helper/QuizHelper"; +import {useQuizService} from "../../../services/QuizService"; import {NavigationProp} from "@react-navigation/native"; const difficulty = [ diff --git a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx b/screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx similarity index 85% rename from screens/PlayQuiz/PlayQuizJoinQuiz.tsx rename to screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx index 4493ce4..264ad60 100644 --- a/screens/PlayQuiz/PlayQuizJoinQuiz.tsx +++ b/screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx @@ -1,12 +1,12 @@ import { StyleSheet, View, Text } from "react-native"; -import AboutAQuiz from "../../components/PlayQuiz/AboutAQuiz"; -import BlueButton from "../../components/PlayQuiz/BlueButton"; +import AboutAQuiz from "../../../components/PlayQuiz/AboutAQuiz"; +import BlueButton from "../../../components/PlayQuiz/BlueButton"; import React, { useEffect } from "react"; -import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; -import {Quiz} from "../../models/Quiz"; -import { useQuizService } from "../../services/QuizService"; -import HttpError from "../../services/HttpError"; -import {getIdOfCategory} from "../../helper/QuizHelper"; +import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; +import {Quiz} from "../../../models/Quiz"; +import { useQuizService } from "../../../services/QuizService"; +import HttpError from "../../../services/HttpError"; +import {getIdOfCategory} from "../../../helper/QuizHelper"; import {NavigationProp} from "@react-navigation/native"; interface Props { -- GitLab From be15756c2f10d56bef706f1d2e6f90c2adfbe31e Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 17:55:12 +0100 Subject: [PATCH 164/283] refactor(files archi): moved InformationsOfQuiz --- .../{ => Community}/InformationsOfQuiz/InformationsOfQuiz.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename screens/{ => Community}/InformationsOfQuiz/InformationsOfQuiz.tsx (96%) diff --git a/screens/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx similarity index 96% rename from screens/InformationsOfQuiz/InformationsOfQuiz.tsx rename to screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx index 442776a..a65b874 100644 --- a/screens/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -1,8 +1,8 @@ import React from "react"; import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; -import TemplateMenu from "../../templates/TemplateMenu"; +import TemplateMenu from "../../../templates/TemplateMenu"; import {NavigationProp, RouteProp} from "@react-navigation/native"; -import {Quiz} from "../../models/Quiz"; +import {Quiz} from "../../../models/Quiz"; type RoutePropsType = { InformationsOfQuiz: { -- GitLab From cafc0d33108206b3362cd1e1f63be9c640087719 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 18:03:37 +0100 Subject: [PATCH 165/283] refactor(files archi): moved endQuiz --- screens/{ => PlayingQuiz}/EndQuiz/EndQuiz.tsx | 4 ++-- screens/{ => PlayingQuiz}/EndQuiz/EndQuizChild.tsx | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) rename screens/{ => PlayingQuiz}/EndQuiz/EndQuiz.tsx (97%) rename screens/{ => PlayingQuiz}/EndQuiz/EndQuizChild.tsx (90%) diff --git a/screens/EndQuiz/EndQuiz.tsx b/screens/PlayingQuiz/EndQuiz/EndQuiz.tsx similarity index 97% rename from screens/EndQuiz/EndQuiz.tsx rename to screens/PlayingQuiz/EndQuiz/EndQuiz.tsx index ee5c5c5..8297ee0 100644 --- a/screens/EndQuiz/EndQuiz.tsx +++ b/screens/PlayingQuiz/EndQuiz/EndQuiz.tsx @@ -1,8 +1,8 @@ import {NavigationProp, RouteProp} from "@react-navigation/native"; import { View, StyleSheet, Image, Text } from "react-native"; import EndQuizChild from "./EndQuizChild"; -import TemplateMenu from "../../templates/TemplateMenu"; -import {Quiz} from "../../models/Quiz"; +import TemplateMenu from "../../../templates/TemplateMenu"; +import {Quiz} from "../../../models/Quiz"; type RoutePropsType = { EndQuizChild: { diff --git a/screens/EndQuiz/EndQuizChild.tsx b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx similarity index 90% rename from screens/EndQuiz/EndQuizChild.tsx rename to screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx index 9d31713..2a8dc49 100644 --- a/screens/EndQuiz/EndQuizChild.tsx +++ b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx @@ -1,9 +1,9 @@ import { View, Image, Text, StyleSheet } from "react-native"; -import DefaultButton from "../../components/DefaultButton"; +import DefaultButton from "../../../components/DefaultButton"; import { NavigationProp, RouteProp } from "@react-navigation/native"; -import {Quiz} from "../../models/Quiz"; -import {useQuizService} from "../../services/QuizService"; -import HttpError from "../../services/HttpError"; +import {Quiz} from "../../../models/Quiz"; +import {useQuizService} from "../../../services/QuizService"; +import HttpError from "../../../services/HttpError"; @@ -49,12 +49,12 @@ export default function EndQuizChild({ navigation, quiz }: Props) { return ( <View style={styles.container}> <View style={styles.containerHeader}> - <Image source={require('../../assets/TitleApp.png')}/> + <Image source={require('../../../assets/TitleApp.png')}/> </View> <View style={styles.containerBody}> <Text style={styles.TextBodyTitle}>{((quiz.score/quiz.questionCount)*100 > 50 ) ? 'Victory' : 'Defeat'}</Text> <Text style={styles.TextBody}>you finish the test with a score of {`${quiz.score}/${quiz.questionCount}`} ! </Text> - <Image source={require('../../assets/uWin.png')} /> + <Image source={require('../../../assets/uWin.png')} /> <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton> <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu} buttonStyle={styles.blueButton} buttonText={styles.blueButtonText}></DefaultButton> </View> -- GitLab From 5ee8a1996f19dd6772e18dca952fff3d51a20c3d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 18:40:52 +0100 Subject: [PATCH 166/283] refactor(files archi): moved Home --- {screens/Home => components/navigation}/HeaderNavigation.tsx | 2 +- screens/Home/HomeChild.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename {screens/Home => components/navigation}/HeaderNavigation.tsx (96%) diff --git a/screens/Home/HeaderNavigation.tsx b/components/navigation/HeaderNavigation.tsx similarity index 96% rename from screens/Home/HeaderNavigation.tsx rename to components/navigation/HeaderNavigation.tsx index 03370bb..1b17e9c 100644 --- a/screens/Home/HeaderNavigation.tsx +++ b/components/navigation/HeaderNavigation.tsx @@ -1,6 +1,6 @@ import { View, StyleSheet, Image, TouchableOpacity } from "react-native"; import { NavigationProp } from "@react-navigation/native"; -import ConfirmModal from "../../components/PlayingQuiz/ComfirmModal"; +import ConfirmModal from "../PlayingQuiz/ComfirmModal"; interface Props { navigation: NavigationProp<any>; diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 780947f..1df4cb5 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -53,7 +53,7 @@ export default function HomeChild({navigation}: Props) { </View> <View style={styles.buttonContainer}> <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonQuickGamePressed}/> - <MenuButton text={"PLAY A QUIZ"} handleButtonPressed={handleButtonGeneratePressed}/> + <MenuButton text={"PLAY"} handleButtonPressed={handleButtonGeneratePressed}/> {/*<MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/>*/} </View> <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> -- GitLab From 1757ed0a3680e30e529f0d87624335845cb0a834 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 18:41:08 +0100 Subject: [PATCH 167/283] refactor(files archi): moved rest --- routes/StackNavigator.tsx | 10 +++++----- screens/{ => Home}/MyQuizzes/MyQuizzes.tsx | 4 ++-- templates/TemplateMenu.tsx | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename screens/{ => Home}/MyQuizzes/MyQuizzes.tsx (81%) diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index cbfdc43..7f91326 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -1,13 +1,13 @@ import {createNativeStackNavigator} from "@react-navigation/native-stack"; import {NavigationContainer} from "@react-navigation/native"; import Home from "../screens/Home/Home"; -import GenerateQuiz from "../screens/GenerateQuiz/GenerateQuiz"; -import EndQuiz from "../screens/EndQuiz/EndQuiz"; +import GenerateQuiz from "../screens/Home/PlayQuiz/GenerateQuiz/GenerateQuiz"; +import EndQuiz from "../screens/PlayingQuiz/EndQuiz/EndQuiz"; import PlayingQuiz from "../screens/PlayingQuiz/PlayingQuiz"; import TabNavigator from "./TabNavigation"; -import PlayQuiz from "../screens/PlayQuiz/PlayQuiz"; -import MyQuizzes from "../screens/MyQuizzes/MyQuizzes"; -import InformationsOfQuiz from "../screens/InformationsOfQuiz/InformationsOfQuiz"; +import PlayQuiz from "../screens/Home/PlayQuiz/PlayQuiz"; +import MyQuizzes from "../screens/Home/MyQuizzes/MyQuizzes"; +import InformationsOfQuiz from "../screens/Community/InformationsOfQuiz/InformationsOfQuiz"; const Stack = createNativeStackNavigator(); diff --git a/screens/MyQuizzes/MyQuizzes.tsx b/screens/Home/MyQuizzes/MyQuizzes.tsx similarity index 81% rename from screens/MyQuizzes/MyQuizzes.tsx rename to screens/Home/MyQuizzes/MyQuizzes.tsx index eb61204..66004ca 100644 --- a/screens/MyQuizzes/MyQuizzes.tsx +++ b/screens/Home/MyQuizzes/MyQuizzes.tsx @@ -1,7 +1,7 @@ -import TemplateQuizList from "../../templates/TemplateQuizList"; +import TemplateQuizList from "../../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; import {useState} from "react"; -import {Quiz} from "../../models/Quiz"; +import {Quiz} from "../../../models/Quiz"; interface Props { navigation: NavigationProp<any> diff --git a/templates/TemplateMenu.tsx b/templates/TemplateMenu.tsx index 6be8186..d58ffa2 100644 --- a/templates/TemplateMenu.tsx +++ b/templates/TemplateMenu.tsx @@ -1,5 +1,5 @@ import {ImageBackground, Text, View, StyleSheet, SafeAreaView} from "react-native"; -import HeaderNavigation from "../screens/Home/HeaderNavigation"; +import HeaderNavigation from "../components/navigation/HeaderNavigation"; import {NavigationProp} from "@react-navigation/native"; interface Props{ -- GitLab From 2a10ec4617f4bc87f57c9517e1dc41ccd5676e14 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 18:54:22 +0100 Subject: [PATCH 168/283] refactor(files archi): moved defaultButton --- components/ModalJoinQuiz.tsx | 2 +- components/{ => buttons}/DefaultButton.tsx | 4 ++-- components/comboBox/ComboBox.tsx | 2 +- screens/Home/HomeChild.tsx | 2 +- screens/Home/PlayQuiz/GenerateQuiz/GenerateQuizBody.tsx | 2 +- screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx | 2 +- screens/PlayingQuiz/PlayingQuizBody.tsx | 2 +- screens/PlayingQuiz/PlayingQuizHeader.tsx | 2 +- screens/Profil/Modals/ProfilModalLogin.tsx | 2 +- screens/Profil/Modals/ProfilModalSignup.tsx | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) rename components/{ => buttons}/DefaultButton.tsx (88%) diff --git a/components/ModalJoinQuiz.tsx b/components/ModalJoinQuiz.tsx index 77320ad..be899d2 100644 --- a/components/ModalJoinQuiz.tsx +++ b/components/ModalJoinQuiz.tsx @@ -1,5 +1,5 @@ import { Modal, View, Text, StyleSheet, TextInput } from "react-native"; -import DefaultButton from "./DefaultButton"; +import DefaultButton from "./buttons/DefaultButton"; import {useState} from "react"; import {useQuizService} from "../services/QuizService"; import HttpError from "../services/HttpError"; diff --git a/components/DefaultButton.tsx b/components/buttons/DefaultButton.tsx similarity index 88% rename from components/DefaultButton.tsx rename to components/buttons/DefaultButton.tsx index d4aaa4f..cd0fe2a 100644 --- a/components/DefaultButton.tsx +++ b/components/buttons/DefaultButton.tsx @@ -1,6 +1,6 @@ import {TouchableOpacity, Text} from "react-native"; -import {ButtonsStyles} from "../styles/ButtonsStyles"; -import {TextsStyles} from "../styles/TextsStyles"; +import {ButtonsStyles} from "../../styles/ButtonsStyles"; +import {TextsStyles} from "../../styles/TextsStyles"; interface Props{ text: string; diff --git a/components/comboBox/ComboBox.tsx b/components/comboBox/ComboBox.tsx index d89f425..12ed9f3 100644 --- a/components/comboBox/ComboBox.tsx +++ b/components/comboBox/ComboBox.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { View, Text, StyleSheet, Modal, TouchableOpacity, TextInput, Platform } from 'react-native'; import { Picker } from '@react-native-picker/picker'; -import DefaultButton from "../DefaultButton"; +import DefaultButton from "../buttons/DefaultButton"; interface Props { options: string[]; diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 1df4cb5..fd9c7da 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -1,5 +1,5 @@ import { View, StyleSheet, Image, Modal, TextInput } from "react-native"; -import DefaultButton from "../../components/DefaultButton"; +import DefaultButton from "../../components/buttons/DefaultButton"; import {useTranslation} from "react-i18next"; import { useState } from "react"; import { TextsStyles } from "../../styles/TextsStyles"; diff --git a/screens/Home/PlayQuiz/GenerateQuiz/GenerateQuizBody.tsx b/screens/Home/PlayQuiz/GenerateQuiz/GenerateQuizBody.tsx index 854806a..a50e17d 100644 --- a/screens/Home/PlayQuiz/GenerateQuiz/GenerateQuizBody.tsx +++ b/screens/Home/PlayQuiz/GenerateQuiz/GenerateQuizBody.tsx @@ -1,5 +1,5 @@ import ComboBox from "../../../../components/comboBox/ComboBox"; -import DefaultButton from "../../../../components/DefaultButton"; +import DefaultButton from "../../../../components/buttons/DefaultButton"; import {StyleSheet, View} from "react-native"; import {useState} from "react"; import {useTranslation} from "react-i18next"; diff --git a/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx index 2a8dc49..2d8bafc 100644 --- a/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx +++ b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx @@ -1,5 +1,5 @@ import { View, Image, Text, StyleSheet } from "react-native"; -import DefaultButton from "../../../components/DefaultButton"; +import DefaultButton from "../../../components/buttons/DefaultButton"; import { NavigationProp, RouteProp } from "@react-navigation/native"; import {Quiz} from "../../../models/Quiz"; import {useQuizService} from "../../../services/QuizService"; diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index ad504db..3393f06 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -1,5 +1,5 @@ import { View, StyleSheet, Text } from "react-native"; -import DefaultButton from "../../components/DefaultButton"; +import DefaultButton from "../../components/buttons/DefaultButton"; import { ButtonsStyles } from "../../styles/ButtonsStyles"; import React, { useState, useEffect, SetStateAction, Dispatch } from "react"; import { useQuizService } from "../../services/QuizService"; diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index e2c5634..9da0130 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -2,7 +2,7 @@ import { View, StyleSheet, Text } from "react-native"; import { TextsStyles } from "../../styles/TextsStyles"; import {Quiz} from "../../models/Quiz"; import {useTranslation} from "react-i18next"; -import DefaultButton from "../../components/DefaultButton"; +import DefaultButton from "../../components/buttons/DefaultButton"; import GoHomeButton from "../../components/PlayingQuiz/GoHomeButton"; import {NavigationProp} from "@react-navigation/native"; import ConfirmModal from "../../components/PlayingQuiz/ComfirmModal"; diff --git a/screens/Profil/Modals/ProfilModalLogin.tsx b/screens/Profil/Modals/ProfilModalLogin.tsx index 48b38b2..2f651da 100644 --- a/screens/Profil/Modals/ProfilModalLogin.tsx +++ b/screens/Profil/Modals/ProfilModalLogin.tsx @@ -1,5 +1,5 @@ import { TextInput, View, StyleSheet, Text } from "react-native"; -import DefaultButton from "../../../components/DefaultButton"; +import DefaultButton from "../../../components/buttons/DefaultButton"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import React, { useState } from "react"; import {useUserService} from "../../../services/UserService"; diff --git a/screens/Profil/Modals/ProfilModalSignup.tsx b/screens/Profil/Modals/ProfilModalSignup.tsx index db63d11..a673efa 100644 --- a/screens/Profil/Modals/ProfilModalSignup.tsx +++ b/screens/Profil/Modals/ProfilModalSignup.tsx @@ -1,5 +1,5 @@ import {TextInput, View, StyleSheet, Text} from "react-native"; -import DefaultButton from "../../../components/DefaultButton"; +import DefaultButton from "../../../components/buttons/DefaultButton"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import React, {useState} from "react"; import {useUserService} from "../../../services/UserService"; -- GitLab From d49c3e898addc6d2151126841d837b83d6929102 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 19:06:34 +0100 Subject: [PATCH 169/283] refactor(files archi): moved Blue Button --- components/PlayingQuiz/ComfirmModal.tsx | 2 +- components/{PlayQuiz => buttons}/BlueButton.tsx | 0 screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx | 2 +- screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx | 2 +- screens/PlayingQuiz/PlayingQuizBody.tsx | 2 +- styles/ButtonsStyles.ts | 7 +++++++ styles/TextsStyles.ts | 4 ++++ 7 files changed, 15 insertions(+), 4 deletions(-) rename components/{PlayQuiz => buttons}/BlueButton.tsx (100%) diff --git a/components/PlayingQuiz/ComfirmModal.tsx b/components/PlayingQuiz/ComfirmModal.tsx index 81af836..4b91e82 100644 --- a/components/PlayingQuiz/ComfirmModal.tsx +++ b/components/PlayingQuiz/ComfirmModal.tsx @@ -8,7 +8,7 @@ import { TouchableWithoutFeedback, } from "react-native"; import Icon from "react-native-vector-icons/MaterialIcons"; -import BlueButton from "../PlayQuiz/BlueButton"; // Pour l'icône de fermeture +import BlueButton from "../buttons/BlueButton"; // Pour l'icône de fermeture interface Props { visible: boolean; diff --git a/components/PlayQuiz/BlueButton.tsx b/components/buttons/BlueButton.tsx similarity index 100% rename from components/PlayQuiz/BlueButton.tsx rename to components/buttons/BlueButton.tsx diff --git a/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx b/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx index bfcb144..481b82f 100644 --- a/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx +++ b/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx @@ -2,7 +2,7 @@ import { Text,View, StyleSheet } from "react-native"; import SelectListBox from "../../../components/PlayQuiz/SelectListBox"; import React, {useEffect} from "react"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; -import BlueButton from "../../../components/PlayQuiz/BlueButton"; +import BlueButton from "../../../components/buttons/BlueButton"; import {getIdOfCategory} from "../../../helper/QuizHelper"; import {useQuizService} from "../../../services/QuizService"; import {NavigationProp} from "@react-navigation/native"; diff --git a/screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx b/screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx index 264ad60..9f856f9 100644 --- a/screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx +++ b/screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx @@ -1,6 +1,6 @@ import { StyleSheet, View, Text } from "react-native"; import AboutAQuiz from "../../../components/PlayQuiz/AboutAQuiz"; -import BlueButton from "../../../components/PlayQuiz/BlueButton"; +import BlueButton from "../../../components/buttons/BlueButton"; import React, { useEffect } from "react"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import {Quiz} from "../../../models/Quiz"; diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 3393f06..da6f1f6 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -5,7 +5,7 @@ import React, { useState, useEffect, SetStateAction, Dispatch } from "react"; import { useQuizService } from "../../services/QuizService"; import {NavigationProp} from "@react-navigation/native"; import HttpError from "../../services/HttpError"; -import BlueButton from "../../components/PlayQuiz/BlueButton"; +import BlueButton from "../../components/buttons/BlueButton"; import {Question} from "../../models/Question"; import {Answer} from "../../models/Answer"; import {Quiz} from "../../models/Quiz"; diff --git a/styles/ButtonsStyles.ts b/styles/ButtonsStyles.ts index 4f56435..5d33ddb 100644 --- a/styles/ButtonsStyles.ts +++ b/styles/ButtonsStyles.ts @@ -7,6 +7,13 @@ export const ButtonsStyles = StyleSheet.create( { justifyContent: 'center', borderRadius: 10, }, + whiteButton: { + backgroundColor: "#45128C", + padding: 10, + alignItems: "center", + justifyContent: 'center', + borderRadius: 10, + }, answerButton: { backgroundColor: "#F3F3F3", height: '15%', diff --git a/styles/TextsStyles.ts b/styles/TextsStyles.ts index 1ec37f0..84e6ba3 100644 --- a/styles/TextsStyles.ts +++ b/styles/TextsStyles.ts @@ -8,6 +8,10 @@ export const TextsStyles = StyleSheet.create( { color: "white", fontSize: 17, }, + whiteButtonText: { + color: "white", + fontSize: 17, + }, titleText: { fontSize: 24, fontWeight: "bold", -- GitLab From 5e8f377f4318cf80766afbc6d2464b8f92e265d6 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 19:11:48 +0100 Subject: [PATCH 170/283] feat: white button --- components/buttons/WhiteButton.tsx | 50 ++++++++++++++++++++++++++++++ screens/Home/HomeChild.tsx | 4 +++ 2 files changed, 54 insertions(+) create mode 100644 components/buttons/WhiteButton.tsx diff --git a/components/buttons/WhiteButton.tsx b/components/buttons/WhiteButton.tsx new file mode 100644 index 0000000..a702557 --- /dev/null +++ b/components/buttons/WhiteButton.tsx @@ -0,0 +1,50 @@ +import { View, Text, StyleSheet, TouchableOpacity, GestureResponderEvent } from "react-native"; + +interface Props { + onPress: ((event: GestureResponderEvent) => void) | undefined; + text: string; + buttonStyle?: any; + isDisabled: boolean; +} + +export default function WhiteButton({ onPress, text, buttonStyle, isDisabled }: Props) { + return ( + <TouchableOpacity + style={[ + styles.container, + isDisabled ? styles.disabledContainer : styles.enabledContainer, + buttonStyle, + ]} + onPress={onPress} + disabled={isDisabled} + > + <Text style={[styles.text, isDisabled && styles.disabledText]}>{text}</Text> + </TouchableOpacity> + ); +} + +const styles = StyleSheet.create({ + container: { + width: "100%", + height: 70, + alignItems: "center", + justifyContent: "center", + borderRadius: 10, + borderWidth: 5, + borderColor: "#508bff", + }, + enabledContainer: { + backgroundColor: "white", // Couleur bleue par défaut + }, + disabledContainer: { + backgroundColor: "#b0b0b0", // Gris désactivé + }, + text: { + fontSize: 28, + color: "#508bff", + fontWeight: "bold", + }, + disabledText: { + color: "#d0d0d0", // Texte grisé pour un bouton désactivé + }, +}); diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index fd9c7da..da7d22d 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -8,6 +8,8 @@ import MenuButton from "../../components/buttons/MenuButton"; import {NavigationProp} from "@react-navigation/native"; import {useQuizService} from "../../services/QuizService"; import HttpError from "../../services/HttpError"; +import BlueButton from "../../components/buttons/BlueButton"; +import WhiteButton from "../../components/buttons/WhiteButton"; interface Props { navigation: NavigationProp<any>; @@ -54,6 +56,8 @@ export default function HomeChild({navigation}: Props) { <View style={styles.buttonContainer}> <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonQuickGamePressed}/> <MenuButton text={"PLAY"} handleButtonPressed={handleButtonGeneratePressed}/> + <BlueButton onPress={()=>{}} text={"test"} isDisabled={false}/> + <WhiteButton onPress={()=>{}} text={"test"} isDisabled={false}/> {/*<MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/>*/} </View> <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> -- GitLab From af8c68a46567228b5b97889d00d906d64f075208 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 19:12:39 +0100 Subject: [PATCH 171/283] feat: testing compononents pushed --- screens/Home/HomeChild.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index da7d22d..fd9c7da 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -8,8 +8,6 @@ import MenuButton from "../../components/buttons/MenuButton"; import {NavigationProp} from "@react-navigation/native"; import {useQuizService} from "../../services/QuizService"; import HttpError from "../../services/HttpError"; -import BlueButton from "../../components/buttons/BlueButton"; -import WhiteButton from "../../components/buttons/WhiteButton"; interface Props { navigation: NavigationProp<any>; @@ -56,8 +54,6 @@ export default function HomeChild({navigation}: Props) { <View style={styles.buttonContainer}> <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonQuickGamePressed}/> <MenuButton text={"PLAY"} handleButtonPressed={handleButtonGeneratePressed}/> - <BlueButton onPress={()=>{}} text={"test"} isDisabled={false}/> - <WhiteButton onPress={()=>{}} text={"test"} isDisabled={false}/> {/*<MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/>*/} </View> <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> -- GitLab From bc9ee8b8263a4b97ebfcbc46f65dc0b8d0ec07f0 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 20:49:44 +0100 Subject: [PATCH 172/283] feat: indicator on answer button --- components/buttons/AnswerButton.tsx | 58 +++++++++++++++++++++++++ screens/PlayingQuiz/PlayingQuizBody.tsx | 4 +- 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 components/buttons/AnswerButton.tsx diff --git a/components/buttons/AnswerButton.tsx b/components/buttons/AnswerButton.tsx new file mode 100644 index 0000000..2cc1fb6 --- /dev/null +++ b/components/buttons/AnswerButton.tsx @@ -0,0 +1,58 @@ +import {TouchableOpacity, Text, StyleSheet, View} from "react-native"; +import {ButtonsStyles} from "../../styles/ButtonsStyles"; +import {TextsStyles} from "../../styles/TextsStyles"; + +interface Props{ + text: string; + handleButtonPressed:() => void; + buttonStyle?: any; + buttonText?: any; + indicator?: number | undefined; +} + +/** + * AnswerButton : Un bouton par défaut, il possède déjà un style mais on peut le surcharger grâce à la propriété buttonStyle + * @param text - Texte du bouton + * @param handleButtonPressed - Fonction qui sera exécutée lorsque le bouton sera pressé + * @param buttonStyle - Surcharge du style du bouton + * @param buttonText - Surcharge du style du texte du bouton + */ +export default function AnswerButton({text, handleButtonPressed, buttonStyle, buttonText, indicator}: Props){ + return( + <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> + {indicator !== undefined && ( + <View style={styles.indicator}> + <Text style={styles.indicatorText}>{indicator}</Text> + </View> + )} + <Text style={[TextsStyles.defaultButtonText, buttonText]}>{text}</Text> + </TouchableOpacity> + ) +} + +const styles = StyleSheet.create({ + defaultButton: { + backgroundColor: "#45128C", + padding: 10, + alignItems: "center", + justifyContent: 'center', + borderRadius: 10, + }, + indicator: { + position: "absolute", + top: "-16%", + left: "5%", + width: 25, + height: 25, + borderRadius: 15, + backgroundColor: "#D3D3D3", + justifyContent: "center", + alignItems: "center", + zIndex: 1, + }, + indicatorText: { + color: "#000", + fontWeight: "bold", + fontSize: 14, + }, +}) \ No newline at end of file diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index da6f1f6..ea8adb4 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -9,6 +9,7 @@ import BlueButton from "../../components/buttons/BlueButton"; import {Question} from "../../models/Question"; import {Answer} from "../../models/Answer"; import {Quiz} from "../../models/Quiz"; +import AnswerButton from "../../components/buttons/AnswerButton"; interface Props { quiz: Quiz; @@ -135,12 +136,13 @@ export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIs {currentQuestion ? ( <> {currentQuestion.answers.map((answer, index) => ( - <DefaultButton + <AnswerButton key={index} text={answer.text} handleButtonPressed={() => onAnsweredButtonClicked(answer.id)} buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)} buttonText={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)} + indicator={3} /> ))} {/* Ajout du PlayButton à la fin */} -- GitLab From ecc8d09827a0d909e82392dca7d9b3f22d144f2b Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 21:20:23 +0100 Subject: [PATCH 173/283] feat: testing code was pushed --- screens/PlayingQuiz/PlayingQuizBody.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index ea8adb4..5e96e78 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -142,7 +142,6 @@ export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIs handleButtonPressed={() => onAnsweredButtonClicked(answer.id)} buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)} buttonText={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)} - indicator={3} /> ))} {/* Ajout du PlayButton à la fin */} -- GitLab From 34dcf3dbc68a78c23961cc8060b14aad424c1914 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 5 Jan 2025 21:31:30 +0100 Subject: [PATCH 174/283] feat: progress bar --- components/progressBar/ProgressBar.tsx | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 components/progressBar/ProgressBar.tsx diff --git a/components/progressBar/ProgressBar.tsx b/components/progressBar/ProgressBar.tsx new file mode 100644 index 0000000..2908587 --- /dev/null +++ b/components/progressBar/ProgressBar.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { View, StyleSheet, Animated } from "react-native"; + +interface Props { + progress: number; // Valeur entre 0 et 1 représentant la progression +} + +export default function ProgressBar({ progress }: Props) { + // Limite la progression entre 0 et 1 + const clampedProgress = Math.min(Math.max(progress, 0), 1); + + // Calcul de la couleur en fonction de la progression + const interpolateColor = () => { + const red = Math.floor(255 * clampedProgress); + const blue = Math.floor(255 * (1 - clampedProgress)); + return `rgb(${red}, 0, ${blue})`; + }; + + return ( + <View style={styles.container}> + <Animated.View + style={[ + styles.progressBar, + { + width: `${clampedProgress * 100}%`, + backgroundColor: interpolateColor(), + }, + ]} + /> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + height: 30, // Épaisseur de la barre + width: "100%", + backgroundColor: "#e0e0e0", // Couleur de fond + borderRadius: 15, // Bords arrondis + overflow: "hidden", // Empêche le débordement + }, + progressBar: { + height: "100%", + borderRadius: 15, + }, +}); -- GitLab From 243f9bcd7a7200ca72fedefde8e72e038dd577ad Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Mon, 6 Jan 2025 20:23:46 +0100 Subject: [PATCH 175/283] feat: progress bar color --- components/progressBar/ProgressBar.tsx | 39 +++--- package-lock.json | 182 +++++++++++++++++++++++++ package.json | 1 + 3 files changed, 202 insertions(+), 20 deletions(-) diff --git a/components/progressBar/ProgressBar.tsx b/components/progressBar/ProgressBar.tsx index 2908587..38ad361 100644 --- a/components/progressBar/ProgressBar.tsx +++ b/components/progressBar/ProgressBar.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { View, StyleSheet, Animated } from "react-native"; +import { View, StyleSheet } from "react-native"; +import * as Progress from "react-native-progress"; interface Props { progress: number; // Valeur entre 0 et 1 représentant la progression @@ -11,21 +12,26 @@ export default function ProgressBar({ progress }: Props) { // Calcul de la couleur en fonction de la progression const interpolateColor = () => { - const red = Math.floor(255 * clampedProgress); - const blue = Math.floor(255 * (1 - clampedProgress)); - return `rgb(${red}, 0, ${blue})`; + const startColor = { r: 43, g: 115, b: 254 }; // #45128C (bleu-violet) + const endColor = { r: 255, g: 0, b: 0 }; // Rouge + + const r = Math.floor(startColor.r + (endColor.r - startColor.r) * clampedProgress); + const g = Math.floor(startColor.g + (endColor.g - startColor.g) * clampedProgress); + const b = Math.floor(startColor.b + (endColor.b - startColor.b) * clampedProgress); + + return `rgb(${r}, ${g}, ${b})`; }; return ( <View style={styles.container}> - <Animated.View - style={[ - styles.progressBar, - { - width: `${clampedProgress * 100}%`, - backgroundColor: interpolateColor(), - }, - ]} + <Progress.Bar + progress={clampedProgress} + width={null} // Occupe toute la largeur disponible + height={30} // Épaisseur de la barre + color={interpolateColor()} // Couleur dynamique + borderRadius={15} // Bords arrondis + unfilledColor="#e0e0e0" // Couleur de fond + borderWidth={0} // Supprime la bordure /> </View> ); @@ -33,14 +39,7 @@ export default function ProgressBar({ progress }: Props) { const styles = StyleSheet.create({ container: { - height: 30, // Épaisseur de la barre width: "100%", - backgroundColor: "#e0e0e0", // Couleur de fond - borderRadius: 15, // Bords arrondis - overflow: "hidden", // Empêche le débordement - }, - progressBar: { - height: "100%", - borderRadius: 15, + padding: 10, // Ajoute un espace autour de la barre }, }); diff --git a/package-lock.json b/package-lock.json index 34cf915..74090c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "react-i18next": "^15.1.1", "react-native": "0.76.3", "react-native-dropdown-select-list": "^2.0.5", + "react-native-progress": "^5.0.1", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", "react-native-vector-icons": "^10.2.0", @@ -4718,6 +4719,13 @@ "node": "*" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC", + "peer": true + }, "node_modules/bplist-creator": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz", @@ -5443,6 +5451,60 @@ "node": ">=8" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -5614,6 +5676,65 @@ "node": ">=8" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.1.tgz", + "integrity": "sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -5683,6 +5804,19 @@ "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-editor": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/env-editor/-/env-editor-0.4.2.tgz", @@ -7822,6 +7956,13 @@ "node": ">=0.10" } }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0", + "peer": true + }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -8635,6 +8776,19 @@ "node": ">=4" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", @@ -9533,6 +9687,18 @@ "integrity": "sha512-TepbcagQVUMB6nLuIlVU2ghRpQHAECOeZWe8K04ymW6NqbKbxuczZSDFfdCiABiiQ2dFD+8Dz65y4K7/uUEqGg==", "license": "MIT" }, + "node_modules/react-native-progress": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-native-progress/-/react-native-progress-5.0.1.tgz", + "integrity": "sha512-TYfJ4auAe5vubDma2yfFvt7ktSI+UCfysqJnkdHEcLXqAitRFOozgF/cLgN5VNi/iLdaf3ga1ETi2RF4jVZ/+g==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react-native-svg": "*" + } + }, "node_modules/react-native-safe-area-context": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.12.0.tgz", @@ -9557,6 +9723,22 @@ "react-native": "*" } }, + "node_modules/react-native-svg": { + "version": "15.10.1", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.10.1.tgz", + "integrity": "sha512-Hqz/doQciVFK/Df2v+wsW96oY5jxlta7rZ31KQYo78dlgvAHEaGr6paEOAMvlIruw7EHNQ0Vc1ZmJPJF2kfIPQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3", + "warn-once": "0.1.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-vector-icons": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.2.0.tgz", diff --git a/package.json b/package.json index b58c2ac..c2f6b34 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react-i18next": "^15.1.1", "react-native": "0.76.3", "react-native-dropdown-select-list": "^2.0.5", + "react-native-progress": "^5.0.1", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", "react-native-vector-icons": "^10.2.0", -- GitLab From 93f7c95c8e8bd421d9a400b9548892effa197315 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Mon, 6 Jan 2025 20:25:17 +0100 Subject: [PATCH 176/283] feat: progress bar height --- components/progressBar/ProgressBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/progressBar/ProgressBar.tsx b/components/progressBar/ProgressBar.tsx index 38ad361..fbf4cb5 100644 --- a/components/progressBar/ProgressBar.tsx +++ b/components/progressBar/ProgressBar.tsx @@ -27,7 +27,7 @@ export default function ProgressBar({ progress }: Props) { <Progress.Bar progress={clampedProgress} width={null} // Occupe toute la largeur disponible - height={30} // Épaisseur de la barre + height={25} // Épaisseur de la barre color={interpolateColor()} // Couleur dynamique borderRadius={15} // Bords arrondis unfilledColor="#e0e0e0" // Couleur de fond -- GitLab From 628883db40447f451e7275d998492650124da855 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Mon, 6 Jan 2025 20:41:19 +0100 Subject: [PATCH 177/283] refactor: sort multiplayer file like the global refactor --- routes/StackNavigator.tsx | 8 ++++---- screens/{ => Multiplayer}/Lobby/Lobby.tsx | 0 .../MultiplayerCommunity/MultiplayerCommunity.tsx | 8 ++++---- .../OngoingQuizzes/OngoingQuizzes.tsx | 8 ++++---- .../OnlineQuiz/OnlineCreateLobby.tsx | 8 ++++---- .../{ => Multiplayer}/OnlineQuiz/OnlinePlayQuiz.tsx | 12 ++++++------ screens/{ => Multiplayer}/OnlineQuiz/OnlineQuiz.tsx | 2 +- .../{ => Multiplayer}/OnlineQuiz/OnlineQuizChild.tsx | 2 +- .../OnlineQuiz/OnlineSelectMode.tsx | 2 +- 9 files changed, 25 insertions(+), 25 deletions(-) rename screens/{ => Multiplayer}/Lobby/Lobby.tsx (100%) rename screens/{ => Multiplayer}/MultiplayerCommunity/MultiplayerCommunity.tsx (92%) rename screens/{ => Multiplayer}/OngoingQuizzes/OngoingQuizzes.tsx (89%) rename screens/{ => Multiplayer}/OnlineQuiz/OnlineCreateLobby.tsx (93%) rename screens/{ => Multiplayer}/OnlineQuiz/OnlinePlayQuiz.tsx (88%) rename screens/{ => Multiplayer}/OnlineQuiz/OnlineQuiz.tsx (92%) rename screens/{ => Multiplayer}/OnlineQuiz/OnlineQuizChild.tsx (94%) rename screens/{ => Multiplayer}/OnlineQuiz/OnlineSelectMode.tsx (97%) diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index f81935e..2fbc28e 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -6,10 +6,10 @@ import EndQuiz from "../screens/PlayingQuiz/EndQuiz/EndQuiz"; import PlayingQuiz from "../screens/PlayingQuiz/PlayingQuiz"; import TabNavigator from "./TabNavigation"; import Multiplayer from "../screens/Multiplayer/Multiplayer"; -import MultiplayerCommunity from "../screens/MultiplayerCommunity/MultiplayerCommunity"; -import OnlineQuiz from "../screens/OnlineQuiz/OnlineQuiz"; -import OngoingQuizzes from "../screens/OngoingQuizzes/OngoingQuizzes"; -import Lobby from "../screens/Lobby/Lobby"; +import MultiplayerCommunity from "../screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity"; +import OnlineQuiz from "../screens/Multiplayer/OnlineQuiz/OnlineQuiz"; +import OngoingQuizzes from "../screens/Multiplayer/OngoingQuizzes/OngoingQuizzes"; +import Lobby from "../screens/Multiplayer/Lobby/Lobby"; import PlayQuiz from "../screens/Home/PlayQuiz/PlayQuiz"; import MyQuizzes from "../screens/Home/MyQuizzes/MyQuizzes"; import InformationsOfQuiz from "../screens/Community/InformationsOfQuiz/InformationsOfQuiz"; diff --git a/screens/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx similarity index 100% rename from screens/Lobby/Lobby.tsx rename to screens/Multiplayer/Lobby/Lobby.tsx diff --git a/screens/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx similarity index 92% rename from screens/MultiplayerCommunity/MultiplayerCommunity.tsx rename to screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx index cb9dcc8..8da6d0c 100644 --- a/screens/MultiplayerCommunity/MultiplayerCommunity.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -1,11 +1,11 @@ import {View, Text, Modal, TextInput} from "react-native"; -import TemplateQuizList from "../../templates/TemplateQuizList"; +import TemplateQuizList from "../../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; import React, {useState} from "react"; -import {useQuizService} from "../../services/QuizService"; -import HttpError from "../../services/HttpError"; +import {useQuizService} from "../../../services/QuizService"; +import HttpError from "../../../services/HttpError"; import {useInfiniteQuery} from "@tanstack/react-query"; -import { Quiz } from "../../models/Quiz"; +import { Quiz } from "../../../models/Quiz"; import DefaultButton from "../../components/DefaultButton"; interface Props { diff --git a/screens/OngoingQuizzes/OngoingQuizzes.tsx b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx similarity index 89% rename from screens/OngoingQuizzes/OngoingQuizzes.tsx rename to screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx index 64502b9..ca24520 100644 --- a/screens/OngoingQuizzes/OngoingQuizzes.tsx +++ b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx @@ -1,11 +1,11 @@ import {View, Text} from "react-native"; -import TemplateQuizList from "../../templates/TemplateQuizList"; +import TemplateQuizList from "../../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; import {useEffect, useState} from "react"; -import {useQuizService} from "../../services/QuizService"; -import HttpError from "../../services/HttpError"; +import {useQuizService} from "../../../services/QuizService"; +import HttpError from "../../../services/HttpError"; import {useInfiniteQuery} from "@tanstack/react-query"; -import { Quiz } from "../../models/Quiz"; +import { Quiz } from "../../../models/Quiz"; import DefaultButton from "../../components/DefaultButton"; interface Props { diff --git a/screens/OnlineQuiz/OnlineCreateLobby.tsx b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx similarity index 93% rename from screens/OnlineQuiz/OnlineCreateLobby.tsx rename to screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx index 95f7a94..62756f5 100644 --- a/screens/OnlineQuiz/OnlineCreateLobby.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx @@ -1,10 +1,10 @@ import { Text,View, StyleSheet } from "react-native"; -import SelectListBox from "../../components/PlayQuiz/SelectListBox"; +import SelectListBox from "../../../components/PlayQuiz/SelectListBox"; import React, {useEffect} from "react"; -import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; +import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import BlueButton from "../../components/PlayQuiz/BlueButton"; -import {getIdOfCategory} from "../../helper/QuizHelper"; -import {useQuizService} from "../../services/QuizService"; +import {getIdOfCategory} from "../../../helper/QuizHelper"; +import {useQuizService} from "../../../services/QuizService"; import {NavigationProp} from "@react-navigation/native"; const difficulty = [ diff --git a/screens/OnlineQuiz/OnlinePlayQuiz.tsx b/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx similarity index 88% rename from screens/OnlineQuiz/OnlinePlayQuiz.tsx rename to screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx index 48f212f..958dd33 100644 --- a/screens/OnlineQuiz/OnlinePlayQuiz.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx @@ -1,12 +1,12 @@ import { StyleSheet, View, Text } from "react-native"; -import AboutAQuiz from "../../components/PlayQuiz/AboutAQuiz"; +import AboutAQuiz from "../../../components/PlayQuiz/AboutAQuiz"; import BlueButton from "../../components/PlayQuiz/BlueButton"; import React, { useEffect } from "react"; -import InputPlayQuiz from "../../components/PlayQuiz/InputPlayQuiz"; -import {Quiz} from "../../models/Quiz"; -import { useQuizService } from "../../services/QuizService"; -import HttpError from "../../services/HttpError"; -import {getIdOfCategory} from "../../helper/QuizHelper"; +import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; +import {Quiz} from "../../../models/Quiz"; +import { useQuizService } from "../../../services/QuizService"; +import HttpError from "../../../services/HttpError"; +import {getIdOfCategory} from "../../../helper/QuizHelper"; import {NavigationProp} from "@react-navigation/native"; interface Props { diff --git a/screens/OnlineQuiz/OnlineQuiz.tsx b/screens/Multiplayer/OnlineQuiz/OnlineQuiz.tsx similarity index 92% rename from screens/OnlineQuiz/OnlineQuiz.tsx rename to screens/Multiplayer/OnlineQuiz/OnlineQuiz.tsx index 76c45c3..7b3ce82 100644 --- a/screens/OnlineQuiz/OnlineQuiz.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineQuiz.tsx @@ -1,6 +1,6 @@ import { NavigationProp } from "@react-navigation/native"; import { View, StyleSheet } from "react-native"; -import TemplateMenu from "../../templates/TemplateMenu"; +import TemplateMenu from "../../../templates/TemplateMenu"; import OnlineQuizChild from "./OnlineQuizChild"; interface Props{ diff --git a/screens/OnlineQuiz/OnlineQuizChild.tsx b/screens/Multiplayer/OnlineQuiz/OnlineQuizChild.tsx similarity index 94% rename from screens/OnlineQuiz/OnlineQuizChild.tsx rename to screens/Multiplayer/OnlineQuiz/OnlineQuizChild.tsx index 15c561b..ce6258b 100644 --- a/screens/OnlineQuiz/OnlineQuizChild.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineQuizChild.tsx @@ -1,6 +1,6 @@ import { NavigationProp } from "@react-navigation/native"; import { useState } from "react"; -import { OnlineSelectModeType, SelectModeType } from "../../models/SelectModeType"; +import { OnlineSelectModeType, SelectModeType } from "../../../models/SelectModeType"; import { TouchableWithoutFeedback, View, StyleSheet, Keyboard } from "react-native"; import OnlineSelectMode from "./OnlineSelectMode"; import OnlineCreateLobby from "./OnlineCreateLobby"; diff --git a/screens/OnlineQuiz/OnlineSelectMode.tsx b/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx similarity index 97% rename from screens/OnlineQuiz/OnlineSelectMode.tsx rename to screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx index 8275f69..0f27af3 100644 --- a/screens/OnlineQuiz/OnlineSelectMode.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx @@ -1,5 +1,5 @@ import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; -import { OnlineSelectModeType } from "../../models/SelectModeType"; +import { OnlineSelectModeType } from "../../../models/SelectModeType"; interface Props { mode: OnlineSelectModeType; -- GitLab From 60a434d99111f1dbeed10b45fa870dc74f3bb9b4 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Mon, 6 Jan 2025 20:47:10 +0100 Subject: [PATCH 178/283] refactor: replace default button by Blue/white Button --- .../MultiplayerCommunity/MultiplayerCommunity.tsx | 10 ++++++---- screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx | 5 +++-- screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx | 2 +- screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx | 4 ++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx index 8da6d0c..555dadb 100644 --- a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -6,7 +6,9 @@ import {useQuizService} from "../../../services/QuizService"; import HttpError from "../../../services/HttpError"; import {useInfiniteQuery} from "@tanstack/react-query"; import { Quiz } from "../../../models/Quiz"; -import DefaultButton from "../../components/DefaultButton"; +import WhiteButton from "../../../components/buttons/WhiteButton"; +import BlueButton from "../../../components/buttons/BlueButton"; +// import DefaultButton from "../../components/DefaultButton"; interface Props { navigation: NavigationProp<any> @@ -75,9 +77,9 @@ export default function MultiplayerCommunity({navigation}: Props) { <View> <Text>CREATE A LOBBY</Text> <Text>NUMBER OF PLAYER</Text> - <TextInput></TextInput> - <DefaultButton text="CLOSE" handleButtonPressed={onClosePressed}/> - <DefaultButton text="PLAY" handleButtonPressed={onPlayPressed}/> + <TextInput/> + <WhiteButton text="CLOSE" onPress={onClosePressed} isDisabled={false}/> + <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> </View> </View> </Modal> diff --git a/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx index ca24520..89dee2a 100644 --- a/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx +++ b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx @@ -6,7 +6,8 @@ import {useQuizService} from "../../../services/QuizService"; import HttpError from "../../../services/HttpError"; import {useInfiniteQuery} from "@tanstack/react-query"; import { Quiz } from "../../../models/Quiz"; -import DefaultButton from "../../components/DefaultButton"; +import BlueButton from "../../../components/buttons/BlueButton"; +// import DefaultButton from "../../components/DefaultButton"; interface Props { navigation: NavigationProp<any> @@ -53,7 +54,7 @@ export default function Community({navigation}: Props) { return ( <View> <TemplateQuizList title={"Ongoing Quizzes"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> - <DefaultButton text="JOIN" handleButtonPressed={onJoinPressed}/> + <BlueButton text="JOIN" onPress={onJoinPressed} isDisabled={false}/> </View> ); } \ No newline at end of file diff --git a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx index 62756f5..b59c626 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx @@ -2,10 +2,10 @@ import { Text,View, StyleSheet } from "react-native"; import SelectListBox from "../../../components/PlayQuiz/SelectListBox"; import React, {useEffect} from "react"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; -import BlueButton from "../../components/PlayQuiz/BlueButton"; import {getIdOfCategory} from "../../../helper/QuizHelper"; import {useQuizService} from "../../../services/QuizService"; import {NavigationProp} from "@react-navigation/native"; +import BlueButton from "../../../components/buttons/BlueButton"; const difficulty = [ { key: "easy", value: "easy" }, diff --git a/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx b/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx index 958dd33..d298d8c 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx @@ -1,13 +1,13 @@ import { StyleSheet, View, Text } from "react-native"; import AboutAQuiz from "../../../components/PlayQuiz/AboutAQuiz"; -import BlueButton from "../../components/PlayQuiz/BlueButton"; import React, { useEffect } from "react"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import {Quiz} from "../../../models/Quiz"; import { useQuizService } from "../../../services/QuizService"; import HttpError from "../../../services/HttpError"; -import {getIdOfCategory} from "../../../helper/QuizHelper"; +// import {getIdOfCategory} from "../../../helper/QuizHelper"; import {NavigationProp} from "@react-navigation/native"; +import BlueButton from "../../../components/buttons/BlueButton"; interface Props { navigation: NavigationProp<any>; -- GitLab From 9b2b2dad7c71792d92caef55bb435de300053da1 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Mon, 6 Jan 2025 21:02:07 +0100 Subject: [PATCH 179/283] feat: add button on multiplayer community page --- .../MultiplayerCommunity.tsx | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx index 555dadb..5f3a18f 100644 --- a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -1,4 +1,4 @@ -import {View, Text, Modal, TextInput} from "react-native"; +import {View, Text, Modal, TextInput, StyleSheet} from "react-native"; import TemplateQuizList from "../../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; import React, {useState} from "react"; @@ -8,7 +8,6 @@ import {useInfiniteQuery} from "@tanstack/react-query"; import { Quiz } from "../../../models/Quiz"; import WhiteButton from "../../../components/buttons/WhiteButton"; import BlueButton from "../../../components/buttons/BlueButton"; -// import DefaultButton from "../../components/DefaultButton"; interface Props { navigation: NavigationProp<any> @@ -69,9 +68,18 @@ export default function MultiplayerCommunity({navigation}: Props) { return allPages.length * 7; }}); + const onCreateLobbyPressed = () => { + setShowModal(true); + }; + return ( - <View> - <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> + <View style={styles.containerGlobal}> + <View style={styles.templateQuizListContainer}> + <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> + </View> + <View style={styles.buttonContainer}> + <BlueButton text="CREATE LOBBY" onPress={onCreateLobbyPressed} isDisabled={false}/> + </View> <Modal visible={showModal}> <View> <View> @@ -85,4 +93,21 @@ export default function MultiplayerCommunity({navigation}: Props) { </Modal> </View> ); -} \ No newline at end of file +} + +const styles = StyleSheet.create({ + containerGlobal: { + display: 'flex', + width: '100%', + height: '100%', + flexDirection: 'column', + }, + templateQuizListContainer: { + height: '90%', + }, + buttonContainer: { + height: '10%', + width: '95%', + marginLeft: '2.5%', + } +}); \ No newline at end of file -- GitLab From f9a3f877d29bdb4694f738ebfb36034b1dc8d879 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Mon, 6 Jan 2025 21:05:15 +0100 Subject: [PATCH 180/283] feat: modify action when quiz is pressed --- .../MultiplayerCommunity/MultiplayerCommunity.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx index 5f3a18f..e890761 100644 --- a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -17,6 +17,7 @@ export default function MultiplayerCommunity({navigation}: Props) { const [input, setInput] = useState<string>("") const [loading, setLoading] = useState<boolean>(false) const [showModal, setShowModal] = useState<boolean>(false); + const [quiz, setQuiz] = useState<Quiz | null>(null); const fetchCommunityQuizzes = async ({pageParam = 1}) => { const quizzes = await getCommunityQuiz(pageParam,7); @@ -27,10 +28,14 @@ export default function MultiplayerCommunity({navigation}: Props) { return quizzes; } - const onQuizPressed = (quiz: Quiz) => { + const onCreateLobbyPressed = () => { setShowModal(true); }; + const onQuizPressed = (quiz: Quiz) => { + setQuiz(quiz); + }; + const onClosePressed = () => { setShowModal(false); }; @@ -68,9 +73,7 @@ export default function MultiplayerCommunity({navigation}: Props) { return allPages.length * 7; }}); - const onCreateLobbyPressed = () => { - setShowModal(true); - }; + return ( <View style={styles.containerGlobal}> -- GitLab From d4aed3bd7449aa4c975bbba9d7c72f7aabd7bf6c Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Mon, 6 Jan 2025 21:13:35 +0100 Subject: [PATCH 181/283] feat: create modal create lobby --- components/Multiplayer/ModalCreateLobby.tsx | 25 +++++++++++++++++++ .../MultiplayerCommunity.tsx | 13 ++-------- 2 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 components/Multiplayer/ModalCreateLobby.tsx diff --git a/components/Multiplayer/ModalCreateLobby.tsx b/components/Multiplayer/ModalCreateLobby.tsx new file mode 100644 index 0000000..f55e56e --- /dev/null +++ b/components/Multiplayer/ModalCreateLobby.tsx @@ -0,0 +1,25 @@ +import { Modal, View, Text, TextInput } from "react-native"; +import WhiteButton from "../buttons/WhiteButton"; +import BlueButton from "../buttons/BlueButton"; + +interface Props { + showModal: boolean; + onClosePressed: () => void; + onPlayPressed: () => void; +} + +export default function ModalCreateLobby({showModal, onClosePressed, onPlayPressed}: Props) { + return ( + <Modal visible={showModal}> + <View> + <View> + <Text>CREATE A LOBBY</Text> + <Text>NUMBER OF PLAYER</Text> + <TextInput/> + <WhiteButton text="CLOSE" onPress={onClosePressed} isDisabled={false}/> + <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> + </View> + </View> + </Modal> + ); +} \ No newline at end of file diff --git a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx index e890761..decdc8b 100644 --- a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -8,6 +8,7 @@ import {useInfiniteQuery} from "@tanstack/react-query"; import { Quiz } from "../../../models/Quiz"; import WhiteButton from "../../../components/buttons/WhiteButton"; import BlueButton from "../../../components/buttons/BlueButton"; +import ModalCreateLobby from "../../../components/Multiplayer/ModalCreateLobby"; interface Props { navigation: NavigationProp<any> @@ -83,17 +84,7 @@ export default function MultiplayerCommunity({navigation}: Props) { <View style={styles.buttonContainer}> <BlueButton text="CREATE LOBBY" onPress={onCreateLobbyPressed} isDisabled={false}/> </View> - <Modal visible={showModal}> - <View> - <View> - <Text>CREATE A LOBBY</Text> - <Text>NUMBER OF PLAYER</Text> - <TextInput/> - <WhiteButton text="CLOSE" onPress={onClosePressed} isDisabled={false}/> - <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> - </View> - </View> - </Modal> + <ModalCreateLobby onClosePressed={onClosePressed} onPlayPressed={onPlayPressed} showModal={showModal}/> </View> ); } -- GitLab From ab06b58c8f21e61fb79b9661ab09fbc436415117 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Mon, 6 Jan 2025 21:48:52 +0100 Subject: [PATCH 182/283] style: add style for modal create lobby --- components/Multiplayer/ModalCreateLobby.tsx | 92 ++++++++++++++++++--- 1 file changed, 82 insertions(+), 10 deletions(-) diff --git a/components/Multiplayer/ModalCreateLobby.tsx b/components/Multiplayer/ModalCreateLobby.tsx index f55e56e..7b084a1 100644 --- a/components/Multiplayer/ModalCreateLobby.tsx +++ b/components/Multiplayer/ModalCreateLobby.tsx @@ -1,6 +1,8 @@ -import { Modal, View, Text, TextInput } from "react-native"; +import { Modal, View, Text, TextInput, StyleSheet } from "react-native"; import WhiteButton from "../buttons/WhiteButton"; import BlueButton from "../buttons/BlueButton"; +import babelConfig from "../../babel.config"; +import DefaultButton from "../buttons/DefaultButton"; interface Props { showModal: boolean; @@ -10,16 +12,86 @@ interface Props { export default function ModalCreateLobby({showModal, onClosePressed, onPlayPressed}: Props) { return ( - <Modal visible={showModal}> - <View> - <View> - <Text>CREATE A LOBBY</Text> - <Text>NUMBER OF PLAYER</Text> - <TextInput/> - <WhiteButton text="CLOSE" onPress={onClosePressed} isDisabled={false}/> - <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> + <Modal visible={showModal} animationType="slide" transparent={true}> + <View style={styles.centeredView}> + <View style={styles.modalView}> + <Text style={styles.title}>CREATE A LOBBY</Text> + <View style={styles.inputGroup}> + <Text>NUMBER OF PLAYER</Text> + <TextInput style={styles.input}/> + </View> + <View style={styles.buttonGroup}> + <View style={styles.button}> + <WhiteButton text="CLOSE" onPress={onClosePressed} isDisabled={false}/> + </View> + <View style={styles.button}> + <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> + </View> + </View> </View> </View> </Modal> ); -} \ No newline at end of file +} + +const styles = StyleSheet.create({ + centeredView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.5)' + }, + modalView: { + display: 'flex', + justifyContent: 'space-between', + height: '50%', + width: '90%', + margin: '10%', + padding: '5%', + backgroundColor: 'white', + borderRadius: 20, + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.25, + shadowRadius: 4, + elevation: 5, + }, + buttonGroup: { + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%', + }, + button: { + width: '45%', + }, + title: { + fontSize: 40, + fontWeight: "bold", + color: "#00b3f4", + marginBottom: 10, + alignSelf: "center", // Centre uniquement le titre + textAlign: 'center', + }, + inputGroup: { + width: '100%', + marginBottom: 20, + }, + input: { + width: "100%", + height: 70, + backgroundColor: "white", + borderColor: "#2ec7ff", + borderWidth: 3, + borderRadius: 8, + paddingLeft: 10, + elevation: 5, + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + }, +}); \ No newline at end of file -- GitLab From c55978fd13cc60b4d027eef4e69a6f01a77c072d Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Mon, 6 Jan 2025 22:03:13 +0100 Subject: [PATCH 183/283] feat: action on play pressed --- components/Multiplayer/ModalCreateLobby.tsx | 10 +++++----- .../MultiplayerCommunity.tsx | 20 ++++--------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/components/Multiplayer/ModalCreateLobby.tsx b/components/Multiplayer/ModalCreateLobby.tsx index 7b084a1..f87b518 100644 --- a/components/Multiplayer/ModalCreateLobby.tsx +++ b/components/Multiplayer/ModalCreateLobby.tsx @@ -1,16 +1,16 @@ import { Modal, View, Text, TextInput, StyleSheet } from "react-native"; import WhiteButton from "../buttons/WhiteButton"; import BlueButton from "../buttons/BlueButton"; -import babelConfig from "../../babel.config"; -import DefaultButton from "../buttons/DefaultButton"; +import { useState } from "react"; interface Props { showModal: boolean; onClosePressed: () => void; - onPlayPressed: () => void; + onPlayPressed: (nbPlayer: number) => void; } export default function ModalCreateLobby({showModal, onClosePressed, onPlayPressed}: Props) { + const [nbPlayer, setNbPlayer] = useState<number>(1); return ( <Modal visible={showModal} animationType="slide" transparent={true}> <View style={styles.centeredView}> @@ -18,14 +18,14 @@ export default function ModalCreateLobby({showModal, onClosePressed, onPlayPress <Text style={styles.title}>CREATE A LOBBY</Text> <View style={styles.inputGroup}> <Text>NUMBER OF PLAYER</Text> - <TextInput style={styles.input}/> + <TextInput style={styles.input} defaultValue={nbPlayer.toString()} onChangeText={(text) => setNbPlayer(Number(text))}/> </View> <View style={styles.buttonGroup}> <View style={styles.button}> <WhiteButton text="CLOSE" onPress={onClosePressed} isDisabled={false}/> </View> <View style={styles.button}> - <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> + <BlueButton text="PLAY" onPress={() => onPlayPressed(nbPlayer)} isDisabled={false}/> </View> </View> </View> diff --git a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx index decdc8b..8d7723a 100644 --- a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -21,7 +21,7 @@ export default function MultiplayerCommunity({navigation}: Props) { const [quiz, setQuiz] = useState<Quiz | null>(null); const fetchCommunityQuizzes = async ({pageParam = 1}) => { - const quizzes = await getCommunityQuiz(pageParam,7); + const quizzes = await getCommunityQuiz(pageParam,7);//TODO: changer pour get les quiz community public if (HttpError.isHttpError(quizzes)) { console.error("Error while fetching community quizzes:", quizzes); return []; @@ -41,24 +41,12 @@ export default function MultiplayerCommunity({navigation}: Props) { setShowModal(false); }; - const onPlayPressed = async () => { + const onPlayPressed = async (nbPlayer: number) => { setShowModal(false); - const quizData = await getRandomQuiz() //TODO: change the quiz who's getting played - if (quizData instanceof HttpError) { - console.error("Error while fetching random quiz:", quizData); - return; - } - navigation.reset({ - index: 0, - routes: [ - { - name: "PlayingQuiz", - params: {quizRecovered: quizData}, - }, - ] - }); + navigation.navigate("Lobby", {quiz: quiz, nbPlayer: nbPlayer}); }; + const { data, fetchNextPage, -- GitLab From 53ca5bc3c17940b919f7af39494ee65282916fc6 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Mon, 6 Jan 2025 22:07:13 +0100 Subject: [PATCH 184/283] style: change color of active page --- screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx b/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx index 0f27af3..59ec2c2 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx @@ -57,7 +57,7 @@ const styles = StyleSheet.create({ color: '#bebebe', }, activeText: { - color: '#2b73fe', + color: '#2ec7ff', }, bar: { width: '100%', @@ -66,7 +66,7 @@ const styles = StyleSheet.create({ borderRadius: 10, }, activeBar: { - backgroundColor: '#2b73fe', // Couleur pour le bouton actif + backgroundColor: '#2ec7ff', // Couleur pour le bouton actif }, inactiveBar: { backgroundColor: '#bebebe', // Couleur grise pour les boutons inactifs -- GitLab From e1a535f6ce44998434d2f19b0f1bb5af20693aaf Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Mon, 6 Jan 2025 22:13:12 +0100 Subject: [PATCH 185/283] feat: add number of player to create lobby form --- screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx index b59c626..177d4b6 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx @@ -37,6 +37,7 @@ export default function OnlineCreateLobby({navigation}: Props) { const [difficutlyChoose, setDifficutlyChoose] = React.useState(""); const [categoryChoose, setCategoryChoose] = React.useState(""); const [nbQuestions, setNbQuestions] = React.useState("10"); + const [nbPlayers, setNbPlayers] = React.useState("2"); const [buttonIsDisabled, setButtonIsDisabled] = React.useState(true); const {generateQuiz} = useQuizService(); @@ -58,8 +59,8 @@ export default function OnlineCreateLobby({navigation}: Props) { index: 0, routes: [ { - name: "PlayingQuiz", - params: {quizRecovered: quizGenerated}, + name: "Lobby", + params: {quizGenerated: quizGenerated, nbPlayers: parseInt(nbPlayers)}, }, ] }); @@ -71,7 +72,8 @@ export default function OnlineCreateLobby({navigation}: Props) { <View style={styles.fieldContainer}> <SelectListBox title="CHOOSE A DIFFICULTY" data={difficulty} setSelected={setDifficutlyChoose} /> <SelectListBox title="CHOOSE A CATEGORY" data={categories} setSelected={setCategoryChoose} /> - <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={setNbQuestions} numeric={true} defaultValue={nbQuestions}/> + <InputPlayQuiz title="NUMBER OF QUESTIONS" setText={setNbQuestions} numeric={true} defaultValue={nbQuestions}/> + <InputPlayQuiz title="NUMBER OF PLAYERS" setText={setNbPlayers} numeric={true} defaultValue={nbPlayers}/> </View> <View style={styles.buttonPlayContainer}> <BlueButton onPress={handlePlayButtonPress} text={"CREATE LOBBY"} isDisabled={buttonIsDisabled}/> -- GitLab From cb22560531b9ebce0c22f7d0ed25347e555a8b42 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Mon, 6 Jan 2025 22:22:22 +0100 Subject: [PATCH 186/283] feat: add button to ongoinQuizzes --- .../OngoingQuizzes/OngoingQuizzes.tsx | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx index 89dee2a..ace408c 100644 --- a/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx +++ b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx @@ -1,4 +1,4 @@ -import {View, Text} from "react-native"; +import {View, StyleSheet} from "react-native"; import TemplateQuizList from "../../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; import {useEffect, useState} from "react"; @@ -7,7 +7,6 @@ import HttpError from "../../../services/HttpError"; import {useInfiniteQuery} from "@tanstack/react-query"; import { Quiz } from "../../../models/Quiz"; import BlueButton from "../../../components/buttons/BlueButton"; -// import DefaultButton from "../../components/DefaultButton"; interface Props { navigation: NavigationProp<any> @@ -35,7 +34,7 @@ export default function Community({navigation}: Props) { navigation.goBack(); }; - + //TODO: changer la fonction de fetch const { data, fetchNextPage, @@ -53,8 +52,29 @@ export default function Community({navigation}: Props) { return ( <View> - <TemplateQuizList title={"Ongoing Quizzes"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> - <BlueButton text="JOIN" onPress={onJoinPressed} isDisabled={false}/> + <View style={styles.templateQuizListContainer}> + <TemplateQuizList title={"Ongoing Quizzes"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> + </View> + <View style={styles.buttonContainer}> + <BlueButton text="JOIN" onPress={onJoinPressed} isDisabled={false}/> + </View> </View> ); -} \ No newline at end of file +} + +const styles = StyleSheet.create({ + containerGlobal: { + display: 'flex', + width: '100%', + height: '100%', + flexDirection: 'column', + }, + templateQuizListContainer: { + height: '90%', + }, + buttonContainer: { + height: '10%', + width: '95%', + marginLeft: '2.5%', + } +}); \ No newline at end of file -- GitLab From 3bd1174f9dffdb739c3a8b9604a5ca578f391e42 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 7 Jan 2025 17:37:44 +0100 Subject: [PATCH 187/283] chore: rename file --- .../PlayQuiz/{SelectListBox.tsx => SelectListBoxString.tsx} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename components/PlayQuiz/{SelectListBox.tsx => SelectListBoxString.tsx} (96%) diff --git a/components/PlayQuiz/SelectListBox.tsx b/components/PlayQuiz/SelectListBoxString.tsx similarity index 96% rename from components/PlayQuiz/SelectListBox.tsx rename to components/PlayQuiz/SelectListBoxString.tsx index 12612f6..5bca1d5 100644 --- a/components/PlayQuiz/SelectListBox.tsx +++ b/components/PlayQuiz/SelectListBoxString.tsx @@ -9,7 +9,7 @@ interface Props { } -export default function SelectListBox({data, setSelected, title} : Props) { +export default function SelectListBoxString({data, setSelected, title} : Props) { return ( <View style={styles.container}> -- GitLab From 961f4903b69e7ecb619ae449b08cc22f0854b2c0 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 7 Jan 2025 17:51:07 +0100 Subject: [PATCH 188/283] feat: adding service to fetch categories --- models/Quiz.ts | 2 +- services/QuizService.ts | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/models/Quiz.ts b/models/Quiz.ts index d02f93a..b5087c2 100644 --- a/models/Quiz.ts +++ b/models/Quiz.ts @@ -5,7 +5,7 @@ interface Difficulty { name: string; } -interface Category { +export interface Category { id: number; name: string; } diff --git a/services/QuizService.ts b/services/QuizService.ts index 17a2475..28e66bb 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -166,6 +166,23 @@ export const useQuizService = () => { } } + const getCategories = async () => { + try { + const response = await axios.get(`${url}/quiz/categories`, { + headers: { + "Content-Type": "application/json", + }, + }); + return response.data; + } catch (error: any) { + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + }; + return { generateQuiz: generateQuiz, @@ -173,6 +190,7 @@ export const useQuizService = () => { remainingQuiz: remainingQuiz, getRandomQuiz: getRandomQuiz, getCommunityQuiz: getCommunityQuiz, - restartQuiz: restartQuiz + restartQuiz: restartQuiz, + getCategories: getCategories } } \ No newline at end of file -- GitLab From 8a2e34341bfcd742e6b266c2fc27f5692bdf189e Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 7 Jan 2025 17:56:54 +0100 Subject: [PATCH 189/283] feat: adding SelectListBoxCategory --- components/PlayQuiz/SelectListBoxCategory.tsx | 84 +++++++++++++++++++ .../Home/PlayQuiz/PlayQuizGenerateQuiz.tsx | 34 ++++++-- 2 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 components/PlayQuiz/SelectListBoxCategory.tsx diff --git a/components/PlayQuiz/SelectListBoxCategory.tsx b/components/PlayQuiz/SelectListBoxCategory.tsx new file mode 100644 index 0000000..95715e1 --- /dev/null +++ b/components/PlayQuiz/SelectListBoxCategory.tsx @@ -0,0 +1,84 @@ +import { View, StyleSheet, Text } from "react-native"; +import { SelectList } from "react-native-dropdown-select-list"; +import React from "react"; +import { Category } from "../../models/Quiz"; + +interface Props { + data: Category[], // La liste des catégories + setSelected: (val: Category) => void, // Fonction pour gérer la sélection d'une catégorie + title: string, // Titre affiché au-dessus de la liste +} + +export default function SelectListBoxCategory({ data, setSelected, title }: Props) { + // Transformer les données dans le format attendu + const transformedData = data.map((category) => ({ + key: category.id.toString(), // Sauvegarde l'ID en tant que clé + value: category.name, // Affiche le nom + })); + + return ( + <View style={styles.container}> + <View style={styles.dropdownContainer}> + <Text style={styles.title}>{title}</Text> + <SelectList + setSelected={(key: string) => { + // Trouver la catégorie sélectionnée à partir de son ID + const selectedCategory = data.find((category) => category.id.toString() === key); + if (selectedCategory) { + setSelected(selectedCategory); + } + }} + data={transformedData} // Données transformées pour SelectList + save="key" // Sauvegarde la clé (ID) + dropdownStyles={styles.dropdownStyles} + boxStyles={styles.boxStyles} + inputStyles={styles.inputStyles} + /> + </View> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + alignItems: "center", + width: "100%", + }, + dropdownContainer: { + width: "100%", + }, + title: { + fontSize: 20, + fontWeight: "bold", + marginBottom: 5, + color: "#333", + textAlign: "left", + }, + dropdownStyles: { + position: "absolute", + width: "100%", + top: 40, + zIndex: 2, + backgroundColor: "white", + borderColor: "black", + borderWidth: 1, + borderRadius: 8, + }, + boxStyles: { + backgroundColor: "white", + borderColor: "#bebebe", + borderWidth: 2, + borderRadius: 8, + height: 70, + alignItems: "center", + elevation: 5, + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + }, + inputStyles: { + fontSize: 18, + color: "black", + }, +}); diff --git a/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx b/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx index 481b82f..c55cf39 100644 --- a/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx +++ b/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx @@ -1,11 +1,12 @@ import { Text,View, StyleSheet } from "react-native"; -import SelectListBox from "../../../components/PlayQuiz/SelectListBox"; -import React, {useEffect} from "react"; +import SelectListBoxString from "../../../components/PlayQuiz/SelectListBoxString"; +import React, {useEffect, useState} from "react"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import BlueButton from "../../../components/buttons/BlueButton"; -import {getIdOfCategory} from "../../../helper/QuizHelper"; import {useQuizService} from "../../../services/QuizService"; import {NavigationProp} from "@react-navigation/native"; +import {Category} from "../../../models/Quiz"; +import SelectListBoxCategory from "../../../components/PlayQuiz/SelectListBoxCategory"; const difficulty = [ { key: "easy", value: "easy" }, @@ -35,13 +36,28 @@ interface Props { export default function PlayQuizGenerateQuiz({navigation}: Props) { const [difficutlyChoose, setDifficutlyChoose] = React.useState(""); - const [categoryChoose, setCategoryChoose] = React.useState(""); const [nbQuestions, setNbQuestions] = React.useState("10"); const [buttonIsDisabled, setButtonIsDisabled] = React.useState(true); - const {generateQuiz} = useQuizService(); + const [categories, setCategories] = useState<Category[]>([]); + const [categoryChoose, setCategoryChoose] = React.useState<Category | null>(null); + + const {generateQuiz, getCategories} = useQuizService(); + const fetchingCategories = async () => { + const categories = await getCategories(); + console.log(categories); + let categoriesTemp: Category[] = []; + categories.map((category: Category) => { + categoriesTemp.push({'name': category.name, 'id': category.id}); + }); + setCategories(categoriesTemp); + } + + useEffect(() => { + fetchingCategories(); + }, []); useEffect(() => { - if(nbQuestions === "" || difficutlyChoose === "" || categoryChoose === "") + if(nbQuestions === "" || difficutlyChoose === "" || categoryChoose === null) { setButtonIsDisabled(true); } @@ -53,7 +69,7 @@ export default function PlayQuizGenerateQuiz({navigation}: Props) { }, [difficutlyChoose,categoryChoose,nbQuestions]); const handlePlayButtonPress = async () => { - const quizGenerated = await generateQuiz(parseInt(nbQuestions), getIdOfCategory(categoryChoose), difficutlyChoose); + const quizGenerated = await generateQuiz(parseInt(nbQuestions), categoryChoose ? categoryChoose.id : 10 , difficutlyChoose); navigation.reset({ index: 0, routes: [ @@ -69,8 +85,8 @@ export default function PlayQuizGenerateQuiz({navigation}: Props) { <View style={styles.container}> <View style={styles.contentContainer}> <View style={styles.fieldContainer}> - <SelectListBox title="CHOOSE A DIFFICULTY" data={difficulty} setSelected={setDifficutlyChoose} /> - <SelectListBox title="CHOOSE A CATEGORY" data={categories} setSelected={setCategoryChoose} /> + <SelectListBoxString title="CHOOSE A DIFFICULTY" data={difficulty} setSelected={setDifficutlyChoose} /> + <SelectListBoxCategory title="CHOOSE A CATEGORY" data={categories} setSelected={setCategoryChoose} /> <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={setNbQuestions} numeric={true} defaultValue={nbQuestions}/> </View> <View style={styles.buttonPlayContainer}> -- GitLab From fce7ded1c8dcd00070d498ce1432f76d1219b76c Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Tue, 7 Jan 2025 21:26:56 +0100 Subject: [PATCH 190/283] feat: add image for lobby --- assets/add.png | Bin 0 -> 1688 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/add.png diff --git a/assets/add.png b/assets/add.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca8aad752ac7e6f52cb5fd353a3955526ea037c GIT binary patch literal 1688 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 literal 0 HcmV?d00001 -- GitLab From 8d58a25e2f869ff086ce99393631cca4a34aad9c Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Tue, 7 Jan 2025 21:27:13 +0100 Subject: [PATCH 191/283] feat: add screen lobby --- screens/Multiplayer/Lobby/Lobby.tsx | 96 ++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 7 deletions(-) diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index 541bd56..24d9935 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -1,10 +1,60 @@ -import { View, Text, StyleSheet } from "react-native"; +import { View, Text, StyleSheet, Button, Image } from "react-native"; +import TemplateMenu from "../../../templates/TemplateMenu"; +import { NavigationProp } from "@react-navigation/native"; +import React, { useState } from "react"; +import WhiteButton from "../../../components/buttons/WhiteButton"; +import BlueButton from "../../../components/buttons/BlueButton"; +import { TouchableOpacity } from "react-native"; + +interface Props { + navigation: NavigationProp<any>; +} + +export default function Lobby({navigation}: Props) { + const [isHost, setIsHost] = useState<boolean>(true); + + const onLeavePressed = () => { + navigation.navigate("Multiplayer"); + }; + + const onPlayPressed = () => { + navigation.navigate("PlayingQuiz", {quizId: 1});//TODO : changer le quizId + } + + const onCancelPressed = () => { + navigation.navigate("Multiplayer"); + }; + + const onInvitePressed = () => { + //TODO : implementer l'invite + } -export default function Lobby() { return ( - <View style={styles.container}> - <Text style={styles.title}>Lobby</Text> - </View> + <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> + <Text style={styles.title}>{'BMW OFFICIAL QUIZ (I’m leaking oil)'}</Text> + <View style={styles.containerPlayers}> + <View style={styles.PlayerItem}> + <TouchableOpacity style={styles.buttonInvite} onPress={onInvitePressed}> + <Image source={require('../../../assets/add.png')} style={styles.imagePlayer}/> + </TouchableOpacity> + <Text style={styles.textPlayer}>Invite</Text> + </View> + </View> + {isHost ? + <View style={styles.buttonGroup}> + <View style={styles.button}> + <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> + </View> + <View style={styles.button}> + <WhiteButton text="CANCEL" onPress={onCancelPressed} isDisabled={false}/> + </View> + </View> + : + <View style={styles.buttonGroup}> + <WhiteButton text="LEAVE" onPress={onLeavePressed} isDisabled={false}/> + </View> + } + </TemplateMenu> ) } @@ -18,5 +68,37 @@ const styles = StyleSheet.create({ }, title: { fontSize: 40, - } -}); \ No newline at end of file + textAlign: 'center', + }, + containerPlayers: { + width: '100%', + height: '60%', + overflow: 'scroll', + }, + PlayerItem: { + width: '30%', + height: 'auto', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + buttonInvite: { + }, + imagePlayer: { + width: 100, + height: 100, + }, + textPlayer: { + fontSize: 20, + }, + buttonGroup: { + flexDirection: 'row', + justifyContent: 'space-evenly', + width: '100%', + }, + button: { + width: '45%', + }, + +}); -- GitLab From 654d97e8817c3be5c64a66d4b89bcd1fbcc0a1d1 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Tue, 7 Jan 2025 21:28:25 +0100 Subject: [PATCH 192/283] feat: join lobby --- screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx index ace408c..4b251f2 100644 --- a/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx +++ b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx @@ -31,7 +31,7 @@ export default function Community({navigation}: Props) { }; const onJoinPressed = () => { - navigation.goBack(); + navigation.navigate("Lobby"); }; //TODO: changer la fonction de fetch -- GitLab From f7d46b9494544d0d11afacd7e9b9ca96432ca174 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Tue, 7 Jan 2025 21:52:36 +0100 Subject: [PATCH 193/283] feat: add modal Invite Player --- components/Multiplayer/ModalInvitePlayer.tsx | 72 ++++++++++++++++++++ screens/Multiplayer/Lobby/Lobby.tsx | 57 +++++++++------- 2 files changed, 103 insertions(+), 26 deletions(-) create mode 100644 components/Multiplayer/ModalInvitePlayer.tsx diff --git a/components/Multiplayer/ModalInvitePlayer.tsx b/components/Multiplayer/ModalInvitePlayer.tsx new file mode 100644 index 0000000..dbf2294 --- /dev/null +++ b/components/Multiplayer/ModalInvitePlayer.tsx @@ -0,0 +1,72 @@ +import { Modal, View, Text, StyleSheet, Image } from "react-native"; +import WhiteButton from "../buttons/WhiteButton"; + +interface Props { + showModal: boolean; + onClosePressed: () => void; +} + +export default function ModalInvitePlayer({showModal, onClosePressed,}: Props) { + return ( + <Modal visible={showModal} animationType="slide" transparent={true}> + <View style={styles.centeredView}> + <View style={styles.modalView}> + <Text style={styles.title}>JOIN</Text> + <Image source={require('../../assets/favicon.png')}/> + <Text style={styles.or}>OR</Text> + <Text style={styles.link}>https://klebert-host.com/join_quiz/6975</Text> + <View style={styles.buttonGroup}> + <WhiteButton text="CLOSE" onPress={onClosePressed} isDisabled={false}/> + </View> + </View> + </View> + </Modal> + ); +} + +const styles = StyleSheet.create({ + centeredView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.5)' + }, + modalView: { + display: 'flex', + justifyContent: 'space-between', + height: '50%', + width: '90%', + margin: '10%', + padding: '5%', + backgroundColor: 'white', + borderRadius: 20, + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.25, + shadowRadius: 4, + elevation: 5, + }, + buttonGroup: { + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%', + }, + title: { + fontSize: 40, + fontWeight: "bold", + color: "#00b3f4", + marginBottom: 10, + alignSelf: "center", + textAlign: 'center', + }, + or: { + fontSize: 32, + }, + link: { + color: '#0000EE', + } +}); \ No newline at end of file diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index 24d9935..0a27500 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -5,6 +5,7 @@ import React, { useState } from "react"; import WhiteButton from "../../../components/buttons/WhiteButton"; import BlueButton from "../../../components/buttons/BlueButton"; import { TouchableOpacity } from "react-native"; +import ModalInvitePlayer from "../../../components/Multiplayer/ModalInvitePlayer"; interface Props { navigation: NavigationProp<any>; @@ -12,6 +13,7 @@ interface Props { export default function Lobby({navigation}: Props) { const [isHost, setIsHost] = useState<boolean>(true); + const [showModal, setShowModal] = useState<boolean>(false); const onLeavePressed = () => { navigation.navigate("Multiplayer"); @@ -19,42 +21,45 @@ export default function Lobby({navigation}: Props) { const onPlayPressed = () => { navigation.navigate("PlayingQuiz", {quizId: 1});//TODO : changer le quizId - } + }; const onCancelPressed = () => { navigation.navigate("Multiplayer"); }; const onInvitePressed = () => { - //TODO : implementer l'invite - } + setShowModal(true); + }; return ( - <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> - <Text style={styles.title}>{'BMW OFFICIAL QUIZ (I’m leaking oil)'}</Text> - <View style={styles.containerPlayers}> - <View style={styles.PlayerItem}> - <TouchableOpacity style={styles.buttonInvite} onPress={onInvitePressed}> - <Image source={require('../../../assets/add.png')} style={styles.imagePlayer}/> - </TouchableOpacity> - <Text style={styles.textPlayer}>Invite</Text> - </View> - </View> - {isHost ? - <View style={styles.buttonGroup}> - <View style={styles.button}> - <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> - </View> - <View style={styles.button}> - <WhiteButton text="CANCEL" onPress={onCancelPressed} isDisabled={false}/> + <View> + <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> + <Text style={styles.title}>{'BMW OFFICIAL QUIZ (I’m leaking oil)'}</Text> + <View style={styles.containerPlayers}> + <View style={styles.PlayerItem}> + <TouchableOpacity style={styles.buttonInvite} onPress={onInvitePressed}> + <Image source={require('../../../assets/add.png')} style={styles.imagePlayer}/> + </TouchableOpacity> + <Text style={styles.textPlayer}>Invite</Text> </View> </View> - : - <View style={styles.buttonGroup}> - <WhiteButton text="LEAVE" onPress={onLeavePressed} isDisabled={false}/> - </View> - } - </TemplateMenu> + {isHost ? + <View style={styles.buttonGroup}> + <View style={styles.button}> + <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> + </View> + <View style={styles.button}> + <WhiteButton text="CANCEL" onPress={onCancelPressed} isDisabled={false}/> + </View> + </View> + : + <View style={styles.buttonGroup}> + <WhiteButton text="LEAVE" onPress={onLeavePressed} isDisabled={false}/> + </View> + } + </TemplateMenu> + <ModalInvitePlayer showModal={showModal} onClosePressed={() => setShowModal(false)}/> + </View> ) } -- GitLab From 3994b731af6964a78de3bbee53e6193d12d0f142 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Tue, 7 Jan 2025 22:02:55 +0100 Subject: [PATCH 194/283] style: page lobby --- screens/Multiplayer/Lobby/Lobby.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index 0a27500..021f173 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -72,12 +72,14 @@ const styles = StyleSheet.create({ alignItems: "center", }, title: { - fontSize: 40, + fontSize: 32, textAlign: 'center', + color: '#00B3F4', + marginBottom: 20, }, containerPlayers: { width: '100%', - height: '60%', + height: '65%', overflow: 'scroll', }, PlayerItem: { -- GitLab From 25cbb79c11a9860d6be04e53e0e12ae76e053f0e Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 8 Jan 2025 10:18:52 +0100 Subject: [PATCH 195/283] chore: removed unused comments --- screens/PlayingQuiz/EndQuiz/EndQuiz.tsx | 75 ------------------------- 1 file changed, 75 deletions(-) diff --git a/screens/PlayingQuiz/EndQuiz/EndQuiz.tsx b/screens/PlayingQuiz/EndQuiz/EndQuiz.tsx index 8297ee0..2d567b3 100644 --- a/screens/PlayingQuiz/EndQuiz/EndQuiz.tsx +++ b/screens/PlayingQuiz/EndQuiz/EndQuiz.tsx @@ -35,78 +35,3 @@ const styles = StyleSheet.create({ flexDirection: 'column', }, }); - - - -// import DefaultButton from "../../components/DefaultButton"; -// import { TextsStyles } from "../../styles/TextsStyles"; -// import { ButtonsStyles } from "../../styles/ButtonsStyles"; -// import {useTranslation} from "react-i18next"; -// import QuizModel from "../../models/QuizModel"; -// import {NavigationProp, RouteProp} from "@react-navigation/native"; - -// type RoutePropsType = { -// EndQuiz: { -// quiz: QuizModel; -// }; -// }; - -// interface Props { -// navigation: NavigationProp<any>; -// route: RouteProp<RoutePropsType, "EndQuiz">; -// } - -// export default function EndQuiz({ navigation, route }: Props) { -// const { quiz } = route.params; -// const {t} = useTranslation(); -// const handleButtonRePlayPressed = () => { -// quiz.nbActualQuestion = 1; -// quiz.score = 0; -// navigation.reset({ -// index: 0, -// routes: [ -// { -// name: "PlayingQuiz", -// params: {quiz: quiz}, -// }, -// ] -// }); -// } - -// const handleButtonHomePressed = () => { -// navigation.navigate('Home'); -// } - -// return ( -// <View style={styles.container}> -// <Image source={require('../../assets/FondTemplate.png')} style={styles.image}/> -// <Text style={TextsStyles.titleText}>{t("app.screens.endQuiz.score")} : {quiz.score}</Text> -// <Text style={TextsStyles.subtitleText}>{((quiz.score/quiz.nbQuestions)*100 > 50 ) ? t("app.screens.endQuiz.lose") : t("app.screens.endQuiz.win")}</Text> -// <View style={styles.buttonContainer}> -// <DefaultButton text={t("app.screens.endQuiz.replay")} handleButtonPressed={handleButtonRePlayPressed} buttonStyle={ButtonsStyles.defaultButton}></DefaultButton> -// <DefaultButton text={t("app.screens.endQuiz.goHome")} handleButtonPressed={handleButtonHomePressed}></DefaultButton> -// </View> -// </View> -// ) -// } - -// const styles = StyleSheet.create({ -// container: { -// display: 'flex', -// width: '100%', -// height: '100%', -// alignItems: 'center', -// justifyContent: 'center', -// gap: 10, -// }, -// buttonContainer: { -// width: '80%', -// margin: '10%', -// gap: 20 -// }, -// image: { -// width: '100%', -// height: '100%', -// position: 'absolute', -// }, -// }); \ No newline at end of file -- GitLab From 7557b98b9e70f03e1eea7518ba2ffd95896e33df Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 8 Jan 2025 10:19:39 +0100 Subject: [PATCH 196/283] feat: added the EndQuizMulti screen --- .../PlayingQuiz/EndQuizMulti/EndQuizMulti.tsx | 41 +++++ .../EndQuizMulti/EndQuizMultiChild.tsx | 156 ++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 screens/PlayingQuiz/EndQuizMulti/EndQuizMulti.tsx create mode 100644 screens/PlayingQuiz/EndQuizMulti/EndQuizMultiChild.tsx diff --git a/screens/PlayingQuiz/EndQuizMulti/EndQuizMulti.tsx b/screens/PlayingQuiz/EndQuizMulti/EndQuizMulti.tsx new file mode 100644 index 0000000..1945637 --- /dev/null +++ b/screens/PlayingQuiz/EndQuizMulti/EndQuizMulti.tsx @@ -0,0 +1,41 @@ +import {NavigationProp, RouteProp} from "@react-navigation/native"; +import { View, StyleSheet, Image, Text } from "react-native"; +import TemplateMenu from "../../../templates/TemplateMenu"; +import {Quiz} from "../../../models/Quiz"; +import EndQuizChild from "../EndQuiz/EndQuizChild"; +import EndQuizMultiChild from "./EndQuizMultiChild"; + +type RoutePropsType = { + EndQuizChild: { + quiz: Quiz; + }; +}; +interface Props { + route: RouteProp<RoutePropsType, "EndQuizChild">; + navigation: NavigationProp<any>; +} + +export default function EndQuizMulti({route,navigation}: Props) { + const { quiz } = route.params; + return ( + <View style={styles.containerGlobal}> + <TemplateMenu navigation={navigation} headerNavigation={false} buttonGoBack={false}> + <View> + <EndQuizMultiChild navigation={navigation} quiz={quiz}/> + </View> + </TemplateMenu> + </View> + ); +} + +const styles = StyleSheet.create({ + containerGlobal: { + display: 'flex', + width: '100%', + height: '100%', + flexDirection: 'column', + }, +}); + + + diff --git a/screens/PlayingQuiz/EndQuizMulti/EndQuizMultiChild.tsx b/screens/PlayingQuiz/EndQuizMulti/EndQuizMultiChild.tsx new file mode 100644 index 0000000..3ba9918 --- /dev/null +++ b/screens/PlayingQuiz/EndQuizMulti/EndQuizMultiChild.tsx @@ -0,0 +1,156 @@ +import { View, Image, Text, StyleSheet } from "react-native"; +import DefaultButton from "../../../components/buttons/DefaultButton"; +import { NavigationProp, RouteProp } from "@react-navigation/native"; +import {Quiz} from "../../../models/Quiz"; +import {useQuizService} from "../../../services/QuizService"; +import HttpError from "../../../services/HttpError"; +import UserScore from "../../../components/PlayingQuiz/EndQuiz/UserScore"; +import UserModel from "../../../models/UserModel"; +import EndQuizListPlayer from "../../../components/PlayingQuiz/EndQuiz/EndQuizListPlayer"; + + + +interface Props { + navigation: NavigationProp<any>; + quiz: Quiz; +} + +export default function EndQuizMultiChild({ navigation, quiz }: Props) { + + const {restartQuiz, remainingQuiz} = useQuizService() + + const handleRestartPress = async () => { + const isRestarted = await restartQuiz(quiz.id); + if(HttpError.isHttpError(isRestarted)){ + console.log(isRestarted.message); + return + } + if(!isRestarted) { + return; + } + const quizRestarted = await remainingQuiz(quiz.id); + if(HttpError.isHttpError(quizRestarted)){ + console.log(quizRestarted.message); + return + } + + navigation.reset({ + index: 0, + routes: [ + { + name: "PlayingQuiz", + params: {quizRecovered: quizRestarted}, + }, + ] + }); + }; + + const handleBackToMenu = () => { + navigation.navigate('TabNavigator'); + }; + const user = new UserModel(10,"Thomassss", "Thomas"); + + return ( + <View style={styles.container}> + <View style={styles.containerHeader}> + <Image source={require('../../../assets/TitleApp.png')}/> + </View> + <View style={styles.containerBody}> + <Image + source={require('../../../assets/stars.png')} // URL de l'avatar ou placeholder + style={styles.stars} + /> + <View style={styles.usersContainer}> + <UserScore user={user} score={10} maxScore={quiz.questionCount} isFirst={false}/> + <UserScore user={user} score={10} maxScore={quiz.questionCount} isFirst={true}/> + <UserScore user={user} score={10} maxScore={quiz.questionCount} isFirst={false}/> + </View> + <EndQuizListPlayer users={[new UserModel(1, "user1", "user1@email.com"), new UserModel(2, "user2", "user2@email.com")]} maxScore={quiz.questionCount}/> + + <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton> + <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu} buttonStyle={styles.blueButton} buttonText={styles.blueButtonText}></DefaultButton> + </View> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + height: '100%', + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-around' + }, + usersContainer: { + display: 'flex', + width: '100%', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-around', + }, + containerHeader: { + height: '10%', + width: '100%', + alignItems: 'center', + }, + containerBody: { + height: '80%', + width: '90%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-around', + backgroundColor: '#F3F3F3', + borderRadius: 40, + padding: '5%', + shadowColor: "#000", + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + elevation: 5, + }, + TextBodyTitle: { + textAlign: 'center', + fontSize: 24, + fontWeight: 'bold', + }, + TextBody: { + textAlign: 'center', + fontSize: 22, + fontWeight: 'bold', + }, + whiteButton: { + height: '12%', + width: '80%', + backgroundColor: '#FFFFFF', + borderColor: '#2B73FE', + borderWidth: 4, + borderRadius: 10, + padding: 0 + }, + whiteButtonText: { + fontSize: 24, + fontWeight: 'bold', + color: '#2B73FE', + }, + blueButton: { + height: '12%', + width: '80%', + backgroundColor: '#2B73FE', + borderRadius: 10, + padding: 0 + }, + blueButtonText: { + fontSize: 24, + fontWeight: 'bold', + color: '#FFFFFF', + }, + stars: { + resizeMode: "cover", + }, +}); \ No newline at end of file -- GitLab From 26d19830c74b148e10fde84ca73ab05285ccea27 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 8 Jan 2025 10:20:04 +0100 Subject: [PATCH 197/283] feat: added the EndQuizListPlayer --- .../PlayingQuiz/EndQuiz/EndQuizListPlayer.tsx | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 components/PlayingQuiz/EndQuiz/EndQuizListPlayer.tsx diff --git a/components/PlayingQuiz/EndQuiz/EndQuizListPlayer.tsx b/components/PlayingQuiz/EndQuiz/EndQuizListPlayer.tsx new file mode 100644 index 0000000..a2378cb --- /dev/null +++ b/components/PlayingQuiz/EndQuiz/EndQuizListPlayer.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import { FlatList, View, Text, StyleSheet, Image } from "react-native"; +import UserModel from "../../../models/UserModel"; + +interface Props { + users: UserModel[]; + maxScore: number; +} + +export default function EndQuizListPlayer({ users, maxScore }: Props) { + const renderUser = ({ item, index }: { item: UserModel; index: number }) => ( + <View style={[styles.userContainer, index === 0 && styles.firstPlace]}> + <Image + source={require("../../../assets/ProfilBaseImage.png")} // Replace with actual profile picture if available + style={styles.avatar} + /> + <View style={styles.infoContainer}> + <Text style={[styles.username, index === 0 && styles.firstPlaceText]}> + {item.username.toUpperCase()} + </Text> + <Text style={styles.score}> + {10 + index}/{maxScore} {/* Adjust score logic as needed */} + </Text> + </View> + </View> + ); + + return ( + <View style={styles.container}> + <FlatList + data={users} + renderItem={renderUser} + keyExtractor={(item) => item.id.toString()} + showsVerticalScrollIndicator={false} + /> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + width: "100%", + padding: 20, + backgroundColor: "#fff", + borderRadius: 20, + }, + userContainer: { + flexDirection: "row", + alignItems: "center", + backgroundColor: "#f9f9f9", + borderRadius: 10, + padding: 15, + marginBottom: 10, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 3, + elevation: 2, + }, + firstPlace: { + // backgroundColor: "#ffe700", + // shadowColor: "#ff9900", + // shadowOpacity: 0.3, + }, + avatar: { + width: 40, + height: 40, + borderRadius: 25, + marginRight: 15, + }, + infoContainer: { + flex: 1, + }, + username: { + fontSize: 18, + fontWeight: "bold", + color: "#333", + }, + firstPlaceText: { + // color: "#ff9900", + }, + score: { + fontSize: 16, + color: "#666", + }, +}); -- GitLab From 196e7f0d60eacb4377b54e8a91e820dfdde6a5c5 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 8 Jan 2025 10:20:17 +0100 Subject: [PATCH 198/283] feat: added the UserScore --- components/PlayingQuiz/EndQuiz/UserScore.tsx | 79 ++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 components/PlayingQuiz/EndQuiz/UserScore.tsx diff --git a/components/PlayingQuiz/EndQuiz/UserScore.tsx b/components/PlayingQuiz/EndQuiz/UserScore.tsx new file mode 100644 index 0000000..d57489c --- /dev/null +++ b/components/PlayingQuiz/EndQuiz/UserScore.tsx @@ -0,0 +1,79 @@ +import UserModel from "../../../models/UserModel"; +import { View, Text, StyleSheet, Image } from "react-native"; + +interface Props { + user: UserModel; + score: number; + maxScore: number; + isFirst: boolean; +} + +export default function UserScore({ user, score, maxScore, isFirst }: Props) { + + function shortenString(input: string): string { + if (input.length <= 5) { + return input; // Si la chaîne est déjà de 4 caractères ou moins, on la retourne telle quelle + } + return `${input.slice(0, 5)}...`; // Garde les 4 premiers caractères et ajoute "..." + } + + return ( + <View style={styles.container}> + <View style={[styles.avatarContainer, isFirst && styles.firstAvatar]}> + <Image + source={require('../../../assets/ProfilBaseImage.png')} // URL de l'avatar ou placeholder + style={styles.avatar} + /> + </View> + <Text style={styles.userName}>{shortenString(user.username.toUpperCase())}</Text> + <Text style={styles.userScore}> + {score}/{maxScore} + </Text> + </View> + ); +} + + + +const styles = StyleSheet.create({ + container: { + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "space-around", + marginHorizontal: 10, + }, + avatarContainer: { + width: 70, + height: 70, + borderRadius: 50, + backgroundColor: "#d9e3f0", // Fond gris clair + alignItems: "center", + justifyContent: "center", + }, + firstAvatar: { + width: 90, + height: 90, + // borderWidth: 3, // Ajoute une bordure pour le premier utilisateur + // borderColor: "#FFD700", // Couleur or pour marquer le premier + }, + avatar: { + width: "100%", + height: "100%", + resizeMode: "cover", + }, + userName: { + fontSize: 16, + fontWeight: "bold", + color: "#000", + marginTop: 5, + textAlign: "center", + }, + userScore: { + fontSize: 14, + fontWeight: "500", + color: "#333", + textAlign: "center", + marginTop: 5, + }, +}); -- GitLab From 47f3e388626975e4f8e988bbfd7ca13129b4b7fa Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 8 Jan 2025 10:20:54 +0100 Subject: [PATCH 199/283] feat: added the navigation for EndQuizMulti --- routes/StackNavigator.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index 7f91326..e0b86ca 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -8,6 +8,7 @@ import TabNavigator from "./TabNavigation"; import PlayQuiz from "../screens/Home/PlayQuiz/PlayQuiz"; import MyQuizzes from "../screens/Home/MyQuizzes/MyQuizzes"; import InformationsOfQuiz from "../screens/Community/InformationsOfQuiz/InformationsOfQuiz"; +import EndQuizMulti from "../screens/PlayingQuiz/EndQuizMulti/EndQuizMulti"; const Stack = createNativeStackNavigator(); @@ -21,6 +22,7 @@ export default function StackNavigator() { <Stack.Screen name="PlayingQuiz" component={PlayingQuiz} options={{ headerShown: false }}/> <Stack.Screen name="PlayQuiz" component={PlayQuiz} options={{ headerShown: false }}/> <Stack.Screen name="EndQuiz" component={EndQuiz} options={{ headerShown: false }}/> + <Stack.Screen name="EndQuizMulti" component={EndQuizMulti} options={{ headerShown: false }}/> <Stack.Screen name="MyQuizzes" component={MyQuizzes} options={{ headerShown: false }}/> <Stack.Screen name="InformationsOfQuiz" component={InformationsOfQuiz} options={{ headerShown: false }}/> </Stack.Navigator> -- GitLab From 09b96f1cdf1382d849a55ed7d5e8ace42a1e9811 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 8 Jan 2025 10:21:07 +0100 Subject: [PATCH 200/283] feat: added stars.png --- assets/stars.png | Bin 0 -> 6666 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/stars.png diff --git a/assets/stars.png b/assets/stars.png new file mode 100644 index 0000000000000000000000000000000000000000..09c430621cb58984d1b88f1e46e5d71c2b9a3284 GIT binary patch literal 6666 zcmV+l8ujIgP)<h;3K|Lk000e1NJLTq007zm002t}1^@s67AKiK00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yP<VFdsH8J|f+K~#7F?VSmH zRMok_&)xTXXP?PJ7?PL(mWWW6s34YF1^X1NsHj-At#zqY>(*AWJhg6E_q{&1Dk5st zx;&@?TI*gx7NG*k%p@!VLRhlT+&S<2?o24Qyeg1PqCJ0q*UU`j4wHNCcfReM?<0i4 zU@#aA27|$1Fc=I5gV8q`HEL9pBV-{wT*z_ULPb(8qLgxEF9`;NBQ#CB)X<EJ_vL)X zzF}z24VrBHJ!n}238U{aV#J6TPq-{cIO-i7PeE9oHcd0~W#`7j@t7vdS4e`ehUefj zIF9n1kipjpqsJLIa9~j2#ZGugWl>&RRaI3&7=4oqF1SDuc%g;og$_e8F5-E9D+-Lr z^UD-Psov8$)%~iALGwKz2*MV8Z`T38qLL`T%`{qJPy}US?%MO-_19JH9d_;eFoGyF zavX7sg291!NTRfg5T1enZVcG=XnYS2AZWQGbX|W02G!*`PXlHJL7r<H_GyA3Zsgz` zS(c{~MnTXN<6;yrOEVWJvbF&q+qQibVf0NzQG5e_P%r~e#OHxPz?MXD0h*GcAaBt% z^KMyER-=iD?*jqM2!b>Ve6o@FeoHeh@wA{QEiEc4(g~v=sH%BC{Gfiz`E_||`9z+R zV7#PEWm)BcgwglVRrSBY-k`;?z|A|>3^}{KzW?vmFB$OrBSvXEI9>$53l3hOiY%@N z^YX@s6DjXHHxvpL2^_xz%$F=^y9tC*Fl1SIj^lY+Ru(%wldSo{Pfm-$IEYl2^|$BD znIjNJ-vZ3NlTjlLuhBZv37KY+Jk2I}x@oCFZ@k=3ulr??KL3#EZdw#roT)E+qP4F4 zhNi@jNoY<&;N%uO7tGBC%s(hBk|e!`Cj6846t7ArB>MUC5ba+idy`_&A3UEh`WE1O z4FU;q1ExS**92%51kFuxAaD}Rz)v?RQa2&MPXY%=)9_k0p=3Xt8=nOxW-APA*4A>q z9HVf+<n|U?B5yor!&r$lQ!mq}?+t>Wp$RE$2!~@+CQqLHrP1TBD?Is{F=WV)BnZp; zZ7o^y$!h&Wms8v<l_3BkdI#ZL_^VvPdH?19z%1u-Pd~qHYT9)*i4$Mi(YE6~!YC-R zA_s9ins-XC!*mH|qf3rCvW<+a5lGhMVxND$Y0ir;{Ar`98J`-4@kBHnJ*%dsrkXH% z4^>rP#^8tFHcs8S=6jKr&0~soroMebXUbgWqA3ZQESt(5E|`@o-w4p6hz|Z6Pc79N zLm1skJQj~D;DeclIZsuzg_^3(H+AzJ)3WAks{WxQ$PMT}gMUnqJ8sP2RO%RiOGCxA znWRSFdnrhVRBLq12#tnp9&?)%Bf>aV_*hqs<&G2h@!<GimhUVyKHGN<`dX0qykXmC zV+XV)`#t5R)K)i@7qTRpkb>vGWfJDbBM&ng1ROw)@9^vXiG<Ou_<X+UUAaj-a1sR5 z!t=i%*9mz}k%oK%J@FWWHrGYhwydqVYUi5Ck&eb;m!k>US*O7`iL`l5wC&{MtB;LF z!xutc^fgFucMy`xd)l@$i!cfYgz2Zi0Bpg;=dnkG6X~SYp2-WAr^V1`^8`$9Z>=A2 zH{E=e1sQ<RBG0Yhc#cA--_<q!&Uieo5=OUBR#xUWb?dKQLK*}s8ZLe~8jODDw^!9% z_{o9@O}%H+)`UgV2`xVXiWJ7}^Ug&7TQl`a1CCkF`VeSKYS@gf&xNS*O!Vs@rQ?PI zfu9gYVK8NNItDX%BGALWt)~<3_+ik5-Lx16MtB^=j@DGg9W9C2)tz;L+cT-~GtHky z{&vMVHq5Rt%MzSr`Nd#@_9u*PVcN85Mko}y4}}dTLQJ1rhbwu~4aI*<C;2aqk#~rm z#o<{Yc{3k>-;wI?t=6#(`*uMPsVqqu5abgGqvyd3uPXY3Xt%?R>4N=#WlLSZKY9Kb zm>gY$m`#z~%$VZ6h@;yzxZtO`35Y;KWOCl@0F8xY&j%0%alPN~Ka4QCg&8wu@Up1R z@Ya#?=tJA{&YUu8a5`1C05zGL`iJkF8!Pe!Axoz~qpd1xFNxXr<~3PUX=&+X!svM_ zD=QUU)qf9zmxY;fn<i`CvZ<}9Lk~+Q{f(L3XT&`oWY<q`u;Ezr=OK^6O!+<s+D#Dn z?V#DtB#dsOq@?8gilT1B>#K<J+CxVibW&$yzlAR3&%ANi!#Lq<9d#x>?+k}xzD<&} ztz~89-zSWo2Zao@*8{jt5wuTBzz!U>`|md=#}suYqO&vkf!aOVn0uVlY?(xthEv<y zQvGH(ZyX<mnKGbj=3VGhfWPNLV1=!_>qb=77Y{hn$2_jq5ydQA?in48RkPfLlCv~+ z2^gA>N=MV*Z946E%d<2jNp7pEs`^Gnhegtd`E31Xceb=OpN*j`2x81@OSd`EKy-94 z7W{n5WPfP;x^*+#mCRw5ZW7CAg>YAvbS?Lw`Txy((<0B$<ze8t<~KiZA6oJ0)^$Ap z)|r`1CIqHQIohQF@4D_40WI*@*0!yUgt6azzOvZY-n?btS(kN^m*#EfB$AyZm!9(N zmee2CD<pXdWY4_!ag>rY@h8V0-)<~kxwAwTrA?to=zFVHty)4DJr5+N{a`L;NIqmt z@?m2x|CsmolX-i}Aj+lC2X6Ag%qV?frpzQ|ddNXKC0y>juCOz%_V4;k42;N<{DCT~ zrxHfD(Owt7sd<$~heD1FwbYY$1!za2<b&qMZ`EwCE5Eukp`^P^$j+onYlm6hI@L%p zayt12(q}c=vSrJfRMj|2;`w+w>z29cOdKR53|6EKlc8g1La8Wad>j$TrY$bHczPQ- zAa0YfBe#NFaU_fDw5UQ>C!2AN2$co-fh>$T$+{5M=XZj+*a+t6N+)10S-*aLLp~40 z=q6k#{>)Q<?EKM^)f<BcRm)`Z(QQQFcZ}fN?H{|m{d6Z+{*;<sHI?JDB2PNWp`#@7 z-eO8zxAWU@`s0Mr`(PAu;ftqLCO#;C^|Oy7=_wN}@UtX(V3k6*EjM;W89!SRq>_Lu z48$hP^$<qExbx0C6+_b#=%YP(PbeP=mb@HVXT1i8n#nYXS@fmR$A0!c2gXpKj^%rk zF!~;w5))(3K0e~VWI=31GxCQg1m16ND7mC9+5bm({kD2KCbmUh`WsuVUsyu^&?#s# z>Y7x)BQf-*&Uz=^34->-T_zn|uFxya_0#56DunbUPY9R(+4kH3zt7n5$Rm?{gwZ$A zb!!stGlRi(L!qMK-fN0-Zf^U$@DgDZ3@`5~tINS=r#_$0^SSQ4Vfgn>I3|)t3&Zo7 zA<s5;q)u19;dnbQnwDGC(s6C0K$pFJ^v?;S@1kq^UHRS9i!I9vN|JON225mGzL78r zMmQWk2`vf8RjwF5e0YUr7`KRm(1IFHm9=$c#l^poMWqqnW2!v8;g><WVNrk{5Lc)G z0(jX^N1Q|$eHRd}5DeaGh=ov1(Qa2naV`$7shP(UM#0cDeGyK0ima>^d9jUn%d!MF z5{f)nQ&Z#ZIfGP6siB!q<HBPUxQbGZmi03!j$ObBTuK;yn?N8i8icDIi}H8}gek#% zi4#Uah{a-u^TKZF6OM0#>;FV)X=%+K-ybeH5hB6~3}#_41kVWvBhL~>-$z&V=_EhU zk|3>~GGz*@Nl_q5N=kyDsTSC_?-5PYelf6QVDPJ)7Y#*^1<T`)U~Z-y+qsS~`aa{v zjT047nTz=|5VR)=<7)#hOeT!454LRwEX}&yahw5!!C){L-+Y+wvKtkp6_t`INi6_j zFc{sBt!bkLo^KZfp(z@Teq+m?F?t8)c;?9xp~TH*m8|Rf(rs<!gu&=Wn4skmWML;R zhQypednUs)+BcX3iuw=gN}MwaOw=D80?AdcF@~Zj7ZAo?6Ny9)(p2rq0}nj#2+~J^ z#Q8{Y5vU@`i-r#$?hr;{!O{jiCo~aEa`XJ%gt6Bcy8bFIz4F<<*GV76Htq9sn|}!n z`2!CUMq$Af=b|XTgAWynZL8TAG)?>1Yci<ouaG{9rs%kn!`p3-MRki~#XlpA!Xk;% z9mE?1;({4eK^R{dT;>KswgVng!!Y~gY8+g30+VSYP6nZ1$g4pKrfy4R|H@&6Q8+@O z&@fy*j;qZzMVUz$Uzq^|24FE-&m^-f>vGZuIpmN-Dp0soMezzS>6-o=!HGf9R-S$K z*{mt$|A%eZQ(;E>tEKCcNH>7yq9QAcaKj=v%$wrAINRy_74h5tQwUlEV2%y1so8H0 zft_3}&AO>5TGVZuK>fD;GtY;#oaga5OsvIRz7M+J@n5;8bMT%(O*nJrOry58HbDLp zQ6+VG&erB#&_K7=mey)yKhah7EU(fo$5Tz#ZX%4Ygd!?05UegN$c_8EF!+o!&QML$ zT)^e4oE<!9(0-S0r*w`GF9|+}d*6zp;a_*XKA^Pp$2iu4Ank|*Bj-7ebF*Vv_rnDK zrmpHs!JXJ9Nm2{X^I7oevJkE8G<CBMf@h;hp@A@|-W$OKTeD9F&Exq6-p*>C{{$}7 ze)(2lLP!!XF2Ta8k|4cL7+=c42Ok`kL=l&*f!Sz3M)m{n^-RmO{=((U2|Bhtt2^$k z=K88j#?|`0`{c2^s^{Wowl-GJY_BhA9e;@JY2sYj3oJV?{^K#nWkpeJ6L}$xeIJNd zqEMhEC1r)Jq6{-n7;eZ0&;+kSw5f;`m{=@!pvX(hc`n!92QJEb+xAZ-`$5REsufwA z8L$Gigz=?B!{O_5^)@*g4u|)-!tk-j9&6c_{UVniOUH8NFI>2Aze^%EZ=4!!sV{9R z4vU`f*1a8Yw>ONuE}K-dSea}2f&e{Yq<@L!I1e3O8++`L--YPB$1VEibJg=FPMjDU zH*TD-JYGH(?lTwLP=Q7(d{t{pcYfm4MC5RjrH}&K`an`(P^0RK`g?3(ho&SZp5gfe zxG#+`dX(>e_q&cPitD_vzNUXj_7ShgxN6MG2^nZc!+5_sRBe%N;8z#Me?IkC2m370 zvY1TU>nxf{DxGdB-`xh=MK#3V#qZF*HvU|$+l@dwQl-oA{Gcy52?VUktEeW*Ge~a$ zHu>`;my>A1n;dK88-{hp-h4)PZT7Pa>xUp@TL{jsJa#nUmzRdam4wk_gaUy}xP0Yk z|2|q0IR249<bkeA(h#*=04Dr?ui6+lVni(93qCA~N(Yuo2v`zb_<}E2_j6Z|hFo8U z&ZJ4(H&3qWXb66iO<=7_p0?K)y%mi{!99^Oc@295=?%QR1USF&kor{L;f8KJl4}|v zWDU)@0h?6p>AcM=jxU-yeMqg(woiuy;$LA#+z>FkM-zhSa)2btD@Z=nEb`)J2%7we zFuEO#48qv8lH9Vj#rx_@!F}^T_!(G&PFM8BXhM?xzz1lWaU)Fn?WlzU4o@#QL#K(i zO|Q+D`-OH<d7(>4w4<@+=5*4(6N|jIu2Jb_=SH`qb%dYcx18%qZ!vJ-z+mo+a4i!i zOi<>`nIkBQb{X&0%;)gpzp$I%##K~Q^eZb3{bs~a{}x*pvt72hSE<T0zeBo-m>CO$ z9ep?F)!6r(>a-$hw=z?*d#S0ZF=R<fLLj8QC>-AFK68Ty50)L<e;?k+@RX{mx?+DS zN~7Ro?>s>ewxQ5M#L|rf{IfoNE1J%FExY#6)+^a1Yy`BJz6EPcV&lYLpXcOT0}C!h zRDVHw6A+?<v0;QF%Zu<i&S+cK9#Z?z%;Jx+7$)B25E}ivscAn~74>D%bQxUsf>9f^ zmt!HxGdZwBB=}h&(v5gFKbVpNFEr;G<B2$sui%#=jBex7OD{EQDk}DRCA>F2nsHlB z$h;)-vg+#UVzQ5bko^Geu@rtZM542rroS|xWWYgm?e)QQy_3y&TI*kX>19%~=ci@! zjVrx2UrB}54%57*8&#Eq&L_Q%p%_P^&?t)ha&z5?@#$32UosHMLDl=w+z72GmEa-@ zUM>U=1!frxrCxi9L6^++)5q^9f!8JMhqaXS5I$e{U|Ez`=32h;%}lHH4Zxqi5rqt` z8L)hoMon{{x2>SiLe8s><lO~Dlr{!~!P8)@OIz1XD@!MIuZ{06GjW$@6KH!wS)H5z z9Zlx??73iGet*2_h5RFtqMwr925w?6xqIp)|MpB$bGv2+uL~!9*){J3=)iJ$W%U4w zUVV`RVO1ZfWjW_)CJZ{R*75`qX0|Ix4}vKO4NO{HQH~*uUI+JrNrIThrEaRMt*)x7 zDk1wx&=)yfmDTt8yoU4^G6;ejFZI_?zu2MkXIpghQp4+c-ep4K<LwP&&(9?6jGJ#* zpVMp-J!Mkyr|5b>^7<Ci+rZ6x8}=8w`a)lRrXV>-Gv7}-os!(<M8)hKtBh>!59Gbh zA=oH<;WI(pj)E6so34ino_}NX9_Y)Px_+ghYO^tLLAsGag9aU78}`Mjs?7(XOMAWW z^2z9s#nX3R@aF{$?o|kYY+iH3@N~m~KX(b57lEvc?W~NKT!iOXrn#zDcXFU3Eb{zf zRIihc_R~yP)Arr%U+>Ec6dloBvju3gPOyuQJk-efaDs9tVK90C^dJ3pc%rGA_u^hd z6rB099nVbkn$u&XvEi6+U3yN@gBuoU`PNb*ZL5#Z!*#CU(zL)mo*h4Ge3bMCJ~b8> zIdKV^eX7XQ`>sbplc-y#ZgRVXYPYD4yXy~=TQeYdPu=T7O$I)bvR2cyeuTm3K_CsK zLoh4@7F2e_M}h6RNF&^jbqXClEHoSA1hvCdx@<v^b|#$mt*LR|@+w_7Uc(%r;{<+8 z3XRVUjf4EeHdM#u{dD^J(+*A7%e14u^x4e1>f_QdBm7GUH9^29(>JuNj$Pfl+M<;Z zB*A=}F3Z0m3`P$Flle&Ww<J+o(vd2994G!XO!)U-Yf@3*ccqvNjiX16DVsZP=upp` zHBB2!T0lfpe~lCpT{q7a1*rwi#iPd<H1VDdnhYX0NoxsRKE>=zhMvO2-#-hQ+aP7P z*HukyO9o%tu~Mg%{dI4_rYb8hLs(xT3`P$U4Mvai+!Rrr-<ldSr88;JNAIw_UTnM| zQlIIYVVlk@ZwVg=9R%lA#pCgzMMXu!ytI&_bQWsA8a9@@lJk<&5<UF{r>VWp??q?O z|7uPRIIXE^k}sVyHtdStbgd(Uqd8H0$JWMg)ub^pnG)#-uLP;z7Q7WCx~%_}Fc>|E zu9)Y+4_p-C?fjm8@$hFHQ}QB{kQTV6>wW!-`W+<;ay{{yqli1PS&E_ANqnDUIO9oA zBg-;m+p(Lb;6kMF7cF%|Cqkkxn}GyiCSkbSww>Z{NrsNW{DT+T-<1r6z(;-H)3qxn z2ixmQ?n$RGU?O@>=lwUvKIL;AIy-IK9!(gG9s*22tP6q*l^?Ug9Q5+KJX%!hHUIox z?0y6H<E`+XY;HG5ZfguA#4jQ}Nx%u5gnM%E%c-({b!lm738>UTx6LSf^x@z6>9v=K z)|;w+_3;yf?<GDAcu^ZKbc%wO&|6#Aj;x(Lq5pR_E%l{6!Sr62Xj5X$;b1f0o?rE! zfX`%G$r(KYWDusIStxMAcHC1Y2x4c*@4xKdof8hnP84}@tyeJ%?oklX`$$iL{*x>! z+ev<7CFIP!#(sq7vhc-oYZdd^c#aq7MQ2%_w#tR`g=}jwa;c%JkKybP_KWo72?l-X z(Fpx)Ma75t3*Zw)aWS5+>w4k$_%Qk`xQ=!}S=ot(Vf@lEoQJ`ZddAev*T9_nU&z7T zzc&R?K^xWb`6h=TWSNl9cga^duOeCz(KYiPP1KsKz1)$q*C?WP8AKW11^+Ao$9nLq z8sST?0oA(*gk-KHN%zaLHj6-TW9VR&rjv@d4WTm`d?FkU9L;+>Q@eJnf?p=!#yMfF zA}J53syfzjoFlz$Hax!>7ljhWe=HD&mm{IbsgfwZ4)<2t+vk{{<hyDyc0I>F*<bZ@ zF+r{?`Zh=@bw+}b)5%`aXG#xNRvsJ%L2?~Spe?JSAZ+$WZ@qkqA5`xW0W{qOX9lvb z{=uZ@Omk?+*4i<W<87yb1BCc9_=qfOMwqp}v_i1NOXtA*o>dg(4G{9T6h)l}4(u#N z)^65S>#Te=CdSu{s;a7xEXnm<7806kC{S1;0ix6EWl4HpQ?+;D`A0DJ^RWl^UVi?- zv17-6{d?sJyu212%)dO}|COC<4vA=*@-s9iK~t?>KELd?be)`TPdKaE5~Jh&g2543 zOb3K!1<X{tw##U;_Ts<$H?U}nBuHM3kS`qG4sR~MC5F#pJ6&0nXZkJcFjffl>%jqK z2b2qfguyXP`>cZd<Cb7BIDW!}31Pzc2cYUd#Qi%;ZsBeOG}UGd-^+sF?YdpJW<<X& z?^S-mG|X*f@z+39^D2n$yaAf(ORL6W00m6Zzky)RhhrWb3qf;!%yZl}Za3lcR_hBw z7MF>^cbY&LeV(#N<j7d8bRsVABaA(xs@l_@su!ebx@tV?g+>MOm7eHH{Lg#JP+-yY zFjaFb_<b{EQF<RYdy>4+$)flwVKBZ4K-BO$uRx1x%=31pa@<ZuaeA%KSS%KctFn9z z*z_OxEbBPJV0;q*O>!!P{vk5SCE#g771du627|$1Fc=I5gTY`h7z_r3@t?>40d|}+ U!EG5~)c^nh07*qoM6N<$g1z6<u>b%7 literal 0 HcmV?d00001 -- GitLab From 57198d68e6b983884b461117d381800775b8db6e Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 8 Jan 2025 10:21:39 +0100 Subject: [PATCH 201/283] debug: for go into endquiz screen --- screens/Home/Home.tsx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/screens/Home/Home.tsx b/screens/Home/Home.tsx index dc3b638..1369070 100644 --- a/screens/Home/Home.tsx +++ b/screens/Home/Home.tsx @@ -3,12 +3,35 @@ import TemplateMono from "../../templates/TemplateMono"; import HomeChild from "./HomeChild"; import TemplateMenu from "../../templates/TemplateMenu"; import {NavigationProp} from "@react-navigation/native"; +import {Quiz} from "../../models/Quiz"; interface Props { navigation: NavigationProp<any>; } export default function Home({navigation}: Props) { + const debugQuiz: Quiz = { + id: "debug-quiz-001", + name: "Debug Quiz", + description: "This is a basic quiz for debugging purposes.", + score: 0, + questionIndex: 0, + questionCount: 0, + categoryId: 1, + difficultyId: 1, + authorId: 1, + category: { + id: 1, + name: "General Knowledge", + }, + difficulty: { + id: 1, + name: "Easy", + }, + questions: [], // Tableau de questions vide + }; + + navigation.navigate("EndQuizMulti", {quiz: debugQuiz}); return ( <View style={styles.containerGlobal}> <TemplateMenu navigation={navigation} headerNavigation={false} buttonGoBack={false}> -- GitLab From 553ff5fd9222600ef6990dc756e4e43abd76fa8e Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 8 Jan 2025 14:02:53 +0100 Subject: [PATCH 202/283] fix: changed port number to use prod API --- services/QuizService.ts | 2 +- services/UserService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index 28e66bb..6ceff37 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -5,7 +5,7 @@ import {Answer} from "../models/Answer"; import {shuffleAnswersOfQuiz} from "../helper/QuizHelper"; export const useQuizService = () => { - const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API + const url = "https://klebert-host.com:33036"; // Remplace par l'URL de ton endpoint API const generateQuiz = async (amount: number, category: number, difficulty: string) : Promise<Quiz | HttpError> => { const requestBody = { diff --git a/services/UserService.ts b/services/UserService.ts index 08399a9..f76e653 100644 --- a/services/UserService.ts +++ b/services/UserService.ts @@ -4,7 +4,7 @@ import axios from "axios"; import {transformToUserModel} from "../helper/UserHelper"; export const useUserService = () => { - const url = "https://klebert-host.com:33037"; + const url = "https://klebert-host.com:33036"; const loginUser = async (login: string, password: string): Promise<boolean | HttpError> => { -- GitLab From 3563b3415cf4c932f8a1e93ae88b6a2a91e10bcf Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 8 Jan 2025 14:09:01 +0100 Subject: [PATCH 203/283] feat: add header go back to multi community --- .../Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx index 8d7723a..18dba4d 100644 --- a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -67,7 +67,7 @@ export default function MultiplayerCommunity({navigation}: Props) { return ( <View style={styles.containerGlobal}> <View style={styles.templateQuizListContainer}> - <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> + <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={true} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> </View> <View style={styles.buttonContainer}> <BlueButton text="CREATE LOBBY" onPress={onCreateLobbyPressed} isDisabled={false}/> -- GitLab From 0605dd637f7137ba865a7e372493c889271dd2cb Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 8 Jan 2025 14:09:25 +0100 Subject: [PATCH 204/283] feat: add header go back to ongoingquizzes --- screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx index 4b251f2..f368297 100644 --- a/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx +++ b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx @@ -53,7 +53,7 @@ export default function Community({navigation}: Props) { return ( <View> <View style={styles.templateQuizListContainer}> - <TemplateQuizList title={"Ongoing Quizzes"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={false} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> + <TemplateQuizList title={"Ongoing Quizzes"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={true} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> </View> <View style={styles.buttonContainer}> <BlueButton text="JOIN" onPress={onJoinPressed} isDisabled={false}/> -- GitLab From 5e823e8548c7dfd1fec2d2ffa023ed66b2514d2c Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 8 Jan 2025 20:57:04 +0100 Subject: [PATCH 205/283] feat: add player list in lobby --- screens/Multiplayer/Lobby/Lobby.tsx | 98 ++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index 021f173..71c5a5c 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -1,4 +1,4 @@ -import { View, Text, StyleSheet, Button, Image } from "react-native"; +import { View, Text, StyleSheet, Button, Image, ScrollView, FlatList } from "react-native"; import TemplateMenu from "../../../templates/TemplateMenu"; import { NavigationProp } from "@react-navigation/native"; import React, { useState } from "react"; @@ -15,6 +15,47 @@ export default function Lobby({navigation}: Props) { const [isHost, setIsHost] = useState<boolean>(true); const [showModal, setShowModal] = useState<boolean>(false); + const players = [ + { id: 0, name: 'Invite'}, + { id: 1, name: 'John Doe'}, + { id: 2, name: 'John Doe'}, + { id: 3, name: 'John Doe'}, + { id: 4, name: 'John Doe'}, + { id: 5, name: 'John Doe'}, + { id: 6, name: 'John Doe'}, + { id: 7, name: 'John Doe'}, + { id: 8, name: 'John Doe'}, + { id: 9, name: 'John Doe'}, + { id: 10, name: 'John Doe'}, + { id: 11, name: 'John Doe'}, + { id: 12, name: 'John Doe'}, + { id: 13, name: 'John Doe'}, + { id: 14, name: 'John Doe'}, + { id: 15, name: 'John Doe'}, + ]; + + const item = ({item}: {item: {id: number, name: string}}) => { + return ( + <> + {item.name === 'Invite' ? + <View style={styles.PlayerItem}> + <TouchableOpacity style={styles.imagePlayer} onPress={onInvitePressed}> + <Image source={require('../../../assets/add.png')} /> + </TouchableOpacity> + <Text style={styles.textPlayer}>Invite</Text> + </View> + : + <View style={styles.PlayerItem}> + <Image source={require('../../../assets/ProfilBaseImage.png')} style={styles.imagePlayer} /> + <Text style={styles.textPlayer}>{item.name}</Text> + </View> + } + </> + + ) + } + + const onLeavePressed = () => { navigation.navigate("Multiplayer"); }; @@ -36,12 +77,13 @@ export default function Lobby({navigation}: Props) { <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> <Text style={styles.title}>{'BMW OFFICIAL QUIZ (I’m leaking oil)'}</Text> <View style={styles.containerPlayers}> - <View style={styles.PlayerItem}> - <TouchableOpacity style={styles.buttonInvite} onPress={onInvitePressed}> - <Image source={require('../../../assets/add.png')} style={styles.imagePlayer}/> - </TouchableOpacity> - <Text style={styles.textPlayer}>Invite</Text> - </View> + <FlatList + data={players} + renderItem={item} + numColumns={3} + keyExtractor={(item) => item.id.toString()} + showsVerticalScrollIndicator={true} + /> </View> {isHost ? <View style={styles.buttonGroup}> @@ -64,40 +106,37 @@ export default function Lobby({navigation}: Props) { } const styles = StyleSheet.create({ - container: { - display: "flex", - height: "100%", - width: "100%", - justifyContent: "center", - alignItems: "center", - }, - title: { - fontSize: 32, - textAlign: 'center', - color: '#00B3F4', - marginBottom: 20, - }, containerPlayers: { - width: '100%', height: '65%', - overflow: 'scroll', + width: '100%', }, PlayerItem: { width: '30%', - height: 'auto', - display: 'flex', - flexDirection: 'column', + aspectRatio: 1, justifyContent: 'center', alignItems: 'center', - }, - buttonInvite: { + margin: 5, }, imagePlayer: { - width: 100, - height: 100, + width: '80%', + height: '80%', }, textPlayer: { fontSize: 20, + textAlign: 'center', + }, + container: { + display: "flex", + height: "100%", + width: "100%", + justifyContent: "center", + alignItems: "center", + }, + title: { + fontSize: 32, + textAlign: 'center', + color: '#00B3F4', + marginBottom: 20, }, buttonGroup: { flexDirection: 'row', @@ -107,5 +146,4 @@ const styles = StyleSheet.create({ button: { width: '45%', }, - }); -- GitLab From dc38bee02a570eae0906421458ae5e99ebd352c0 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 8 Jan 2025 21:30:26 +0100 Subject: [PATCH 206/283] styles: fix styles for online selectMode --- screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx b/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx index 59ec2c2..bf33162 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx @@ -10,14 +10,14 @@ export default function OnlineSelectMode({ mode, setMode }: Props) { return ( <View style={styles.container}> <View style={styles.buttonContainer}> - <TouchableOpacity onPress={() => setMode("create")} style={{marginLeft: '10%'}}> + <TouchableOpacity onPress={() => setMode("create")}> <Text style={[styles.text, mode === "create" && styles.activeText]}>CREATE A LOBBY</Text> </TouchableOpacity> <View style={[styles.bar, mode === "create" ? styles.activeBar : styles.inactiveBar]} /> </View> <View style={styles.buttonContainer}> - <TouchableOpacity onPress={() => setMode("join")} style={{marginRight: '10%'}}> + <TouchableOpacity onPress={() => setMode("join")}> <Text style={[styles.text, mode === "join" && styles.activeText]}>JOIN A QUIZ</Text> </TouchableOpacity> <View style={[styles.bar, mode === "join" ? styles.activeBar : styles.inactiveBar]} /> @@ -31,7 +31,7 @@ const styles = StyleSheet.create({ container: { display: 'flex', width: '100%', - height: '15%', + height: '12%', flexDirection: 'row', backgroundColor: '#f3f3f3', borderRadius: 10, @@ -48,13 +48,14 @@ const styles = StyleSheet.create({ buttonContainer: { display: 'flex', flexDirection: 'column', - width: '40%', + width: '45%', alignItems: 'center', - gap: "5%", + gap: "2%", }, text: { - fontSize: 25, + fontSize: 18, color: '#bebebe', + textAlign: 'center', }, activeText: { color: '#2ec7ff', -- GitLab From d2a62992a7ad39b63988274ad52ab383c13583ce Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 8 Jan 2025 21:36:42 +0100 Subject: [PATCH 207/283] style: fix style for lobby player list --- screens/Multiplayer/Lobby/Lobby.tsx | 53 ++++++++++++++++------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index 71c5a5c..6bc6cd4 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -75,30 +75,32 @@ export default function Lobby({navigation}: Props) { return ( <View> <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> - <Text style={styles.title}>{'BMW OFFICIAL QUIZ (I’m leaking oil)'}</Text> - <View style={styles.containerPlayers}> - <FlatList - data={players} - renderItem={item} - numColumns={3} - keyExtractor={(item) => item.id.toString()} - showsVerticalScrollIndicator={true} - /> - </View> - {isHost ? - <View style={styles.buttonGroup}> - <View style={styles.button}> - <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> - </View> - <View style={styles.button}> - <WhiteButton text="CANCEL" onPress={onCancelPressed} isDisabled={false}/> - </View> + <View style={styles.containerLobby}> + <Text style={styles.title}>{'BMW OFFICIAL QUIZ (I’m leaking oil)'}</Text> + <View style={styles.containerPlayers}> + <FlatList + data={players} + renderItem={item} + numColumns={3} + keyExtractor={(item) => item.id.toString()} + showsVerticalScrollIndicator={true} + /> </View> - : - <View style={styles.buttonGroup}> - <WhiteButton text="LEAVE" onPress={onLeavePressed} isDisabled={false}/> - </View> - } + {isHost ? + <View style={styles.buttonGroup}> + <View style={styles.button}> + <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> + </View> + <View style={styles.button}> + <WhiteButton text="CANCEL" onPress={onCancelPressed} isDisabled={false}/> + </View> + </View> + : + <View style={styles.buttonGroup}> + <WhiteButton text="LEAVE" onPress={onLeavePressed} isDisabled={false}/> + </View> + } + </View> </TemplateMenu> <ModalInvitePlayer showModal={showModal} onClosePressed={() => setShowModal(false)}/> </View> @@ -146,4 +148,9 @@ const styles = StyleSheet.create({ button: { width: '45%', }, + containerLobby: { + height: '87%', + display: 'flex', + justifyContent: 'space-around', + } }); -- GitLab From 694367bbc6bd0d9dee6e1dbc3b47a4f0df4724d2 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 8 Jan 2025 21:40:02 +0100 Subject: [PATCH 208/283] style: fix style for modal create lobby --- components/Multiplayer/ModalCreateLobby.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Multiplayer/ModalCreateLobby.tsx b/components/Multiplayer/ModalCreateLobby.tsx index f87b518..831538c 100644 --- a/components/Multiplayer/ModalCreateLobby.tsx +++ b/components/Multiplayer/ModalCreateLobby.tsx @@ -44,7 +44,7 @@ const styles = StyleSheet.create({ modalView: { display: 'flex', justifyContent: 'space-between', - height: '50%', + height: 400, width: '90%', margin: '10%', padding: '5%', -- GitLab From 44969d458d5303778b98818653492f5e69155299 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Wed, 8 Jan 2025 22:10:29 +0100 Subject: [PATCH 209/283] style: modif modal --- screens/Profil/Modals/ProfilModal.tsx | 2 +- screens/Profil/Modals/ProfilModalLogin.tsx | 14 ++++++-------- screens/Profil/Modals/ProfilModalSignup.tsx | 8 ++++++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/screens/Profil/Modals/ProfilModal.tsx b/screens/Profil/Modals/ProfilModal.tsx index fb5afbd..c24d3fc 100644 --- a/screens/Profil/Modals/ProfilModal.tsx +++ b/screens/Profil/Modals/ProfilModal.tsx @@ -82,7 +82,7 @@ const styles = StyleSheet.create({ position: "absolute", }, containerModal: { - height: "80%", + height: 600, width: "90%", backgroundColor: "#D9D9D9", borderRadius: 40, diff --git a/screens/Profil/Modals/ProfilModalLogin.tsx b/screens/Profil/Modals/ProfilModalLogin.tsx index 2f651da..41c3553 100644 --- a/screens/Profil/Modals/ProfilModalLogin.tsx +++ b/screens/Profil/Modals/ProfilModalLogin.tsx @@ -1,10 +1,10 @@ import { TextInput, View, StyleSheet, Text } from "react-native"; -import DefaultButton from "../../../components/buttons/DefaultButton"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import React, { useState } from "react"; import {useUserService} from "../../../services/UserService"; import HttpError from "../../../services/HttpError"; import {NavigationProp} from "@react-navigation/native"; +import BlueButton from "../../../components/buttons/BlueButton"; interface Props { navigation: NavigationProp<any>; @@ -40,14 +40,14 @@ export default function ProfilModalLogin({navigation, closeModal}: Props) { <InputPlayQuiz placeholder="EMAIL..." setText={setEmail} noTitle={true} /> <InputPlayQuiz placeholder="PASSWORD..." setText={setPassword} noTitle={true} isSecure={true}/> </View> - <DefaultButton text="LOGIN" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText} /> + <BlueButton text="LOGIN" onPress={handleSubmitPressed} buttonStyle={styles.SubmitButton} isDisabled={false}/> </View> ); } const styles = StyleSheet.create({ containerForm: { - height: '67%', + height: 'auto', width: '100%', display: 'flex', flexDirection: 'column', @@ -56,7 +56,10 @@ const styles = StyleSheet.create({ marginTop: "3%", }, container:{ + height: 475, paddingTop: '5%', + display: 'flex', + justifyContent: 'space-between' }, textInput: { height: '20%', @@ -74,11 +77,6 @@ const styles = StyleSheet.create({ backgroundColor: '#2b73fe', borderRadius: 10, }, - SubmitText: { - fontSize: 20, - fontWeight: 'bold', - color: '#FFFFFF', - }, errorText: { color: 'red', fontSize: 16, diff --git a/screens/Profil/Modals/ProfilModalSignup.tsx b/screens/Profil/Modals/ProfilModalSignup.tsx index a673efa..124a42e 100644 --- a/screens/Profil/Modals/ProfilModalSignup.tsx +++ b/screens/Profil/Modals/ProfilModalSignup.tsx @@ -5,6 +5,7 @@ import React, {useState} from "react"; import {useUserService} from "../../../services/UserService"; import HttpError from "../../../services/HttpError"; import {NavigationProp} from "@react-navigation/native"; +import BlueButton from "../../../components/buttons/BlueButton"; interface Props { navigation: NavigationProp<any>; @@ -49,14 +50,14 @@ export default function ProfilModalSignup({navigation, closeModal}: Props) { <InputPlayQuiz placeholder="PASSWORD..." setText={setPassword} noTitle={true} isSecure={true}/> <InputPlayQuiz placeholder="REPEAT PASSWORD..." setText={setRepeatPassword} noTitle={true} isSecure={true}/> </View> - <DefaultButton text="SIGNUP" handleButtonPressed={handleSubmitPressed} buttonStyle={styles.SubmitButton} buttonText={styles.SubmitText}/> + <BlueButton text="SIGNUP" onPress={handleSubmitPressed} buttonStyle={styles.SubmitButton} isDisabled={false}/> </View> ) } const styles = StyleSheet.create({ containerForm: { - height: '67%', + height: 'auto', width: '100%', display: 'flex', flexDirection: 'column', @@ -65,7 +66,10 @@ const styles = StyleSheet.create({ marginTop: "3%", }, container:{ + height: 475, paddingTop: '5%', + isplay: 'flex', + justifyContent: 'space-between' }, textInput: { height: '20%', -- GitLab From d9346c3d8eee4ff38129a6aefdd63f99612f1b1d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 9 Jan 2025 18:55:05 +0100 Subject: [PATCH 210/283] feat: added new services for playing quiz --- services/QuizService.ts | 119 ++++++++++++++++++++++++++++++++++------ services/UserService.ts | 2 +- 2 files changed, 104 insertions(+), 17 deletions(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index 6ceff37..f65804a 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -3,11 +3,16 @@ import {Quiz} from "../models/Quiz"; import axios from "axios"; import {Answer} from "../models/Answer"; import {shuffleAnswersOfQuiz} from "../helper/QuizHelper"; +import {Question} from "../models/Question"; +import {AnswerResponse} from "../models/AnswerResponse"; +import {QuizInformations} from "../models/QuizInformations"; +import {QuestionResponse} from "../models/QuestionResponse"; +import {QuizGenerateAnswer} from "../models/QuizGenerateAnswer"; export const useQuizService = () => { - const url = "https://klebert-host.com:33036"; // Remplace par l'URL de ton endpoint API + const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API - const generateQuiz = async (amount: number, category: number, difficulty: string) : Promise<Quiz | HttpError> => { + const generateQuiz = async (amount: number, category: number, difficulty: string) : Promise<string | HttpError> => { const requestBody = { amount, category, @@ -15,14 +20,14 @@ export const useQuizService = () => { }; try { - const response = await axios.post<Quiz>(`${url}/quiz/create`, requestBody, { + const response = await axios.post(`${url}/quiz/generate`, requestBody, { headers: { "Content-Type": "application/json", }, }); - - // Récupération des données retournées par l'API - return shuffleAnswersOfQuiz(response.data); + const runId = response.data.id; + // Récupération du run ID + return runId; } catch (error: any) { console.error("Error while generating quiz:", error); @@ -36,6 +41,82 @@ export const useQuizService = () => { } } + const getActualQuestionOfAQuiz = async (quizId: string): Promise<Question | HttpError> => { + const requestBody = { + id: quizId, + }; + + try { + const response = await axios.post<QuestionResponse>(`${url}/run/question`, requestBody, { + headers: { + "Content-Type": "application/json", + }, + }); + + return response.data.question; + } catch (error: any) { + console.error("Error while remaining quiz:", error); + + if (error.response) { + // Gestion des erreurs HTTP + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + // Gestion des erreurs réseau ou autres + return new HttpError(500, "Unexpected error: " + error.message); + } + } + } + + const getQuizInformations = async (quizId: string): Promise<QuizInformations | HttpError> => { + + try { + const response = await axios.get<QuizInformations>(`${url}/quiz/info/`+quizId, { + headers: { + "Content-Type": "application/json", + }, + }); + return response.data; + } catch (error: any) { + console.error("Error while getQuizInformations quiz:", error); + + if (error.response) { + // Gestion des erreurs HTTP + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + // Gestion des erreurs réseau ou autres + return new HttpError(500, "Unexpected error: " + error.message); + } + } + } + + const answerQuestion = async (quizId: string, questionId: number, answerId: string): Promise<AnswerResponse | HttpError> => { + const requestBody = { + id: quizId, + questionID: questionId, + answerID: answerId, + }; + + try { + const response = await axios.post<AnswerResponse>(`${url}/run/answer`, requestBody, { + headers: { + "Content-Type": "application/json", + }, + }); + + return response.data; + } catch (error: any) { + console.error("Error while remaining quiz:", error); + + if (error.response) { + // Gestion des erreurs HTTP + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + // Gestion des erreurs réseau ou autres + return new HttpError(500, "Unexpected error: " + error.message); + } + } + }; + const remainingQuiz = async (id: string): Promise<Quiz | HttpError> => { const requestBody = { id, @@ -102,21 +183,24 @@ export const useQuizService = () => { } }; - const getRandomQuiz = async (): Promise<Quiz | HttpError> => { - try { - const requestBody = { - amount: 10, - }; + const getRandomQuiz = async (): Promise<QuizGenerateAnswer | HttpError> => { + const requestBody = { + amount: 10, + category: 10, + difficulty: "easy", + }; - const response = await axios.post<Quiz>(`${url}/quiz/create`, requestBody, { + try { + const response = await axios.post<QuizGenerateAnswer>(`${url}/quiz/generate`, requestBody, { headers: { "Content-Type": "application/json", }, }); - - return shuffleAnswersOfQuiz(response.data); + const runId = response.data.id; + // Récupération du run ID + return {id: runId, quizId: response.data.quizId}; } catch (error: any) { - console.error("Error while generating random quiz:", error); + console.error("Error while generating quiz:", error); if (error.response) { // Gestion des erreurs HTTP @@ -191,6 +275,9 @@ export const useQuizService = () => { getRandomQuiz: getRandomQuiz, getCommunityQuiz: getCommunityQuiz, restartQuiz: restartQuiz, - getCategories: getCategories + getCategories: getCategories, + answerQuestion: answerQuestion, + getActualQuestionOfAQuiz: getActualQuestionOfAQuiz, + getQuizInformations: getQuizInformations } } \ No newline at end of file diff --git a/services/UserService.ts b/services/UserService.ts index f76e653..08399a9 100644 --- a/services/UserService.ts +++ b/services/UserService.ts @@ -4,7 +4,7 @@ import axios from "axios"; import {transformToUserModel} from "../helper/UserHelper"; export const useUserService = () => { - const url = "https://klebert-host.com:33036"; + const url = "https://klebert-host.com:33037"; const loginUser = async (login: string, password: string): Promise<boolean | HttpError> => { -- GitLab From 84e42c6afb84b4ac1741573224396f9f1840e4f9 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 9 Jan 2025 18:55:41 +0100 Subject: [PATCH 211/283] feat: added new models for playing quiz and services --- models/AnswerResponse.ts | 17 +++++++++++++++++ models/Question.ts | 3 +++ models/QuestionResponse.ts | 5 +++++ models/QuizGenerateAnswer.ts | 4 ++++ 4 files changed, 29 insertions(+) create mode 100644 models/AnswerResponse.ts create mode 100644 models/QuestionResponse.ts create mode 100644 models/QuizGenerateAnswer.ts diff --git a/models/AnswerResponse.ts b/models/AnswerResponse.ts new file mode 100644 index 0000000..217cf6e --- /dev/null +++ b/models/AnswerResponse.ts @@ -0,0 +1,17 @@ +import {Answer} from "./Answer"; +import {Category} from "./Quiz"; + +export interface AnswerResponse { + score: number; + answers:{ + id: number; + text: string; + type:string; + categoryId: number; + quizId: string; + order: number; + answers: Answer[]; + } + category: Category; + +} \ No newline at end of file diff --git a/models/Question.ts b/models/Question.ts index a474b8a..7b77932 100644 --- a/models/Question.ts +++ b/models/Question.ts @@ -1,10 +1,13 @@ import {Answer} from "./Answer"; +import {Category} from "./Quiz"; export interface Question { id: number; text: string; + type: string; categoryId: number; quizId: string; order: number; answers: Answer[]; + category: Category; } \ No newline at end of file diff --git a/models/QuestionResponse.ts b/models/QuestionResponse.ts new file mode 100644 index 0000000..4e1cee6 --- /dev/null +++ b/models/QuestionResponse.ts @@ -0,0 +1,5 @@ +import {Question} from "./Question"; + +export interface QuestionResponse { + question: Question; +} \ No newline at end of file diff --git a/models/QuizGenerateAnswer.ts b/models/QuizGenerateAnswer.ts new file mode 100644 index 0000000..3992ebe --- /dev/null +++ b/models/QuizGenerateAnswer.ts @@ -0,0 +1,4 @@ +export interface QuizGenerateAnswer { + id: string; + quizId: string; +} \ No newline at end of file -- GitLab From a55dfb910e119cc64670193864a77485e1d6cd9e Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 9 Jan 2025 18:56:09 +0100 Subject: [PATCH 212/283] fix: changing for quick game --- screens/Home/HomeChild.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index fd9c7da..22afb5d 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -24,6 +24,7 @@ export default function HomeChild({navigation}: Props) { const handleButtonQuickGamePressed = async () => { const quizData = await getRandomQuiz() + console.log(quizData); if (quizData instanceof HttpError) { console.error("Error while fetching random quiz:", quizData); return; @@ -33,7 +34,7 @@ export default function HomeChild({navigation}: Props) { routes: [ { name: "PlayingQuiz", - params: {quizRecovered: quizData}, + params: {runId: quizData.id, quizId: quizData.quizId}, }, ] }); -- GitLab From 27e1b9a4891f194ee58d8d8a32b5be2b2f96d44b Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 9 Jan 2025 18:56:53 +0100 Subject: [PATCH 213/283] refactor: changed the logic for playing game --- screens/PlayingQuiz/PlayingQuiz.tsx | 63 ++++++++++++------- screens/PlayingQuiz/PlayingQuizBody.tsx | 76 +++++++++-------------- screens/PlayingQuiz/PlayingQuizHeader.tsx | 19 +++--- 3 files changed, 80 insertions(+), 78 deletions(-) diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index bcfe263..406582e 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -1,29 +1,18 @@ -import PlayingQuizBody from "./PlayingQuizBody"; import PlayingQuizHeader from "./PlayingQuizHeader"; -import {useState} from "react"; +import React, {useEffect, useState} from "react"; import TemplateDuo from "../../templates/TemplateDuo"; -import {Quiz} from "../../models/Quiz"; import {NavigationProp, RouteProp} from "@react-navigation/native"; +import {QuizInformations} from "../../models/QuizInformations"; +import {useQuizService} from "../../services/QuizService"; +import HttpError from "../../services/HttpError"; +import {Question} from "../../models/Question"; +import PlayingQuizBody from "./PlayingQuizBody"; -const optionsTheme = [ - "General Knowledge", - "Entertainment: Books", - "Entertainment: Film", - "Entertainment: Music", - "Entertainment: Musicals & Theatres", - "Entertainment: Television", - "Entertainment: Video Games", - "Entertainment: Board Games", - "Science & Nature", - "Science: Computers", - "Science: Mathematics", - "Mythology", - "Sports", - "Geography"] type RoutePropsType = { PlayingQuiz: { - quizRecovered: Quiz; + runId: string; + quizId: string; }; }; @@ -33,14 +22,40 @@ interface Props { } export default function PlayingQuiz({route, navigation}:Props) { - const {quizRecovered} = route.params; - const [isAlreadyPlayed, setIsAlreadyPlayed] = useState(false); - const [quiz, setQuiz] = useState<Quiz>(quizRecovered); + const {runId, quizId} = route.params; + const { getQuizInformations, getActualQuestionOfAQuiz } = useQuizService(); + const [quizInformations, setQuizInformations] = useState<QuizInformations | undefined>(undefined); + const [actualQuestion, setActualQuestion] = useState<Question | undefined>(undefined); + const [score, setScore] = useState(0); + + const fetchQuizInformations = async () => { + const quizInformationsFetched = await getQuizInformations(quizId); + if (HttpError.isHttpError(quizInformationsFetched)) { + return; + } + setQuizInformations(quizInformationsFetched); + } + + const fetchActualQuestion = async () => { + const actualQuestionFetched = await getActualQuestionOfAQuiz(runId); + if (HttpError.isHttpError(actualQuestionFetched)) { + return; + } + setActualQuestion(actualQuestionFetched); + } + + useEffect(() => { + fetchQuizInformations(); + }, []); + + useEffect(() => { + fetchActualQuestion(); + }, [quizId]); return ( <TemplateDuo - childrenHeader={<PlayingQuizHeader quiz={quiz} navigation={navigation}></PlayingQuizHeader>} - childrenBody={<PlayingQuizBody quiz={quiz} setQuiz={setQuiz} isAlreadyPlayed={isAlreadyPlayed} setIsAlreadyPlayed={setIsAlreadyPlayed} navigation={navigation}></PlayingQuizBody>} + childrenHeader={<PlayingQuizHeader quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} score={score} navigation={navigation}></PlayingQuizHeader>} + childrenBody={<PlayingQuizBody quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} fetchActualQuestion={fetchActualQuestion} setScore={setScore} navigation={navigation}></PlayingQuizBody>} /> ); } \ No newline at end of file diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 5e96e78..b8d01b0 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -10,12 +10,14 @@ import {Question} from "../../models/Question"; import {Answer} from "../../models/Answer"; import {Quiz} from "../../models/Quiz"; import AnswerButton from "../../components/buttons/AnswerButton"; +import {QuizInformations} from "../../models/QuizInformations"; interface Props { - quiz: Quiz; - setQuiz: Dispatch<SetStateAction<Quiz>>; - isAlreadyPlayed: boolean; - setIsAlreadyPlayed: (isAlreadyPlayed: boolean) => void; + quizInformations: QuizInformations; + runId?: string; + actualQuestion: Question; + fetchActualQuestion: () => void; + setScore: (score: number) => void; navigation: NavigationProp<any>; } @@ -62,24 +64,13 @@ const getStyleOfAnswerButton = (answerId: number, selectedAnswerIndex: number | return styles.answerButton; } -export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIsAlreadyPlayed, navigation }: Props) { - const [currentQuestion, setCurrentQuestion] = useState<Question>(quiz.questions[quiz.questionIndex - 1]); - const [questionIsFinished, setQuestionIsFinished] = useState(false); +export default function PlayingQuizBody({ quizInformations, runId, actualQuestion, fetchActualQuestion, setScore, navigation }: Props) { const [selectedAnswerId, setSelectedAnswerId] = useState<number | null>(null); - const [validationButtonIsDisabled, setValidationButtonIsDisabled] = useState(true); const [quizState, setQuizState] = useState(QuizState.ANSWERING); const [correctAnswerId, setCorrectAnswerId] = useState<number | null>(null); + const [isLastQuestion, setIsLastQuestion] = useState(false); - const {getAnswerQuiz} = useQuizService(); - - useEffect(() => { - setCurrentQuestion(quiz.questions[quiz.questionIndex - 1]); - setSelectedAnswerId(null); // Réinitialise la sélection - setQuestionIsFinished(false); // Réinitialise l'état de la question - setCorrectAnswerId(null); // Réinitialise l'id de la bonne réponse - setQuizState(QuizState.ANSWERING) - }, [quiz.questionIndex]); - + const {answerQuestion} = useQuizService(); const getCorrectAnswer = (answers: Answer[]): Answer => { const correctAnswer = answers.find((answer) => answer.isCorrect); if (!correctAnswer) throw new Error("No correct answer found"); @@ -88,41 +79,35 @@ export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIs const onValidation = async () => { if(selectedAnswerId === null) return; setQuizState(QuizState.LOADING); - const answerId = currentQuestion.answers.find((answer) => answer.id === selectedAnswerId)?.id; + const answerId = actualQuestion.answers.find((answer) => answer.id === selectedAnswerId)?.id; if(!answerId) return; - const answers = await getAnswerQuiz(quiz.id, currentQuestion.id, answerId); - if (HttpError.isHttpError(answers)) { + const answereFetched = await answerQuestion(runId, actualQuestion.id, answerId); + + if (HttpError.isHttpError(answereFetched)) { return; } - setQuizState(QuizState.SHOWING_RESULTS); - const correctAnswerIdFetched = getCorrectAnswer(answers).id; - if(selectedAnswerId === correctAnswerIdFetched) - setQuiz((prevQuiz) => ({ - ...prevQuiz, - score: prevQuiz.score + 1, - })); + setScore(answereFetched.score); + const correctAnswerIdFetched = getCorrectAnswer(answereFetched.answers.answers).id; setCorrectAnswerId(correctAnswerIdFetched); + setQuizState(QuizState.SHOWING_RESULTS); - }; - - const onContinue = () => { - if (quiz.questionIndex === quiz.questions.length) { - navigation.navigate("EndQuiz", {quiz: quiz}); + // Check si c'est la dernière question + if(answereFetched.answers.order === quizInformations.questionCount - 1) { + setIsLastQuestion(true); return; } - setQuiz((prevQuiz) => ({ - ...prevQuiz, - questionIndex: prevQuiz.questionIndex + 1, - })); - } - useEffect(() => { - if(selectedAnswerId !== null) { - setValidationButtonIsDisabled(false) + }; + const onContinue = () => { + if(isLastQuestion) { + console.log("go endquiz"); return; } - setValidationButtonIsDisabled(true) - }, [selectedAnswerId]); + fetchActualQuestion(); + setQuizState(QuizState.ANSWERING); + setSelectedAnswerId(null); + setCorrectAnswerId(null); + } const onAnsweredButtonClicked = (answerId: number) => { if(quizState === QuizState.SHOWING_RESULTS || quizState === QuizState.LOADING) { @@ -133,9 +118,9 @@ export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIs return ( <View style={styles.buttonContainer}> - {currentQuestion ? ( + {actualQuestion ? ( <> - {currentQuestion.answers.map((answer, index) => ( + {actualQuestion.answers.map((answer, index) => ( <AnswerButton key={index} text={answer.text} @@ -144,7 +129,6 @@ export default function APlayingQuizBody({ quiz, setQuiz, isAlreadyPlayed, setIs buttonText={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)} /> ))} - {/* Ajout du PlayButton à la fin */} <View style={styles.playButtonContainer}> <BlueButton onPress={() => quizState === QuizState.ANSWERING ? onValidation() : onContinue()} text={quizState === QuizState.ANSWERING ? "CONFIRM" : "CONTINUE"} buttonStyle={{borderRadius: 50}} isDisabled={false}/> </View> diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index 9da0130..1caa6bf 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -7,13 +7,18 @@ import GoHomeButton from "../../components/PlayingQuiz/GoHomeButton"; import {NavigationProp} from "@react-navigation/native"; import ConfirmModal from "../../components/PlayingQuiz/ComfirmModal"; import {useState} from "react"; +import {QuizInformations} from "../../models/QuizInformations"; +import {Question} from "../../models/Question"; interface Props { + quizInformations: QuizInformations; + runId?: string; + actualQuestion: Question; + score: number; navigation: NavigationProp<any> - quiz: Quiz; } -export default function PlayingQuizHeader({navigation, quiz}: Props) { +export default function PlayingQuizHeader({quizInformations, runId, actualQuestion, score, navigation}: Props) { const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); const {t} = useTranslation(); @@ -28,26 +33,24 @@ export default function PlayingQuizHeader({navigation, quiz}: Props) { }); } - if (quiz.questionCount <= quiz.questionIndex - 1) return; - return ( <View style={styles.container}> <View style={styles.header}> <GoHomeButton navigation={navigation} onPress={setIsConfirmModalVisible}/> - <Text style={styles.codeQuizText}>ID QUIZZ : {quiz ? quiz.id : "?"}</Text> + <Text style={styles.codeQuizText}>ID QUIZZ : {quizInformations ? quizInformations.id : "?"}</Text> </View> <View style={styles.questionAndScoreContainer}> <View style={styles.InformationsContainer}> <Text style={TextsStyles.titleText}>{t("app.screens.question.question")}</Text> - <Text style={TextsStyles.titleText}>{quiz ? quiz.questionIndex : "?"}/{quiz ? quiz.questionCount : "?"}</Text> + <Text style={TextsStyles.titleText}>{actualQuestion ? actualQuestion.order + 1 :"?"}/{quizInformations ? quizInformations.questionCount : "?"}</Text> </View> <View style={styles.InformationsContainer}> <Text style={TextsStyles.titleText}>{t("app.screens.question.score")}</Text> - <Text style={TextsStyles.titleText}>{quiz ? quiz.score : 0}/{quiz ? quiz.questionCount : "?"}</Text> + <Text style={TextsStyles.titleText}>{score}/{quizInformations ? quizInformations.questionCount : "?"}</Text> </View> </View> <View style={styles.QuizQuestionContainer}> - <Text style={TextsStyles.subtitleText}>{quiz.questions[quiz.questionIndex-1].text ? quiz.questions[quiz.questionIndex-1].text : "Loading..."}</Text> + <Text style={TextsStyles.subtitleText}>{actualQuestion ? actualQuestion.text : "Loading..."}</Text> </View> <ConfirmModal visible={isConfirmModalVisible} onClose={()=>setIsConfirmModalVisible(false)} onConfirm={handleConfirmModalConfirm}/> -- GitLab From f75d27b9a81c0aff5bca40e3a8faabc9dff923d7 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Thu, 9 Jan 2025 18:57:12 +0100 Subject: [PATCH 214/283] feat: add new models --- models/Quiz.ts | 2 +- models/QuizInformations.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 models/QuizInformations.ts diff --git a/models/Quiz.ts b/models/Quiz.ts index b5087c2..1acc2ce 100644 --- a/models/Quiz.ts +++ b/models/Quiz.ts @@ -1,6 +1,6 @@ import {Question} from "./Question"; -interface Difficulty { +export interface Difficulty { id: number; name: string; } diff --git a/models/QuizInformations.ts b/models/QuizInformations.ts new file mode 100644 index 0000000..32d4230 --- /dev/null +++ b/models/QuizInformations.ts @@ -0,0 +1,17 @@ +import UserModel from "./UserModel"; +import {Category, Difficulty} from "./Quiz"; + +export interface QuizInformations { + id: number; + name: string; + description: string; + isCommunity: boolean; + questionCount: number; + categoryId: number; + difficultyId: number; + authorId?: UserModel; + createdAt: string; + updatedAt: string; + category: Category; + difficulty: Difficulty; +} \ No newline at end of file -- GitLab From be67746e81b462f6ee8ddbacfc9c97a569b44f20 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 9 Jan 2025 21:12:46 +0100 Subject: [PATCH 215/283] fix: replace selectListBox --- .../OnlineQuiz/OnlineCreateLobby.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx index 177d4b6..73eb259 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx @@ -1,11 +1,13 @@ import { Text,View, StyleSheet } from "react-native"; -import SelectListBox from "../../../components/PlayQuiz/SelectListBox"; -import React, {useEffect} from "react"; +import React, {useEffect, useState} from "react"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import {getIdOfCategory} from "../../../helper/QuizHelper"; import {useQuizService} from "../../../services/QuizService"; import {NavigationProp} from "@react-navigation/native"; import BlueButton from "../../../components/buttons/BlueButton"; +import SelectListBoxString from "../../../components/PlayQuiz/SelectListBoxString"; +import SelectListBoxCategory from "../../../components/PlayQuiz/SelectListBoxCategory"; +import { Category } from "../../../models/Quiz"; const difficulty = [ { key: "easy", value: "easy" }, @@ -35,14 +37,15 @@ interface Props { export default function OnlineCreateLobby({navigation}: Props) { const [difficutlyChoose, setDifficutlyChoose] = React.useState(""); - const [categoryChoose, setCategoryChoose] = React.useState(""); + const [categories, setCategories] = useState<Category[]>([]); + const [categoryChoose, setCategoryChoose] = React.useState<Category | null>(null); const [nbQuestions, setNbQuestions] = React.useState("10"); const [nbPlayers, setNbPlayers] = React.useState("2"); const [buttonIsDisabled, setButtonIsDisabled] = React.useState(true); const {generateQuiz} = useQuizService(); useEffect(() => { - if(nbQuestions === "" || difficutlyChoose === "" || categoryChoose === "") + if(nbQuestions === "" || difficutlyChoose === "" || categoryChoose === null) { setButtonIsDisabled(true); } @@ -70,9 +73,9 @@ export default function OnlineCreateLobby({navigation}: Props) { <View style={styles.container}> <View style={styles.contentContainer}> <View style={styles.fieldContainer}> - <SelectListBox title="CHOOSE A DIFFICULTY" data={difficulty} setSelected={setDifficutlyChoose} /> - <SelectListBox title="CHOOSE A CATEGORY" data={categories} setSelected={setCategoryChoose} /> - <InputPlayQuiz title="NUMBER OF QUESTIONS" setText={setNbQuestions} numeric={true} defaultValue={nbQuestions}/> + <SelectListBoxString title="CHOOSE A DIFFICULTY" data={difficulty} setSelected={setDifficutlyChoose} /> + <SelectListBoxCategory title="CHOOSE A CATEGORY" data={categories} setSelected={setCategoryChoose} /> + <InputPlayQuiz title="CHOOSE A NUMBER OF QUESTIONS" setText={setNbQuestions} numeric={true} defaultValue={nbQuestions}/> <InputPlayQuiz title="NUMBER OF PLAYERS" setText={setNbPlayers} numeric={true} defaultValue={nbPlayers}/> </View> <View style={styles.buttonPlayContainer}> -- GitLab From db1390e8cd45a132efe5a55101f653f21259c38f Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 16:01:23 +0100 Subject: [PATCH 216/283] feat: add informations quiz for multiplayer community --- .../InformationsOfQuiz/InformationsOfQuiz.tsx | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz.tsx diff --git a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz.tsx new file mode 100644 index 0000000..fdbd797 --- /dev/null +++ b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -0,0 +1,98 @@ +import React from "react"; +import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import TemplateMenu from "../../../../templates/TemplateMenu"; +import {NavigationProp, RouteProp} from "@react-navigation/native"; +import {Quiz} from "../../../../models/Quiz"; + +type RoutePropsType = { + InformationsOfQuiz: { + quiz: Quiz; + }; +}; +interface Props { + navigation: NavigationProp<any>; + route: RouteProp<RoutePropsType, "InformationsOfQuiz">; +} + +export default function InformationsOfQuiz({ navigation, route }: Props) { + const { quiz } = route.params; + + const onCreateLobby = () => { + navigation.navigate('PlayingQuiz', {quizRecovered: quiz}); + } + + return ( + <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> + <View style={styles.container}> + <Text style={styles.quizTitle}>{quiz.name}</Text> + <Text style={styles.quizDescription}>{quiz.category?.name}</Text> + + <View style={styles.aboutContainer}> + <Text style={styles.aboutTitle}>About this quiz :</Text> + <Text style={styles.aboutDetails}>{quiz.questionCount} questions</Text> + <Text style={styles.aboutDetails}>By : Unknown</Text> + </View> + + <TouchableOpacity style={styles.playButton} onPress={onCreateLobby}> + <Text style={styles.playButtonText}>CREATE LOBBY</Text> + </TouchableOpacity> + </View> + </TemplateMenu> + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingHorizontal: 20, + paddingVertical: 20, + justifyContent: "flex-start", + backgroundColor: "#f4f4f4", + }, + quizTitle: { + fontSize: 24, + fontWeight: "bold", + color: "#00b3f4", + marginBottom: 10, + }, + quizDescription: { + fontSize: 16, + color: "#555", + marginBottom: 20, + }, + aboutContainer: { + backgroundColor: "white", + borderRadius: 10, + padding: 15, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 3, + elevation: 5, + }, + aboutTitle: { + fontSize: 18, + fontWeight: "bold", + marginBottom: 10, + color: "#000", + }, + aboutDetails: { + fontSize: 16, + color: "#555", + marginBottom: 5, + }, + playButton: { + backgroundColor: "#007BFF", + paddingVertical: 15, + borderRadius: 10, + alignItems: "center", + justifyContent: "center", + marginTop: 20, + }, + playButtonText: { + fontSize: 18, + fontWeight: "bold", + color: "white", + }, +}); -- GitLab From 00ea7ec1ec28aec02898333174033bcce42ac1e2 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 16:13:49 +0100 Subject: [PATCH 217/283] feat: remove modal and button createlobby --- .../MultiplayerCommunity.tsx | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx index 18dba4d..28ce183 100644 --- a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -17,7 +17,7 @@ export default function MultiplayerCommunity({navigation}: Props) { const {getCommunityQuiz, getRandomQuiz} = useQuizService(); const [input, setInput] = useState<string>("") const [loading, setLoading] = useState<boolean>(false) - const [showModal, setShowModal] = useState<boolean>(false); + const [quiz, setQuiz] = useState<Quiz | null>(null); const fetchCommunityQuizzes = async ({pageParam = 1}) => { @@ -29,22 +29,11 @@ export default function MultiplayerCommunity({navigation}: Props) { return quizzes; } - const onCreateLobbyPressed = () => { - setShowModal(true); - }; - const onQuizPressed = (quiz: Quiz) => { setQuiz(quiz); }; - const onClosePressed = () => { - setShowModal(false); - }; - - const onPlayPressed = async (nbPlayer: number) => { - setShowModal(false); - navigation.navigate("Lobby", {quiz: quiz, nbPlayer: nbPlayer}); - }; + const { @@ -66,13 +55,9 @@ export default function MultiplayerCommunity({navigation}: Props) { return ( <View style={styles.containerGlobal}> - <View style={styles.templateQuizListContainer}> + {/* <View style={styles.templateQuizListContainer}> */} <TemplateQuizList title={"Community"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={true} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> - </View> - <View style={styles.buttonContainer}> - <BlueButton text="CREATE LOBBY" onPress={onCreateLobbyPressed} isDisabled={false}/> - </View> - <ModalCreateLobby onClosePressed={onClosePressed} onPlayPressed={onPlayPressed} showModal={showModal}/> + {/* </View> */} </View> ); } -- GitLab From 06d6e7a9c7678928091181a9b41d804bdf539722 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 16:21:51 +0100 Subject: [PATCH 218/283] feat: add route to info quiz multi --- routes/StackNavigator.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index 2fbc28e..31856e8 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -13,6 +13,7 @@ import Lobby from "../screens/Multiplayer/Lobby/Lobby"; import PlayQuiz from "../screens/Home/PlayQuiz/PlayQuiz"; import MyQuizzes from "../screens/Home/MyQuizzes/MyQuizzes"; import InformationsOfQuiz from "../screens/Community/InformationsOfQuiz/InformationsOfQuiz"; +import MultiInformationsOfQuiz from "../screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz"; const Stack = createNativeStackNavigator(); @@ -34,6 +35,7 @@ export default function StackNavigator() { <Stack.Screen name="OnlineQuiz" component={OnlineQuiz} options={{ headerShown: false }}/> <Stack.Screen name="OngoingQuizzes" component={OngoingQuizzes} options={{ headerShown: false }}/> <Stack.Screen name="Lobby" component={Lobby} options={{ headerShown: false }}/> + <Stack.Screen name="MultiInformationsOfQuiz" component={MultiInformationsOfQuiz} options={{ headerShown: false }}/> </Stack.Navigator> </NavigationContainer> ); -- GitLab From d1bcec0cdbf686123a466c75761bc38da01a6c32 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 16:22:51 +0100 Subject: [PATCH 219/283] feat: change quiz pressed --- .../MultiplayerCommunity/MultiplayerCommunity.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx index 28ce183..dbc402b 100644 --- a/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/MultiplayerCommunity.tsx @@ -17,8 +17,6 @@ export default function MultiplayerCommunity({navigation}: Props) { const {getCommunityQuiz, getRandomQuiz} = useQuizService(); const [input, setInput] = useState<string>("") const [loading, setLoading] = useState<boolean>(false) - - const [quiz, setQuiz] = useState<Quiz | null>(null); const fetchCommunityQuizzes = async ({pageParam = 1}) => { const quizzes = await getCommunityQuiz(pageParam,7);//TODO: changer pour get les quiz community public @@ -30,12 +28,9 @@ export default function MultiplayerCommunity({navigation}: Props) { } const onQuizPressed = (quiz: Quiz) => { - setQuiz(quiz); + navigation.navigate("MultiInformationsOfQuiz", {quiz: quiz}); }; - - - const { data, fetchNextPage, -- GitLab From a19b3c112cd38ba4d216a3183b3fe4724a1cde1a Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 16:23:45 +0100 Subject: [PATCH 220/283] feat: add modal to information quiz --- .../InformationsOfQuiz/InformationsOfQuiz.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz.tsx index fdbd797..a4ffe4e 100644 --- a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -1,8 +1,9 @@ -import React from "react"; +import React, { useState } from "react"; import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; import TemplateMenu from "../../../../templates/TemplateMenu"; import {NavigationProp, RouteProp} from "@react-navigation/native"; import {Quiz} from "../../../../models/Quiz"; +import ModalCreateLobby from "../../../../components/Multiplayer/ModalCreateLobby"; type RoutePropsType = { InformationsOfQuiz: { @@ -14,13 +15,27 @@ interface Props { route: RouteProp<RoutePropsType, "InformationsOfQuiz">; } -export default function InformationsOfQuiz({ navigation, route }: Props) { +export default function MultiInformationsOfQuiz({ navigation, route }: Props) { const { quiz } = route.params; + const [showModal, setShowModal] = useState<boolean>(false); const onCreateLobby = () => { navigation.navigate('PlayingQuiz', {quizRecovered: quiz}); } + const onCreateLobbyPressed = () => { + setShowModal(true); + }; + + const onClosePressed = () => { + setShowModal(false); + }; + + const onPlayPressed = async (nbPlayer: number) => { + setShowModal(false); + navigation.navigate("MultiInformationsOfQuiz", {quiz: quiz, nbPlayer: nbPlayer}); + }; + return ( <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> <View style={styles.container}> @@ -33,10 +48,11 @@ export default function InformationsOfQuiz({ navigation, route }: Props) { <Text style={styles.aboutDetails}>By : Unknown</Text> </View> - <TouchableOpacity style={styles.playButton} onPress={onCreateLobby}> + <TouchableOpacity style={styles.playButton} onPress={onCreateLobbyPressed}> <Text style={styles.playButtonText}>CREATE LOBBY</Text> </TouchableOpacity> </View> + <ModalCreateLobby onClosePressed={onClosePressed} onPlayPressed={onPlayPressed} showModal={showModal}/> </TemplateMenu> ); -- GitLab From 5a824aa98cd54dd579b7809a8c19b09614dea8e7 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 16:29:20 +0100 Subject: [PATCH 221/283] refactor: rename file --- .../{InformationsOfQuiz.tsx => MultiInformationsOfQuiz.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/{InformationsOfQuiz.tsx => MultiInformationsOfQuiz.tsx} (100%) diff --git a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx similarity index 100% rename from screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz.tsx rename to screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx -- GitLab From 9263a718fe96c641e06c03a761aea5cdcc481016 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 16:34:48 +0100 Subject: [PATCH 222/283] refactor: change import file --- routes/StackNavigator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index 31856e8..377abc3 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -13,7 +13,7 @@ import Lobby from "../screens/Multiplayer/Lobby/Lobby"; import PlayQuiz from "../screens/Home/PlayQuiz/PlayQuiz"; import MyQuizzes from "../screens/Home/MyQuizzes/MyQuizzes"; import InformationsOfQuiz from "../screens/Community/InformationsOfQuiz/InformationsOfQuiz"; -import MultiInformationsOfQuiz from "../screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/InformationsOfQuiz"; +import MultiInformationsOfQuiz from "../screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz"; const Stack = createNativeStackNavigator(); -- GitLab From 8c30d5f7a2f7e40bc2d049dab076d087bf22e34d Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 16:35:11 +0100 Subject: [PATCH 223/283] feat: play pressed got to lobby --- .../InformationsOfQuiz/MultiInformationsOfQuiz.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx index a4ffe4e..ddf1fca 100644 --- a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx @@ -33,7 +33,7 @@ export default function MultiInformationsOfQuiz({ navigation, route }: Props) { const onPlayPressed = async (nbPlayer: number) => { setShowModal(false); - navigation.navigate("MultiInformationsOfQuiz", {quiz: quiz, nbPlayer: nbPlayer}); + navigation.navigate("Lobby", {quiz: quiz, nbPlayer: nbPlayer}); }; return ( -- GitLab From 1d8259bf1f832a0f5406fad48a38e3aa9854a859 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sat, 11 Jan 2025 16:35:18 +0100 Subject: [PATCH 224/283] feat: added users stats --- components/Profil/LineOfUserStat.tsx | 45 ++++++++++++++++++++++++ components/Profil/StatsOfUsers.tsx | 41 ++++++++++++++++++++++ helper/UserHelper.ts | 6 ++-- models/AnswerResponse.ts | 1 - models/QuizInformations.ts | 4 +-- models/User.ts | 12 +++++++ models/UserModel.ts | 12 ------- screens/Home/HomeChild.tsx | 1 + screens/PlayingQuiz/PlayingQuizBody.tsx | 14 ++++++-- screens/Profil/ProfilChild.tsx | 46 ++++++++++++++++++------- services/QuizService.ts | 17 +++++---- services/UserService.ts | 10 +++--- 12 files changed, 160 insertions(+), 49 deletions(-) create mode 100644 components/Profil/LineOfUserStat.tsx create mode 100644 components/Profil/StatsOfUsers.tsx create mode 100644 models/User.ts delete mode 100644 models/UserModel.ts diff --git a/components/Profil/LineOfUserStat.tsx b/components/Profil/LineOfUserStat.tsx new file mode 100644 index 0000000..9a718af --- /dev/null +++ b/components/Profil/LineOfUserStat.tsx @@ -0,0 +1,45 @@ +import {View, Text, StyleSheet} from "react-native"; + +export interface Props { + title: string; + value: string; + isLast?: boolean; +} + +export default function LineOfUserStat({title, value, isLast}: Props) { + return ( + <> + <View style={styles.container}> + <Text style={styles.textTitle}>{title} :</Text> + <Text style={styles.textValue}>{value}</Text> + </View> + {!isLast &&<View style={styles.separator} />} + </> + ); +} + +const styles = StyleSheet.create({ + container: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + width: "85%", + }, + textTitle: { + fontWeight: "bold", + fontSize: 20, + color: "#555", + }, + textValue: { + fontStyle: "normal", + fontWeight: "bold", + fontSize: 20, + color: "#00B3F4", + }, + separator: { + width: "90%", + height: 1, + backgroundColor: "#000", + marginVertical: 10, // Espacement entre la ligne et les stats + }, +}); \ No newline at end of file diff --git a/components/Profil/StatsOfUsers.tsx b/components/Profil/StatsOfUsers.tsx new file mode 100644 index 0000000..157097e --- /dev/null +++ b/components/Profil/StatsOfUsers.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import {View, Text, StyleSheet} from "react-native"; +import LineOfUserStat from "./LineOfUserStat"; +import {User} from "../../models/User"; + +interface Props { + user?: User; +} + +export default function StatsOfUsers({user}: Props) { + return ( + <View style={styles.container}> + <LineOfUserStat title="Quizzes created" value={user?.stats ? user.stats.quizzesCreated.toString() : "?"} /> + <LineOfUserStat title="Quizzes played" value={user?.stats ? user.stats.quizzesPlayed.toString() : "?"} /> + <LineOfUserStat title="Correct answers" value={user?.stats ? user.stats.correctAnswers.toString() : "?"} /> + <LineOfUserStat title="Total responses" value={user?.stats ? user.stats.totalAnswers.toString() : "?"} isLast={true}/> + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + display: "flex", + width: "80%", + height: "45%", + marginTop: "10%", + justifyContent: "space-around", + alignItems: "center", + backgroundColor: "#f9f9f9", + borderRadius: 15, + shadowColor: "#000", + shadowOffset: { + width: 0, + height: 4, + }, + shadowOpacity: 0.3, + shadowRadius: 5, + elevation: 5, + paddingVertical: 15, + }, +}); \ No newline at end of file diff --git a/helper/UserHelper.ts b/helper/UserHelper.ts index 123b556..cfacf0f 100644 --- a/helper/UserHelper.ts +++ b/helper/UserHelper.ts @@ -1,7 +1,7 @@ -import UserModel from "../models/UserModel"; +import User from "../models/User"; -export const transformToUserModel = (data: any): UserModel => { - const user: UserModel = { +export const transformToUserModel = (data: any): User => { + const user: User = { id: data.id, email: data.email, username: data.username, diff --git a/models/AnswerResponse.ts b/models/AnswerResponse.ts index 217cf6e..24a83f1 100644 --- a/models/AnswerResponse.ts +++ b/models/AnswerResponse.ts @@ -13,5 +13,4 @@ export interface AnswerResponse { answers: Answer[]; } category: Category; - } \ No newline at end of file diff --git a/models/QuizInformations.ts b/models/QuizInformations.ts index 32d4230..5087ebc 100644 --- a/models/QuizInformations.ts +++ b/models/QuizInformations.ts @@ -1,4 +1,4 @@ -import UserModel from "./UserModel"; +import User from "./User"; import {Category, Difficulty} from "./Quiz"; export interface QuizInformations { @@ -9,7 +9,7 @@ export interface QuizInformations { questionCount: number; categoryId: number; difficultyId: number; - authorId?: UserModel; + authorId?: User; createdAt: string; updatedAt: string; category: Category; diff --git a/models/User.ts b/models/User.ts new file mode 100644 index 0000000..96d2b30 --- /dev/null +++ b/models/User.ts @@ -0,0 +1,12 @@ +interface Stats { + quizzesCreated: number; + quizzesPlayed: number; + correctAnswers: number; + totalAnswers: number; +} +export interface User { + id: number; + email: string; + username: string; + stats: Stats; +} \ No newline at end of file diff --git a/models/UserModel.ts b/models/UserModel.ts deleted file mode 100644 index 66ecffe..0000000 --- a/models/UserModel.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default class UserModel { - id: number; - username: string; - email: string; - constructor(id: number, username: string, email: string) { - this.id = id; - this.username = username; - this.email = email; - } -} - - diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 22afb5d..1072366 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -37,6 +37,7 @@ export default function HomeChild({navigation}: Props) { params: {runId: quizData.id, quizId: quizData.quizId}, }, ] + }); } const handleButtonMyQuizzesPressed = () => { diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index b8d01b0..0992745 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -1,4 +1,4 @@ -import { View, StyleSheet, Text } from "react-native"; +import {View, StyleSheet, Text, ActivityIndicator} from "react-native"; import DefaultButton from "../../components/buttons/DefaultButton"; import { ButtonsStyles } from "../../styles/ButtonsStyles"; import React, { useState, useEffect, SetStateAction, Dispatch } from "react"; @@ -80,7 +80,7 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio if(selectedAnswerId === null) return; setQuizState(QuizState.LOADING); const answerId = actualQuestion.answers.find((answer) => answer.id === selectedAnswerId)?.id; - if(!answerId) return; + if(!answerId || !runId) return; const answereFetched = await answerQuestion(runId, actualQuestion.id, answerId); if (HttpError.isHttpError(answereFetched)) { @@ -134,7 +134,10 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio </View> </> ) : ( - <Text style={styles.loadingText}>Loading...</Text> + <View style={styles.loadingContainer}> + <ActivityIndicator size="large" color="#2b73fe" /> + <Text style={styles.loadingText}>Loading...</Text> + </View> )} </View> ); @@ -150,6 +153,11 @@ const styles = StyleSheet.create({ width: '100%', marginVertical: '3%', }, + loadingContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, playButtonContainer: { marginTop: 20, // Ajoute un espacement au-dessus du bouton Play width: '80%', // S'assure que le bouton prend toute la largeur disponible diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index 81208fc..dd96222 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -4,35 +4,57 @@ import ProfilModal from "./Modals/ProfilModal"; import React, { useEffect, useState } from "react"; import ProfilHeaderAccount from "./ProfilHeaderAccount"; import { ProfilSelectModeType } from "../../models/SelectModeType"; -import UserModel from "../../models/UserModel"; import { useUserService } from "../../services/UserService"; import HttpError from "../../services/HttpError"; import Icon from "react-native-vector-icons/MaterialIcons"; +import {User} from "../../models/User"; +import StatsOfUsers from "../../components/Profil/StatsOfUsers"; interface Props { navigation: NavigationProp<any>; } +enum UserConnectionState { + LOADING = "Loading", + NOT_CONNECTED = "NotConnected", + CONNECTED = "Connected", +} + export default function ProfilChild({ navigation }: Props) { + const [isLogged, setIsLogged] = useState(false); const [modalVisible, setModalVisible] = useState(false); const [mode, setMode] = useState<ProfilSelectModeType>("login"); - const [user, setUser] = useState<UserModel | null>(null); + const [user, setUser] = useState<User | undefined>(undefined); + const [userConnectionState, setUserConnectionState] = useState(UserConnectionState.LOADING); const { getInformationsUser, logoutUser } = useUserService(); - useEffect(() => { - getInformationsUser().then((user: UserModel | HttpError) => { - if (user instanceof HttpError) { - return; - } - setUser(user); + const getUser = async () => { + setUserConnectionState(UserConnectionState.LOADING); + const user = await getInformationsUser(); + console.log(user); + if(user instanceof HttpError){ + setUser(undefined); + setUserConnectionState(UserConnectionState.NOT_CONNECTED); return; - }); + } + setUserConnectionState(UserConnectionState.CONNECTED); + setUser(user); + } + + useEffect(() => { + getUser(); + }, []); + // Pour s'adapter quand l'état de connexion change + useEffect(() => { + getUser(); + }, [isLogged]); + const handleLogout = async () => { const result = await logoutUser(); if (!(result instanceof HttpError)) { - setUser(null); // Réinitialise l'utilisateur après la déconnexion + setUser(undefined); // Réinitialise l'utilisateur après la déconnexion } }; @@ -43,9 +65,6 @@ export default function ProfilChild({ navigation }: Props) { <Image source={require('../../assets/ProfilBaseImage.png')} style={styles.profilImage} /> <Text style={styles.pseudoText}>{user ? user.username : "?"}</Text> </View> - <View style={styles.containerPlayerStats}> - {/*<Text style={styles.statsText}>Played quiz : _123_</Text>*/} - </View> {!user && ( <ProfilHeaderAccount setModalVisible={setModalVisible} setMode={setMode} /> @@ -57,6 +76,7 @@ export default function ProfilChild({ navigation }: Props) { setMode={setMode} navigation={navigation} /> + <StatsOfUsers user={user}/> {user && ( <TouchableOpacity style={styles.logoutButton} onPress={handleLogout}> diff --git a/services/QuizService.ts b/services/QuizService.ts index f65804a..72752c8 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -29,7 +29,7 @@ export const useQuizService = () => { // Récupération du run ID return runId; } catch (error: any) { - console.error("Error while generating quiz:", error); + console.log("Error while generating quiz:", error); if (error.response) { // Gestion des erreurs HTTP @@ -55,7 +55,7 @@ export const useQuizService = () => { return response.data.question; } catch (error: any) { - console.error("Error while remaining quiz:", error); + console.log("Error while remaining quiz:", error); if (error.response) { // Gestion des erreurs HTTP @@ -77,7 +77,7 @@ export const useQuizService = () => { }); return response.data; } catch (error: any) { - console.error("Error while getQuizInformations quiz:", error); + console.log("Error while getQuizInformations quiz:", error); if (error.response) { // Gestion des erreurs HTTP @@ -89,7 +89,7 @@ export const useQuizService = () => { } } - const answerQuestion = async (quizId: string, questionId: number, answerId: string): Promise<AnswerResponse | HttpError> => { + const answerQuestion = async (quizId: string, questionId: number, answerId: number): Promise<AnswerResponse | HttpError> => { const requestBody = { id: quizId, questionID: questionId, @@ -105,7 +105,7 @@ export const useQuizService = () => { return response.data; } catch (error: any) { - console.error("Error while remaining quiz:", error); + console.log("Error while remaining quiz:", error); if (error.response) { // Gestion des erreurs HTTP @@ -132,7 +132,7 @@ export const useQuizService = () => { // Récupération des données retournées par l'API return shuffleAnswersOfQuiz(response.data); } catch (error: any) { - console.error("Error while remaining quiz:", error); + console.log("Error while remaining quiz:", error); if (error.response) { // Gestion des erreurs HTTP @@ -171,7 +171,7 @@ export const useQuizService = () => { return answers; } catch (error: any) { - console.error("Error while fetching answers:", error); + console.log("Error while fetching answers:", error); if (error.response) { // Gestion des erreurs HTTP @@ -200,7 +200,7 @@ export const useQuizService = () => { // Récupération du run ID return {id: runId, quizId: response.data.quizId}; } catch (error: any) { - console.error("Error while generating quiz:", error); + console.log("Error while generating quiz:", error); if (error.response) { // Gestion des erreurs HTTP @@ -218,7 +218,6 @@ export const useQuizService = () => { withCredentials: true, // Inclut les cookies pour une session authentifiée }); - return response.data; } catch (error: any) { diff --git a/services/UserService.ts b/services/UserService.ts index 08399a9..374a311 100644 --- a/services/UserService.ts +++ b/services/UserService.ts @@ -1,5 +1,5 @@ import HttpError from "./HttpError"; -import UserModel from "../models/UserModel"; +import User from "../models/User"; import axios from "axios"; import {transformToUserModel} from "../helper/UserHelper"; @@ -35,15 +35,13 @@ export const useUserService = () => { } }; - const getInformationsUser = async (): Promise<UserModel | HttpError> => { + const getInformationsUser = async (): Promise<User | HttpError> => { try { - const response = await axios.get(`${url}/user/me`, { + const response = await axios.get<User>(`${url}/user/me`, { withCredentials: true, // Inclut les cookies dans la requête }); - const user = transformToUserModel(response.data); - - return user; + return response.data; } catch (error: any) { if (error.response.status === 401) { return new HttpError(401, "No user logged in"); -- GitLab From 6dd1ff56e04bfb96cddb19ee53c2d3dd14055ed1 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 17:24:20 +0100 Subject: [PATCH 225/283] styles: show background --- screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx index a65b874..684af20 100644 --- a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -38,7 +38,6 @@ export default function InformationsOfQuiz({ navigation, route }: Props) { </TouchableOpacity> </View> </TemplateMenu> - ); } @@ -48,7 +47,6 @@ const styles = StyleSheet.create({ paddingHorizontal: 20, paddingVertical: 20, justifyContent: "flex-start", - backgroundColor: "#f4f4f4", }, quizTitle: { fontSize: 24, -- GitLab From 235c9712e4c6e60dc0f8906b33012d6e96d8d70d Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 17:27:57 +0100 Subject: [PATCH 226/283] feat: add description of the quiz --- screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx index 684af20..7c95429 100644 --- a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -25,7 +25,7 @@ export default function InformationsOfQuiz({ navigation, route }: Props) { <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> <View style={styles.container}> <Text style={styles.quizTitle}>{quiz.name}</Text> - <Text style={styles.quizDescription}>{quiz.category?.name}</Text> + <Text style={styles.quizDescription}>{quiz.description && "No Descripion"}</Text> <View style={styles.aboutContainer}> <Text style={styles.aboutTitle}>About this quiz :</Text> -- GitLab From cabac7895a931bc15662f39798bd3de5c51bf5ee Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 17:37:59 +0100 Subject: [PATCH 227/283] feat: add info for quiz --- screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx index 7c95429..1aa7bcc 100644 --- a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -29,8 +29,8 @@ export default function InformationsOfQuiz({ navigation, route }: Props) { <View style={styles.aboutContainer}> <Text style={styles.aboutTitle}>About this quiz :</Text> - <Text style={styles.aboutDetails}>{quiz.questionCount} questions</Text> - <Text style={styles.aboutDetails}>By : Unknown</Text> + <Text style={styles.aboutDetails}>{quiz.questionCount} questions, {quiz.difficulty.name}</Text> + <Text style={styles.aboutDetails}>By : {quiz.authorId && "Unknown"}</Text> </View> <TouchableOpacity style={styles.playButton} onPress={onPlay}> -- GitLab From 1c7ba2ef6b3b8224201d759e1a74fb21a31185c2 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 17:39:12 +0100 Subject: [PATCH 228/283] refactor: delete unused function --- .../InformationsOfQuiz/MultiInformationsOfQuiz.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx index ddf1fca..623305e 100644 --- a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx @@ -18,11 +18,7 @@ interface Props { export default function MultiInformationsOfQuiz({ navigation, route }: Props) { const { quiz } = route.params; const [showModal, setShowModal] = useState<boolean>(false); - - const onCreateLobby = () => { - navigation.navigate('PlayingQuiz', {quizRecovered: quiz}); - } - + const onCreateLobbyPressed = () => { setShowModal(true); }; -- GitLab From 4a93b05640f46d9df466e45aff4fcff8c0f0d182 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 17:39:33 +0100 Subject: [PATCH 229/283] styles: show background --- .../InformationsOfQuiz/MultiInformationsOfQuiz.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx index 623305e..0f50a8c 100644 --- a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx @@ -18,7 +18,7 @@ interface Props { export default function MultiInformationsOfQuiz({ navigation, route }: Props) { const { quiz } = route.params; const [showModal, setShowModal] = useState<boolean>(false); - + const onCreateLobbyPressed = () => { setShowModal(true); }; @@ -60,7 +60,6 @@ const styles = StyleSheet.create({ paddingHorizontal: 20, paddingVertical: 20, justifyContent: "flex-start", - backgroundColor: "#f4f4f4", }, quizTitle: { fontSize: 24, -- GitLab From d3f5712177ae2105867ab35902e2a70a85bd200e Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 17:41:16 +0100 Subject: [PATCH 230/283] feat: add information to quiz detail --- .../Community/InformationsOfQuiz/InformationsOfQuiz.tsx | 2 +- .../InformationsOfQuiz/MultiInformationsOfQuiz.tsx | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx index 1aa7bcc..437c944 100644 --- a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -25,7 +25,7 @@ export default function InformationsOfQuiz({ navigation, route }: Props) { <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> <View style={styles.container}> <Text style={styles.quizTitle}>{quiz.name}</Text> - <Text style={styles.quizDescription}>{quiz.description && "No Descripion"}</Text> + <Text style={styles.quizDescription}>{quiz.description && "No Description"}</Text> <View style={styles.aboutContainer}> <Text style={styles.aboutTitle}>About this quiz :</Text> diff --git a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx index 0f50a8c..23b2a4a 100644 --- a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx @@ -36,12 +36,13 @@ export default function MultiInformationsOfQuiz({ navigation, route }: Props) { <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> <View style={styles.container}> <Text style={styles.quizTitle}>{quiz.name}</Text> - <Text style={styles.quizDescription}>{quiz.category?.name}</Text> + <Text style={styles.quizDescription}>{quiz.description && "No Description"}</Text> + <View style={styles.aboutContainer}> <Text style={styles.aboutTitle}>About this quiz :</Text> - <Text style={styles.aboutDetails}>{quiz.questionCount} questions</Text> - <Text style={styles.aboutDetails}>By : Unknown</Text> + <Text style={styles.aboutDetails}>{quiz.questionCount} questions, {quiz.difficulty.name}</Text> + <Text style={styles.aboutDetails}>By : {quiz.authorId && "Unknown"}</Text> </View> <TouchableOpacity style={styles.playButton} onPress={onCreateLobbyPressed}> -- GitLab From a147d5b8f0a033bcb40dd5e2cba572ab5bbf87f7 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 17:53:22 +0100 Subject: [PATCH 231/283] styles: change the size of detail container --- screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx | 4 +++- .../InformationsOfQuiz/MultiInformationsOfQuiz.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx index 437c944..3e6461c 100644 --- a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -58,8 +58,10 @@ const styles = StyleSheet.create({ fontSize: 16, color: "#555", marginBottom: 20, + height: "15%", }, aboutContainer: { + height: "50%", backgroundColor: "white", borderRadius: 10, padding: 15, @@ -78,7 +80,7 @@ const styles = StyleSheet.create({ aboutDetails: { fontSize: 16, color: "#555", - marginBottom: 5, + marginBottom: 10, }, playButton: { backgroundColor: "#007BFF", diff --git a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx index 23b2a4a..8f45d5c 100644 --- a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx @@ -72,8 +72,10 @@ const styles = StyleSheet.create({ fontSize: 16, color: "#555", marginBottom: 20, + height: "15%", }, aboutContainer: { + height: "50%", backgroundColor: "white", borderRadius: 10, padding: 15, @@ -92,7 +94,7 @@ const styles = StyleSheet.create({ aboutDetails: { fontSize: 16, color: "#555", - marginBottom: 5, + marginBottom: 10, }, playButton: { backgroundColor: "#007BFF", -- GitLab From aa6fba8a7afedabc2dce82d86410e3d5288a90e6 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 20:45:42 +0100 Subject: [PATCH 232/283] refactor: rename answerButton -> answerButtonText --- components/buttons/{AnswerButton.tsx => AnswerButtonText.tsx} | 4 ++-- screens/PlayingQuiz/PlayingQuizBody.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename components/buttons/{AnswerButton.tsx => AnswerButtonText.tsx} (86%) diff --git a/components/buttons/AnswerButton.tsx b/components/buttons/AnswerButtonText.tsx similarity index 86% rename from components/buttons/AnswerButton.tsx rename to components/buttons/AnswerButtonText.tsx index 2cc1fb6..6dbc8b4 100644 --- a/components/buttons/AnswerButton.tsx +++ b/components/buttons/AnswerButtonText.tsx @@ -11,13 +11,13 @@ interface Props{ } /** - * AnswerButton : Un bouton par défaut, il possède déjà un style mais on peut le surcharger grâce à la propriété buttonStyle + * AnswerButtonText : Un bouton par défaut, il possède déjà un style mais on peut le surcharger grâce à la propriété buttonStyle * @param text - Texte du bouton * @param handleButtonPressed - Fonction qui sera exécutée lorsque le bouton sera pressé * @param buttonStyle - Surcharge du style du bouton * @param buttonText - Surcharge du style du texte du bouton */ -export default function AnswerButton({text, handleButtonPressed, buttonStyle, buttonText, indicator}: Props){ +export default function AnswerButtonText({text, handleButtonPressed, buttonStyle, buttonText, indicator}: Props){ return( <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> {indicator !== undefined && ( diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 0992745..5a3fd66 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -9,7 +9,7 @@ import BlueButton from "../../components/buttons/BlueButton"; import {Question} from "../../models/Question"; import {Answer} from "../../models/Answer"; import {Quiz} from "../../models/Quiz"; -import AnswerButton from "../../components/buttons/AnswerButton"; +import AnswerButtonText from "../../components/buttons/AnswerButtonText"; import {QuizInformations} from "../../models/QuizInformations"; interface Props { @@ -121,7 +121,7 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio {actualQuestion ? ( <> {actualQuestion.answers.map((answer, index) => ( - <AnswerButton + <AnswerButtonText key={index} text={answer.text} handleButtonPressed={() => onAnsweredButtonClicked(answer.id)} -- GitLab From 70190d7af9b0562e628a878615d25948a3abc2fc Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 20:58:24 +0100 Subject: [PATCH 233/283] feat: add answerButtonImage --- components/buttons/AnswerButtonImage.tsx | 61 ++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 components/buttons/AnswerButtonImage.tsx diff --git a/components/buttons/AnswerButtonImage.tsx b/components/buttons/AnswerButtonImage.tsx new file mode 100644 index 0000000..3aaca00 --- /dev/null +++ b/components/buttons/AnswerButtonImage.tsx @@ -0,0 +1,61 @@ +import {TouchableOpacity, Text, StyleSheet, View, Image} from "react-native"; +import {ButtonsStyles} from "../../styles/ButtonsStyles"; +import {TextsStyles} from "../../styles/TextsStyles"; + +interface Props{ + url: string; + handleButtonPressed:() => void; + buttonStyle?: any; + buttonText?: any; + indicator?: number | undefined; +} + +/** + * AnswerButtonImage : Un bouton par défaut, il possède déjà un style mais on peut le surcharger grâce à la propriété buttonStyle + * @param url - Texte du bouton + * @param handleButtonPressed - Fonction qui sera exécutée lorsque le bouton sera pressé + * @param buttonStyle - Surcharge du style du bouton + * @param buttonText - Surcharge du style du texte du bouton + */ +export default function AnswerButtonImage({url, handleButtonPressed, buttonStyle, buttonText, indicator}: Props){ + return( + <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> + {indicator !== undefined && ( + <View style={styles.indicator}> + <Text style={styles.indicatorText}>{indicator}</Text> + </View> + )} + <Image + style={[TextsStyles.defaultButtonText, buttonText]} + source={{uri: url}} + /> + </TouchableOpacity> + ) +} + +const styles = StyleSheet.create({ + defaultButton: { + backgroundColor: "#45128C", + padding: 10, + alignItems: "center", + justifyContent: 'center', + borderRadius: 10, + }, + indicator: { + position: "absolute", + top: "-16%", + left: "5%", + width: 25, + height: 25, + borderRadius: 15, + backgroundColor: "#D3D3D3", + justifyContent: "center", + alignItems: "center", + zIndex: 1, + }, + indicatorText: { + color: "#000", + fontWeight: "bold", + fontSize: 14, + }, +}) \ No newline at end of file -- GitLab From 7f2622ff0b24cdde7c41a145ecd00a10c96b3b0d Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 22:02:08 +0100 Subject: [PATCH 234/283] style: fix size header and body for playing quiz --- templates/TemplateDuo.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/TemplateDuo.tsx b/templates/TemplateDuo.tsx index 97a225e..c347a2d 100644 --- a/templates/TemplateDuo.tsx +++ b/templates/TemplateDuo.tsx @@ -31,11 +31,11 @@ const styles = StyleSheet.create({ }, containerHeader: { width: '100%', - height: '26%', + height: '30%', }, containerBody: { width: '100%', - height: '78%', + height: '70%', backgroundColor: '#FFFFFF', borderTopLeftRadius: 46, borderTopRightRadius: 46 -- GitLab From d80ca1986ab223772545f7d11f54b75403d18f71 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 22:50:06 +0100 Subject: [PATCH 235/283] styles: show images answerButtonImage --- components/buttons/AnswerButtonImage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/buttons/AnswerButtonImage.tsx b/components/buttons/AnswerButtonImage.tsx index 3aaca00..bf75c90 100644 --- a/components/buttons/AnswerButtonImage.tsx +++ b/components/buttons/AnswerButtonImage.tsx @@ -6,7 +6,7 @@ interface Props{ url: string; handleButtonPressed:() => void; buttonStyle?: any; - buttonText?: any; + buttonImage?: any; indicator?: number | undefined; } @@ -17,7 +17,7 @@ interface Props{ * @param buttonStyle - Surcharge du style du bouton * @param buttonText - Surcharge du style du texte du bouton */ -export default function AnswerButtonImage({url, handleButtonPressed, buttonStyle, buttonText, indicator}: Props){ +export default function AnswerButtonImage({url, handleButtonPressed, buttonStyle, buttonImage, indicator}: Props){ return( <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> {indicator !== undefined && ( @@ -26,7 +26,7 @@ export default function AnswerButtonImage({url, handleButtonPressed, buttonStyle </View> )} <Image - style={[TextsStyles.defaultButtonText, buttonText]} + style={[TextsStyles.defaultButtonText, {flex: 1, width: "100%", height: "100%"}, buttonImage]} source={{uri: url}} /> </TouchableOpacity> -- GitLab From 52d677736ec14cc885ba21d014d03419c1999bd0 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 22:53:31 +0100 Subject: [PATCH 236/283] feat: add answer button image --- screens/PlayingQuiz/PlayingQuizBody.tsx | 40 ++++++++++++++++++------- styles/ButtonsStyles.ts | 2 +- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 5a3fd66..7fe64e5 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -11,6 +11,7 @@ import {Answer} from "../../models/Answer"; import {Quiz} from "../../models/Quiz"; import AnswerButtonText from "../../components/buttons/AnswerButtonText"; import {QuizInformations} from "../../models/QuizInformations"; +import AnswerButtonImage from "../../components/buttons/AnswerButtonImage"; interface Props { quizInformations: QuizInformations; @@ -120,15 +121,26 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio <View style={styles.buttonContainer}> {actualQuestion ? ( <> - {actualQuestion.answers.map((answer, index) => ( - <AnswerButtonText - key={index} - text={answer.text} - handleButtonPressed={() => onAnsweredButtonClicked(answer.id)} - buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)} - buttonText={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)} - /> - ))} + <View style={styles.buttonListContainer}> + {actualQuestion.answers.map((answer, index) => ( + actualQuestion.type === "image" ? + <AnswerButtonImage + key={index} + url={answer.text} + handleButtonPressed={() => onAnsweredButtonClicked(answer.id)} + buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)} + // buttonImage={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)} + /> + : + <AnswerButtonText + key={index} + text={answer.text} + handleButtonPressed={() => onAnsweredButtonClicked(answer.id)} + buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)} + buttonText={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)} + /> + ))} + </View> <View style={styles.playButtonContainer}> <BlueButton onPress={() => quizState === QuizState.ANSWERING ? onValidation() : onContinue()} text={quizState === QuizState.ANSWERING ? "CONFIRM" : "CONTINUE"} buttonStyle={{borderRadius: 50}} isDisabled={false}/> </View> @@ -201,7 +213,7 @@ const styles = StyleSheet.create({ }, answerButton: { backgroundColor: "#F3F3F3", - height: '15%', + height: '30%', alignItems: 'center', width: '80%', marginVertical: '3%', @@ -212,5 +224,13 @@ const styles = StyleSheet.create({ shadowRadius: 3.5, elevation: 5, }, + buttonListContainer: { + height: '80%', + width: '100%', + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + overflow: 'scroll', + } }); diff --git a/styles/ButtonsStyles.ts b/styles/ButtonsStyles.ts index 5d33ddb..12d8539 100644 --- a/styles/ButtonsStyles.ts +++ b/styles/ButtonsStyles.ts @@ -16,7 +16,7 @@ export const ButtonsStyles = StyleSheet.create( { }, answerButton: { backgroundColor: "#F3F3F3", - height: '15%', + height: '30%', alignItems: 'center', width: '80%', marginVertical: '3%', -- GitLab From a25e07fdd3d26e8715874def6b2dcb691d337d6b Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sat, 11 Jan 2025 22:57:00 +0100 Subject: [PATCH 237/283] fix: generate quiz --- models/QuizInformations.ts | 2 +- models/User.ts | 2 +- package-lock.json | 105 +++++++++++++++++- package.json | 5 +- screens/Home/HomeChild.tsx | 1 - .../Home/PlayQuiz/PlayQuizGenerateQuiz.tsx | 23 +--- screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx | 2 - screens/Profil/ProfilChild.tsx | 1 - services/QuizService.ts | 7 +- 9 files changed, 115 insertions(+), 33 deletions(-) diff --git a/models/QuizInformations.ts b/models/QuizInformations.ts index 5087ebc..32b9aed 100644 --- a/models/QuizInformations.ts +++ b/models/QuizInformations.ts @@ -1,5 +1,5 @@ -import User from "./User"; import {Category, Difficulty} from "./Quiz"; +import {User} from "./User"; export interface QuizInformations { id: number; diff --git a/models/User.ts b/models/User.ts index 96d2b30..e572420 100644 --- a/models/User.ts +++ b/models/User.ts @@ -8,5 +8,5 @@ export interface User { id: number; email: string; username: string; - stats: Stats; + stats?: Stats; } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 74090c7..d1035b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@tanstack/react-query": "^5.62.8", "@tanstack/react-query-devtools": "^5.62.8", "axios": "^1.7.9", + "dotenv": "^16.4.7", "expo": "~52.0.14", "expo-splash-screen": "^0.29.13", "expo-status-bar": "~2.0.0", @@ -31,7 +32,9 @@ "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", "react-native-vector-icons": "^10.2.0", - "react-query": "^3.39.3" + "react-query": "^3.39.3", + "rxjs": "^7.8.1", + "socket.io-client": "^4.8.1" }, "devDependencies": { "@babel/core": "^7.20.0", @@ -3971,6 +3974,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@tanstack/query-core": { "version": "5.62.8", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.8.tgz", @@ -5736,9 +5745,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -5804,6 +5813,49 @@ "once": "^1.4.0" } }, + "node_modules/engine.io-client": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz", + "integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -10168,6 +10220,15 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -10527,6 +10588,34 @@ "node": ">=8.0.0" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -11773,6 +11862,14 @@ "node": ">=8.0" } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index c2f6b34..7a0c0d2 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@tanstack/react-query": "^5.62.8", "@tanstack/react-query-devtools": "^5.62.8", "axios": "^1.7.9", + "dotenv": "^16.4.7", "expo": "~52.0.14", "expo-splash-screen": "^0.29.13", "expo-status-bar": "~2.0.0", @@ -32,7 +33,9 @@ "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", "react-native-vector-icons": "^10.2.0", - "react-query": "^3.39.3" + "react-query": "^3.39.3", + "rxjs": "^7.8.1", + "socket.io-client": "^4.8.1" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 1072366..85120e4 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -24,7 +24,6 @@ export default function HomeChild({navigation}: Props) { const handleButtonQuickGamePressed = async () => { const quizData = await getRandomQuiz() - console.log(quizData); if (quizData instanceof HttpError) { console.error("Error while fetching random quiz:", quizData); return; diff --git a/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx b/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx index c55cf39..8f72775 100644 --- a/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx +++ b/screens/Home/PlayQuiz/PlayQuizGenerateQuiz.tsx @@ -7,6 +7,7 @@ import {useQuizService} from "../../../services/QuizService"; import {NavigationProp} from "@react-navigation/native"; import {Category} from "../../../models/Quiz"; import SelectListBoxCategory from "../../../components/PlayQuiz/SelectListBoxCategory"; +import HttpError from "../../../services/HttpError"; const difficulty = [ { key: "easy", value: "easy" }, @@ -14,22 +15,6 @@ const difficulty = [ { key: "hard", value: "hard" }, ]; -const categories = [ - { key: "9", value: "General Knowledge"}, - { key: "10", value: "Entertainment: Books" }, - { key: "11", value: "Entertainment: Film" }, - { key: "12", value: "Entertainment: Music"}, - { key: "13", value: "Entertainment: Musicals & Theatres" }, - { key: "14", value: "Entertainment: Television" }, - { key: "15", value: "Entertainment: Video Games" }, - { key: "16", value: "Entertainment: Board Games" }, - { key: "17", value: "Science & Nature" }, - { key: "18", value: "Science: Computers" }, - { key: "19", value: "Science: Mathematics" }, - { key: "20", value: "Mythology" }, - { key: "21", value: "Sports" }, - { key: "22", value: "Geography" }, -]; interface Props { navigation: NavigationProp<any>; } @@ -44,7 +29,6 @@ export default function PlayQuizGenerateQuiz({navigation}: Props) { const {generateQuiz, getCategories} = useQuizService(); const fetchingCategories = async () => { const categories = await getCategories(); - console.log(categories); let categoriesTemp: Category[] = []; categories.map((category: Category) => { categoriesTemp.push({'name': category.name, 'id': category.id}); @@ -70,12 +54,15 @@ export default function PlayQuizGenerateQuiz({navigation}: Props) { const handlePlayButtonPress = async () => { const quizGenerated = await generateQuiz(parseInt(nbQuestions), categoryChoose ? categoryChoose.id : 10 , difficutlyChoose); + if(HttpError.isHttpError(quizGenerated)) { + return; + } navigation.reset({ index: 0, routes: [ { name: "PlayingQuiz", - params: {quizRecovered: quizGenerated}, + params: {runId: quizGenerated.id, quizId: quizGenerated.quizId}, }, ] }); diff --git a/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx index 2d8bafc..f5e49a4 100644 --- a/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx +++ b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx @@ -19,7 +19,6 @@ export default function EndQuizChild({ navigation, quiz }: Props) { const handleRestartPress = async () => { const isRestarted = await restartQuiz(quiz.id); if(HttpError.isHttpError(isRestarted)){ - console.log(isRestarted.message); return } if(!isRestarted) { @@ -27,7 +26,6 @@ export default function EndQuizChild({ navigation, quiz }: Props) { } const quizRestarted = await remainingQuiz(quiz.id); if(HttpError.isHttpError(quizRestarted)){ - console.log(quizRestarted.message); return } diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index dd96222..dd89a53 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -31,7 +31,6 @@ export default function ProfilChild({ navigation }: Props) { const getUser = async () => { setUserConnectionState(UserConnectionState.LOADING); const user = await getInformationsUser(); - console.log(user); if(user instanceof HttpError){ setUser(undefined); setUserConnectionState(UserConnectionState.NOT_CONNECTED); diff --git a/services/QuizService.ts b/services/QuizService.ts index 72752c8..2d7eb5c 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -12,7 +12,7 @@ import {QuizGenerateAnswer} from "../models/QuizGenerateAnswer"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API - const generateQuiz = async (amount: number, category: number, difficulty: string) : Promise<string | HttpError> => { + const generateQuiz = async (amount: number, category: number, difficulty: string) : Promise<QuizGenerateAnswer | HttpError> => { const requestBody = { amount, category, @@ -20,14 +20,13 @@ export const useQuizService = () => { }; try { - const response = await axios.post(`${url}/quiz/generate`, requestBody, { + const response = await axios.post<QuizGenerateAnswer>(`${url}/quiz/generate`, requestBody, { headers: { "Content-Type": "application/json", }, }); const runId = response.data.id; - // Récupération du run ID - return runId; + return {id: runId, quizId: response.data.quizId}; } catch (error: any) { console.log("Error while generating quiz:", error); -- GitLab From 257b2577c9ff1d7c63521999459a7b4f47efd590 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sat, 11 Jan 2025 22:59:36 +0100 Subject: [PATCH 238/283] feat: strating websocket --- screens/Multiplayer/Lobby/Lobby.tsx | 90 +++++++++++++------ .../OnlineQuiz/OnlineCreateLobby.tsx | 51 +++++------ 2 files changed, 86 insertions(+), 55 deletions(-) diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index 6bc6cd4..2f9a512 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -1,43 +1,77 @@ import { View, Text, StyleSheet, Button, Image, ScrollView, FlatList } from "react-native"; import TemplateMenu from "../../../templates/TemplateMenu"; -import { NavigationProp } from "@react-navigation/native"; -import React, { useState } from "react"; +import {NavigationProp, RouteProp} from "@react-navigation/native"; +import React, {useEffect, useState} from "react"; import WhiteButton from "../../../components/buttons/WhiteButton"; import BlueButton from "../../../components/buttons/BlueButton"; import { TouchableOpacity } from "react-native"; import ModalInvitePlayer from "../../../components/Multiplayer/ModalInvitePlayer"; +import {User} from "../../../models/User"; +import {QuizGenerateAnswer} from "../../../models/QuizGenerateAnswer"; +import {io, Socket} from "socket.io-client"; interface Props { navigation: NavigationProp<any>; + route: RouteProp<RoutePropsType, "Lobby">; } -export default function Lobby({navigation}: Props) { +type RoutePropsType = { + Lobby: { + runId: string; + nbPlayers: number; + }; +}; + +export default function Lobby({navigation, route}: Props) { + const { runId, nbPlayers} = route.params; + const [isHost, setIsHost] = useState<boolean>(true); const [showModal, setShowModal] = useState<boolean>(false); + const [players, setPlayers] = useState<User[]>([{ id: 0, username: 'Invite', email: 'invite@email.com'}]); + + + + const [socket, setSocket] = useState<Socket | null>(null); + + useEffect(() => { + // Initialise Socket.IO connection + const newSocket = io("http://klebert-host.com:33053/party", { + transports: ["websocket"], // Utilise WebSocket uniquement + withCredentials: true + }); + + setSocket(newSocket); - const players = [ - { id: 0, name: 'Invite'}, - { id: 1, name: 'John Doe'}, - { id: 2, name: 'John Doe'}, - { id: 3, name: 'John Doe'}, - { id: 4, name: 'John Doe'}, - { id: 5, name: 'John Doe'}, - { id: 6, name: 'John Doe'}, - { id: 7, name: 'John Doe'}, - { id: 8, name: 'John Doe'}, - { id: 9, name: 'John Doe'}, - { id: 10, name: 'John Doe'}, - { id: 11, name: 'John Doe'}, - { id: 12, name: 'John Doe'}, - { id: 13, name: 'John Doe'}, - { id: 14, name: 'John Doe'}, - { id: 15, name: 'John Doe'}, - ]; - - const item = ({item}: {item: {id: number, name: string}}) => { + newSocket.on("connect", () => { + console.log("Connected to the socket server id :",runId); + newSocket.emit("joinRoom", { roomId: runId }); + }); + + newSocket.on("connect_error", (err) => { + console.error("Connection error: ", err.message); + }); + + newSocket.on("error", (err) => { + console.error("Socket error: ", err.message); + }); + + // Mettez à jour la liste des joueurs lorsque quelqu'un rejoint + newSocket.on("userJoined", (data: User[]) => { + console.log("Updated user list:", data); + setPlayers(data); + }); + + // Nettoyez la connexion lorsque le composant est démonté + return () => { + newSocket.disconnect(); + setSocket(null); + }; + }, [runId]); + + const item = ({ item }: { item: User }) => { return ( <> - {item.name === 'Invite' ? + {item.username === 'Invite' ? <View style={styles.PlayerItem}> <TouchableOpacity style={styles.imagePlayer} onPress={onInvitePressed}> <Image source={require('../../../assets/add.png')} /> @@ -47,11 +81,11 @@ export default function Lobby({navigation}: Props) { : <View style={styles.PlayerItem}> <Image source={require('../../../assets/ProfilBaseImage.png')} style={styles.imagePlayer} /> - <Text style={styles.textPlayer}>{item.name}</Text> + <Text style={styles.textPlayer}>{item.username}</Text> </View> } </> - + ) } @@ -75,7 +109,7 @@ export default function Lobby({navigation}: Props) { return ( <View> <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> - <View style={styles.containerLobby}> + <View style={styles.containerLobby}> <Text style={styles.title}>{'BMW OFFICIAL QUIZ (I’m leaking oil)'}</Text> <View style={styles.containerPlayers}> <FlatList @@ -90,7 +124,7 @@ export default function Lobby({navigation}: Props) { <View style={styles.buttonGroup}> <View style={styles.button}> <BlueButton text="PLAY" onPress={onPlayPressed} isDisabled={false}/> - </View> + </View> <View style={styles.button}> <WhiteButton text="CANCEL" onPress={onCancelPressed} isDisabled={false}/> </View> diff --git a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx index 73eb259..c31decb 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx @@ -1,13 +1,13 @@ import { Text,View, StyleSheet } from "react-native"; import React, {useEffect, useState} from "react"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; -import {getIdOfCategory} from "../../../helper/QuizHelper"; import {useQuizService} from "../../../services/QuizService"; import {NavigationProp} from "@react-navigation/native"; import BlueButton from "../../../components/buttons/BlueButton"; import SelectListBoxString from "../../../components/PlayQuiz/SelectListBoxString"; import SelectListBoxCategory from "../../../components/PlayQuiz/SelectListBoxCategory"; import { Category } from "../../../models/Quiz"; +import HttpError from "../../../services/HttpError"; const difficulty = [ { key: "easy", value: "easy" }, @@ -15,22 +15,7 @@ const difficulty = [ { key: "hard", value: "hard" }, ]; -const categories = [ - { key: "9", value: "General Knowledge"}, - { key: "10", value: "Entertainment: Books" }, - { key: "11", value: "Entertainment: Film" }, - { key: "12", value: "Entertainment: Music"}, - { key: "13", value: "Entertainment: Musicals & Theatres" }, - { key: "14", value: "Entertainment: Television" }, - { key: "15", value: "Entertainment: Video Games" }, - { key: "16", value: "Entertainment: Board Games" }, - { key: "17", value: "Science & Nature" }, - { key: "18", value: "Science: Computers" }, - { key: "19", value: "Science: Mathematics" }, - { key: "20", value: "Mythology" }, - { key: "21", value: "Sports" }, - { key: "22", value: "Geography" }, -]; + interface Props { navigation: NavigationProp<any>; } @@ -42,7 +27,20 @@ export default function OnlineCreateLobby({navigation}: Props) { const [nbQuestions, setNbQuestions] = React.useState("10"); const [nbPlayers, setNbPlayers] = React.useState("2"); const [buttonIsDisabled, setButtonIsDisabled] = React.useState(true); - const {generateQuiz} = useQuizService(); + const {generateQuiz, getCategories} = useQuizService(); + + const fetchingCategories = async () => { + const categories = await getCategories(); + let categoriesTemp: Category[] = []; + categories.map((category: Category) => { + categoriesTemp.push({'name': category.name, 'id': category.id}); + }); + setCategories(categoriesTemp); + } + + useEffect(() => { + fetchingCategories(); + }, []); useEffect(() => { if(nbQuestions === "" || difficutlyChoose === "" || categoryChoose === null) @@ -57,15 +55,14 @@ export default function OnlineCreateLobby({navigation}: Props) { }, [difficutlyChoose,categoryChoose,nbQuestions]); const handlePlayButtonPress = async () => { - const quizGenerated = await generateQuiz(parseInt(nbQuestions), getIdOfCategory(categoryChoose), difficutlyChoose); - navigation.reset({ - index: 0, - routes: [ - { - name: "Lobby", - params: {quizGenerated: quizGenerated, nbPlayers: parseInt(nbPlayers)}, - }, - ] + const quizGenerated = await generateQuiz(parseInt(nbQuestions), categoryChoose ? categoryChoose.id : 10, difficutlyChoose); + if(HttpError.isHttpError(quizGenerated)) { + return; + } + + navigation.navigate("Lobby", { + runId: quizGenerated.id, + nbPlayers: parseInt(nbPlayers), }); }; -- GitLab From db1c2884edadd2ad2614d51772129bddb837b391 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sat, 11 Jan 2025 23:21:17 +0100 Subject: [PATCH 239/283] refactor: endquiz --- screens/PlayingQuiz/EndQuiz/EndQuiz.tsx | 8 ++- screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx | 63 ++++++++++---------- screens/PlayingQuiz/PlayingQuiz.tsx | 2 +- screens/PlayingQuiz/PlayingQuizBody.tsx | 16 ++++- screens/PlayingQuiz/PlayingQuizHeader.tsx | 2 +- 5 files changed, 53 insertions(+), 38 deletions(-) diff --git a/screens/PlayingQuiz/EndQuiz/EndQuiz.tsx b/screens/PlayingQuiz/EndQuiz/EndQuiz.tsx index 8297ee0..0547db0 100644 --- a/screens/PlayingQuiz/EndQuiz/EndQuiz.tsx +++ b/screens/PlayingQuiz/EndQuiz/EndQuiz.tsx @@ -3,10 +3,12 @@ import { View, StyleSheet, Image, Text } from "react-native"; import EndQuizChild from "./EndQuizChild"; import TemplateMenu from "../../../templates/TemplateMenu"; import {Quiz} from "../../../models/Quiz"; +import {QuizInformations} from "../../../models/QuizInformations"; type RoutePropsType = { EndQuizChild: { - quiz: Quiz; + quizInformations: QuizInformations; + score: number; }; }; interface Props { @@ -15,12 +17,12 @@ interface Props { } export default function EndQuiz({route,navigation}: Props) { - const { quiz } = route.params; + const { quizInformations, score } = route.params; return ( <View style={styles.containerGlobal}> <TemplateMenu navigation={navigation} headerNavigation={false} buttonGoBack={false}> <View> - <EndQuizChild navigation={navigation} quiz={quiz}/> + <EndQuizChild navigation={navigation} quizInformations={quizInformations} score={score}/> </View> </TemplateMenu> </View> diff --git a/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx index 2d8bafc..42b98e5 100644 --- a/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx +++ b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx @@ -4,43 +4,46 @@ import { NavigationProp, RouteProp } from "@react-navigation/native"; import {Quiz} from "../../../models/Quiz"; import {useQuizService} from "../../../services/QuizService"; import HttpError from "../../../services/HttpError"; +import {QuizInformations} from "../../../models/QuizInformations"; interface Props { navigation: NavigationProp<any>; - quiz: Quiz; + quizInformations: QuizInformations; + score: number; } -export default function EndQuizChild({ navigation, quiz }: Props) { +export default function EndQuizChild({ navigation, quizInformations, score }: Props) { const {restartQuiz, remainingQuiz} = useQuizService() - const handleRestartPress = async () => { - const isRestarted = await restartQuiz(quiz.id); - if(HttpError.isHttpError(isRestarted)){ - console.log(isRestarted.message); - return - } - if(!isRestarted) { - return; - } - const quizRestarted = await remainingQuiz(quiz.id); - if(HttpError.isHttpError(quizRestarted)){ - console.log(quizRestarted.message); - return - } - - navigation.reset({ - index: 0, - routes: [ - { - name: "PlayingQuiz", - params: {quizRecovered: quizRestarted}, - }, - ] - }); - }; + // const handleRestartPress = async () => { + // if(!quizInformations) return; + // const isRestarted = await restartQuiz(quizInformations.id); + // if(HttpError.isHttpError(isRestarted)){ + // console.log(isRestarted.message); + // return + // } + // if(!isRestarted) { + // return; + // } + // const quizRestarted = await remainingQuiz(quiz.id); + // if(HttpError.isHttpError(quizRestarted)){ + // console.log(quizRestarted.message); + // return + // } + // + // navigation.reset({ + // index: 0, + // routes: [ + // { + // name: "PlayingQuiz", + // params: {quizRecovered: quizRestarted}, + // }, + // ] + // }); + // }; const handleBackToMenu = () => { navigation.navigate('TabNavigator'); @@ -52,10 +55,10 @@ export default function EndQuizChild({ navigation, quiz }: Props) { <Image source={require('../../../assets/TitleApp.png')}/> </View> <View style={styles.containerBody}> - <Text style={styles.TextBodyTitle}>{((quiz.score/quiz.questionCount)*100 > 50 ) ? 'Victory' : 'Defeat'}</Text> - <Text style={styles.TextBody}>you finish the test with a score of {`${quiz.score}/${quiz.questionCount}`} ! </Text> + <Text style={styles.TextBodyTitle}>{((score/quizInformations.questionCount)*100 > 50 ) ? 'Victory' : 'Defeat'}</Text> + <Text style={styles.TextBody}>you finish the test with a score of {`${score}/${quizInformations.questionCount}`} ! </Text> <Image source={require('../../../assets/uWin.png')} /> - <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton> + {/*<DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton>*/} <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu} buttonStyle={styles.blueButton} buttonText={styles.blueButtonText}></DefaultButton> </View> </View> diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 406582e..aab164e 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -55,7 +55,7 @@ export default function PlayingQuiz({route, navigation}:Props) { return ( <TemplateDuo childrenHeader={<PlayingQuizHeader quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} score={score} navigation={navigation}></PlayingQuizHeader>} - childrenBody={<PlayingQuizBody quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} fetchActualQuestion={fetchActualQuestion} setScore={setScore} navigation={navigation}></PlayingQuizBody>} + childrenBody={<PlayingQuizBody quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} fetchActualQuestion={fetchActualQuestion} setScore={setScore} score={score} navigation={navigation}></PlayingQuizBody>} /> ); } \ No newline at end of file diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 0992745..6a220d2 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -13,10 +13,11 @@ import AnswerButton from "../../components/buttons/AnswerButton"; import {QuizInformations} from "../../models/QuizInformations"; interface Props { - quizInformations: QuizInformations; + quizInformations?: QuizInformations; runId?: string; actualQuestion: Question; fetchActualQuestion: () => void; + score: number; setScore: (score: number) => void; navigation: NavigationProp<any>; } @@ -64,7 +65,7 @@ const getStyleOfAnswerButton = (answerId: number, selectedAnswerIndex: number | return styles.answerButton; } -export default function PlayingQuizBody({ quizInformations, runId, actualQuestion, fetchActualQuestion, setScore, navigation }: Props) { +export default function PlayingQuizBody({ quizInformations, runId, actualQuestion, fetchActualQuestion, score, setScore, navigation }: Props) { const [selectedAnswerId, setSelectedAnswerId] = useState<number | null>(null); const [quizState, setQuizState] = useState(QuizState.ANSWERING); const [correctAnswerId, setCorrectAnswerId] = useState<number | null>(null); @@ -91,6 +92,7 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio setCorrectAnswerId(correctAnswerIdFetched); setQuizState(QuizState.SHOWING_RESULTS); + if (quizInformations === undefined) return; // Check si c'est la dernière question if(answereFetched.answers.order === quizInformations.questionCount - 1) { setIsLastQuestion(true); @@ -100,7 +102,15 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio }; const onContinue = () => { if(isLastQuestion) { - console.log("go endquiz"); + navigation.reset({ + index: 0, + routes: [ + { + name: "EndQuiz", + params: {quizInformations: quizInformations, score: score}, + }, + ] + }); return; } fetchActualQuestion(); diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index 1caa6bf..2f78620 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -11,7 +11,7 @@ import {QuizInformations} from "../../models/QuizInformations"; import {Question} from "../../models/Question"; interface Props { - quizInformations: QuizInformations; + quizInformations?: QuizInformations; runId?: string; actualQuestion: Question; score: number; -- GitLab From a57ac6da87741b362394b5a8fa027bd0a0011ef7 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sat, 11 Jan 2025 23:59:50 +0100 Subject: [PATCH 240/283] style: scroll qui ne scroll pas --- screens/Home/HomeChild.tsx | 5 +- screens/PlayingQuiz/PlayingQuiz.tsx | 80 ++++++++++++++++++++++++++--- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 1072366..7a0b645 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -41,7 +41,8 @@ export default function HomeChild({navigation}: Props) { }); } const handleButtonMyQuizzesPressed = () => { - navigation.navigate("MyQuizzes"); + // navigation.navigate("MyQuizzes"); + navigation.navigate("PlayingQuiz", {runId: "1", quizId: "1"}); } const handleButtonGeneratePressed = () => { @@ -56,7 +57,7 @@ export default function HomeChild({navigation}: Props) { <View style={styles.buttonContainer}> <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonQuickGamePressed}/> <MenuButton text={"PLAY"} handleButtonPressed={handleButtonGeneratePressed}/> - {/*<MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/>*/} + <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/> </View> <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> </View> diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 406582e..677fb69 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -29,18 +29,82 @@ export default function PlayingQuiz({route, navigation}:Props) { const [score, setScore] = useState(0); const fetchQuizInformations = async () => { - const quizInformationsFetched = await getQuizInformations(quizId); - if (HttpError.isHttpError(quizInformationsFetched)) { - return; - } + // const quizInformationsFetched = await getQuizInformations(quizId); + // if (HttpError.isHttpError(quizInformationsFetched)) { + // return; + // } + const quizInformationsFetched: QuizInformations = { + id: 1, + name: "Quiz de Test", + description: "Ceci est un quiz de test pour vérifier l'interface.", + isCommunity: true, + questionCount: 10, + categoryId: 2, + difficultyId: 3, + authorId: { + id: 42, + username: "TestAuthor", + email: "testauthor@example.com" + }, + createdAt: "2025-01-10T10:00:00Z", + updatedAt: "2025-01-11T12:00:00Z", + category: { + id: 2, + name: "Science" + }, + difficulty: { + id: 3, + name: "Moyenne" + } + }; + setQuizInformations(quizInformationsFetched); } const fetchActualQuestion = async () => { - const actualQuestionFetched = await getActualQuestionOfAQuiz(runId); - if (HttpError.isHttpError(actualQuestionFetched)) { - return; - } + // const actualQuestionFetched = await getActualQuestionOfAQuiz(runId); + // if (HttpError.isHttpError(actualQuestionFetched)) { + // return; + // } + const actualQuestionFetched: Question = { + id: 1, + text: "Quelle image correspond à une étoile ?", + type: "image", + categoryId: 2, + quizId: "quiz123", + order: 1, + answers: [ + { + id: 1, + text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + isCorrect: true, + questionId: 1 + }, + { + id: 2, + text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + isCorrect: false, + questionId: 1 + }, + { + id: 3, + text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + isCorrect: false, + questionId: 1 + }, + { + id: 4, + text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + isCorrect: false, + questionId: 1 + } + ], + category: { + id: 2, + name: "Astronomie" + } + }; + setActualQuestion(actualQuestionFetched); } -- GitLab From 08c7fbf293f0af1e676e90874e7226874573ac7d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 12 Jan 2025 00:09:37 +0100 Subject: [PATCH 241/283] refactor: join a quiz --- components/PlayQuiz/AboutAQuiz.tsx | 13 +++++---- models/RunInformations.ts | 14 +++++++++ screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx | 34 +++++++++++++++------- screens/PlayingQuiz/PlayingQuiz.tsx | 11 ++++++- screens/PlayingQuiz/PlayingQuizHeader.tsx | 2 +- services/QuizService.ts | 24 ++++++++++++++- 6 files changed, 80 insertions(+), 18 deletions(-) create mode 100644 models/RunInformations.ts diff --git a/components/PlayQuiz/AboutAQuiz.tsx b/components/PlayQuiz/AboutAQuiz.tsx index 94ae96e..ed01626 100644 --- a/components/PlayQuiz/AboutAQuiz.tsx +++ b/components/PlayQuiz/AboutAQuiz.tsx @@ -1,19 +1,22 @@ import React from "react"; import { View, Text, StyleSheet } from "react-native"; import {Quiz} from "../../models/Quiz"; +import {QuizInformations} from "../../models/QuizInformations"; +import {RunInformations} from "../../models/RunInformations"; interface Props { - quiz?: Quiz; + runInformations?: RunInformations; + quizInformations?: QuizInformations; } -export default function AboutAQuiz({quiz} : Props) { +export default function AboutAQuiz({runInformations, quizInformations} : Props) { return ( <View style={styles.container}> <Text style={styles.title}>About this quiz</Text> <View style={styles.separator} /> <View style={styles.infoContainer}> - <Text style={styles.text}>Category : {quiz ? quiz.category?.name : "?"}</Text> - <Text style={styles.text}>Question : {quiz ? quiz.questionIndex : "?"}/{quiz ? quiz.questionCount : "?"}</Text> - <Text style={styles.text}>Score : {quiz ? quiz.score : "?"}</Text> + <Text style={styles.text}>Category : {quizInformations ? quizInformations.category.name : "?"}</Text> + {/*<Text style={styles.text}>Question : {quizInformations ? quizInformations.o : "?"}/{quiz ? quiz.questionCount : "?"}</Text>*/} + <Text style={styles.text}>Score : {runInformations ? runInformations.score : "?"}</Text> </View> </View> diff --git a/models/RunInformations.ts b/models/RunInformations.ts new file mode 100644 index 0000000..c7b74f0 --- /dev/null +++ b/models/RunInformations.ts @@ -0,0 +1,14 @@ +import {Category, Difficulty} from "./Quiz"; +import {User} from "./User"; + +export interface RunInformations { + id: string; + quizId: string; + originalQuizId: string; + score: number; + questionIndex: number; + startDate: string; + lastChange: string; + quizUserId: number; + roomId?: number; +} \ No newline at end of file diff --git a/screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx b/screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx index 9f856f9..e123a23 100644 --- a/screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx +++ b/screens/Home/PlayQuiz/PlayQuizJoinQuiz.tsx @@ -6,8 +6,9 @@ import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import {Quiz} from "../../../models/Quiz"; import { useQuizService } from "../../../services/QuizService"; import HttpError from "../../../services/HttpError"; -import {getIdOfCategory} from "../../../helper/QuizHelper"; import {NavigationProp} from "@react-navigation/native"; +import {RunInformations} from "../../../models/RunInformations"; +import {QuizInformations} from "../../../models/QuizInformations"; interface Props { navigation: NavigationProp<any>; @@ -16,25 +17,38 @@ export default function PlayQuizJoinQuiz({navigation}: Props) { const [codeQuiz, setCodeQuiz] = React.useState(""); const [quiz, setQuiz] = React.useState<Quiz>(); const [error, setError] = React.useState<string>(""); + const [runInformations, setRunInformations] = React.useState<RunInformations>(); + const [quizInformations, setQuizInformations] = React.useState<QuizInformations>(); - const { remainingQuiz } = useQuizService(); + const { getRunsInfo, getQuizInformations } = useQuizService(); useEffect(() => { - fetchQuiz(); + fetchRuns(); }, [codeQuiz]); - const fetchQuiz = async () => { - if(codeQuiz === "") + const fetchRuns = async () => { + + if(codeQuiz === "") return; + if(codeQuiz.length < 6 ) { + setError("A quiz contains 6 characters"); + return; + } + + const runFetched = await getRunsInfo(codeQuiz); + if (runFetched instanceof HttpError) { + setError("The quiz does not exist"); return; } - const quizFetched = await remainingQuiz(codeQuiz); - if (quizFetched instanceof HttpError) { + const quizInformations = await getQuizInformations(runFetched.quizId); + if (quizInformations instanceof HttpError) { setError("The quiz does not exist"); return; } + setError(""); - setQuiz(quizFetched); + setQuizInformations(quizInformations); + setRunInformations(runFetched); }; const handlePlayButtonPress = () => { @@ -43,7 +57,7 @@ export default function PlayQuizJoinQuiz({navigation}: Props) { routes: [ { name: "PlayingQuiz", - params: {quizRecovered: quiz}, + params: {runId: runInformations?.id, quizId: runInformations?.quizId }, }, ] }); @@ -55,7 +69,7 @@ export default function PlayQuizJoinQuiz({navigation}: Props) { <View style={styles.fieldContainer}> {error ? <Text style={styles.errorText}>{error}</Text> : null} <InputPlayQuiz placeholder={"Enter quiz ID"} setText={setCodeQuiz} noTitle={true} /> - <AboutAQuiz quiz={quiz} /> + <AboutAQuiz runInformations={runInformations} quizInformations={quizInformations} /> </View> <View style={styles.buttonPlayContainer}> <BlueButton onPress={handlePlayButtonPress} text={"PLAY"} isDisabled={codeQuiz==="" || error!==""}/> diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index aab164e..df699f9 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -23,11 +23,19 @@ interface Props { export default function PlayingQuiz({route, navigation}:Props) { const {runId, quizId} = route.params; - const { getQuizInformations, getActualQuestionOfAQuiz } = useQuizService(); + const { getQuizInformations, getRunsInfo, getActualQuestionOfAQuiz } = useQuizService(); const [quizInformations, setQuizInformations] = useState<QuizInformations | undefined>(undefined); const [actualQuestion, setActualQuestion] = useState<Question | undefined>(undefined); const [score, setScore] = useState(0); + const fetchRunInformations = async () => { + const runInformationsFetched = await getRunsInfo(runId); + if (HttpError.isHttpError(runInformationsFetched)) { + return; + } + setScore(runInformationsFetched.score); + } + const fetchQuizInformations = async () => { const quizInformationsFetched = await getQuizInformations(quizId); if (HttpError.isHttpError(quizInformationsFetched)) { @@ -46,6 +54,7 @@ export default function PlayingQuiz({route, navigation}:Props) { useEffect(() => { fetchQuizInformations(); + fetchRunInformations(); }, []); useEffect(() => { diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index 2f78620..ffba640 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -37,7 +37,7 @@ export default function PlayingQuizHeader({quizInformations, runId, actualQuesti <View style={styles.container}> <View style={styles.header}> <GoHomeButton navigation={navigation} onPress={setIsConfirmModalVisible}/> - <Text style={styles.codeQuizText}>ID QUIZZ : {quizInformations ? quizInformations.id : "?"}</Text> + <Text style={styles.codeQuizText}>ID QUIZZ : {runId}</Text> </View> <View style={styles.questionAndScoreContainer}> <View style={styles.InformationsContainer}> diff --git a/services/QuizService.ts b/services/QuizService.ts index 72752c8..f3fc6c1 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -8,6 +8,7 @@ import {AnswerResponse} from "../models/AnswerResponse"; import {QuizInformations} from "../models/QuizInformations"; import {QuestionResponse} from "../models/QuestionResponse"; import {QuizGenerateAnswer} from "../models/QuizGenerateAnswer"; +import {RunInformations} from "../models/RunInformations"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API @@ -266,6 +267,26 @@ export const useQuizService = () => { } }; + const getRunsInfo = async (id: string): Promise<RunInformations | HttpError> => { + try { + const response = await axios.get<RunInformations>(`${url}/run/info/`+id, { + headers: { + "Content-Type": "application/json", + }, + }); + return response.data; + } catch (error: any) { + console.log("Error while getQuizInformations quiz:", error); + + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + } + + return { generateQuiz: generateQuiz, @@ -277,6 +298,7 @@ export const useQuizService = () => { getCategories: getCategories, answerQuestion: answerQuestion, getActualQuestionOfAQuiz: getActualQuestionOfAQuiz, - getQuizInformations: getQuizInformations + getQuizInformations: getQuizInformations, + getRunsInfo: getRunsInfo } } \ No newline at end of file -- GitLab From 50097783f5fcbab6429e1161bd898d168711adad Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 12 Jan 2025 10:16:29 +0100 Subject: [PATCH 242/283] style: size image in image button --- components/buttons/AnswerButtonImage.tsx | 9 ++++++--- screens/PlayingQuiz/PlayingQuizBody.tsx | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/components/buttons/AnswerButtonImage.tsx b/components/buttons/AnswerButtonImage.tsx index bf75c90..61a3f09 100644 --- a/components/buttons/AnswerButtonImage.tsx +++ b/components/buttons/AnswerButtonImage.tsx @@ -6,7 +6,6 @@ interface Props{ url: string; handleButtonPressed:() => void; buttonStyle?: any; - buttonImage?: any; indicator?: number | undefined; } @@ -17,7 +16,7 @@ interface Props{ * @param buttonStyle - Surcharge du style du bouton * @param buttonText - Surcharge du style du texte du bouton */ -export default function AnswerButtonImage({url, handleButtonPressed, buttonStyle, buttonImage, indicator}: Props){ +export default function AnswerButtonImage({url, handleButtonPressed, buttonStyle, indicator}: Props){ return( <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> {indicator !== undefined && ( @@ -26,7 +25,7 @@ export default function AnswerButtonImage({url, handleButtonPressed, buttonStyle </View> )} <Image - style={[TextsStyles.defaultButtonText, {flex: 1, width: "100%", height: "100%"}, buttonImage]} + style={[styles.answerImage]} source={{uri: url}} /> </TouchableOpacity> @@ -58,4 +57,8 @@ const styles = StyleSheet.create({ fontWeight: "bold", fontSize: 14, }, + answerImage: { + width: '100%', + height: '100%', + }, }) \ No newline at end of file diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 7fe64e5..eb7fe38 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -129,7 +129,6 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio url={answer.text} handleButtonPressed={() => onAnsweredButtonClicked(answer.id)} buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)} - // buttonImage={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)} /> : <AnswerButtonText -- GitLab From 40c569ea35b4f992ad67e04e2ab42ba00ea2bb04 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 12 Jan 2025 11:17:14 +0100 Subject: [PATCH 243/283] style: center button in the list --- components/buttons/AnswerButtonImage.tsx | 24 +++++++++++++----------- screens/PlayingQuiz/PlayingQuizBody.tsx | 19 ++++++++++++------- styles/ButtonsStyles.ts | 2 +- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/components/buttons/AnswerButtonImage.tsx b/components/buttons/AnswerButtonImage.tsx index 61a3f09..20b13ff 100644 --- a/components/buttons/AnswerButtonImage.tsx +++ b/components/buttons/AnswerButtonImage.tsx @@ -18,17 +18,19 @@ interface Props{ */ export default function AnswerButtonImage({url, handleButtonPressed, buttonStyle, indicator}: Props){ return( - <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> - {indicator !== undefined && ( - <View style={styles.indicator}> - <Text style={styles.indicatorText}>{indicator}</Text> - </View> - )} - <Image - style={[styles.answerImage]} - source={{uri: url}} - /> - </TouchableOpacity> + <View style={{height: 150, width: '100%'}}> + <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> + {indicator !== undefined && ( + <View style={styles.indicator}> + <Text style={styles.indicatorText}>{indicator}</Text> + </View> + )} + <Image + style={[styles.answerImage]} + source={{uri: url}} + /> + </TouchableOpacity> + </View> ) } diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index eb7fe38..cdbc278 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -1,4 +1,4 @@ -import {View, StyleSheet, Text, ActivityIndicator} from "react-native"; +import {View, StyleSheet, Text, ActivityIndicator, ScrollView} from "react-native"; import DefaultButton from "../../components/buttons/DefaultButton"; import { ButtonsStyles } from "../../styles/ButtonsStyles"; import React, { useState, useEffect, SetStateAction, Dispatch } from "react"; @@ -121,7 +121,10 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio <View style={styles.buttonContainer}> {actualQuestion ? ( <> - <View style={styles.buttonListContainer}> + <ScrollView + style={styles.buttonListContainer} + contentContainerStyle={styles.buttonListContentContainer} + > {actualQuestion.answers.map((answer, index) => ( actualQuestion.type === "image" ? <AnswerButtonImage @@ -139,7 +142,8 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio buttonText={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)} /> ))} - </View> + {/* </View> */} + </ScrollView> <View style={styles.playButtonContainer}> <BlueButton onPress={() => quizState === QuizState.ANSWERING ? onValidation() : onContinue()} text={quizState === QuizState.ANSWERING ? "CONFIRM" : "CONTINUE"} buttonStyle={{borderRadius: 50}} isDisabled={false}/> </View> @@ -212,7 +216,7 @@ const styles = StyleSheet.create({ }, answerButton: { backgroundColor: "#F3F3F3", - height: '30%', + height: '75%', alignItems: 'center', width: '80%', marginVertical: '3%', @@ -224,12 +228,13 @@ const styles = StyleSheet.create({ elevation: 5, }, buttonListContainer: { - height: '80%', + maxHeight: '80%', width: '100%', + }, + buttonListContentContainer: { display: 'flex', justifyContent: 'flex-start', - alignItems: 'center', - overflow: 'scroll', + alignItems: 'center' } }); diff --git a/styles/ButtonsStyles.ts b/styles/ButtonsStyles.ts index 12d8539..679e720 100644 --- a/styles/ButtonsStyles.ts +++ b/styles/ButtonsStyles.ts @@ -16,7 +16,7 @@ export const ButtonsStyles = StyleSheet.create( { }, answerButton: { backgroundColor: "#F3F3F3", - height: '30%', + height: '75%', alignItems: 'center', width: '80%', marginVertical: '3%', -- GitLab From 78c706965c098045ca988dfe3a063bfb7ddeea64 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 12 Jan 2025 11:18:11 +0100 Subject: [PATCH 244/283] refactor: move the style to a var --- components/buttons/AnswerButtonImage.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/buttons/AnswerButtonImage.tsx b/components/buttons/AnswerButtonImage.tsx index 20b13ff..8716144 100644 --- a/components/buttons/AnswerButtonImage.tsx +++ b/components/buttons/AnswerButtonImage.tsx @@ -18,7 +18,7 @@ interface Props{ */ export default function AnswerButtonImage({url, handleButtonPressed, buttonStyle, indicator}: Props){ return( - <View style={{height: 150, width: '100%'}}> + <View style={styles.containerButton}> <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> {indicator !== undefined && ( <View style={styles.indicator}> @@ -63,4 +63,11 @@ const styles = StyleSheet.create({ width: '100%', height: '100%', }, + containerButton: { + height: 150, + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + } }) \ No newline at end of file -- GitLab From 13d4373153eac1f8bdf8bef48fe3e6266a8aa7f4 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 12 Jan 2025 11:19:22 +0100 Subject: [PATCH 245/283] style: update answerbuttonText to fit with the list --- components/buttons/AnswerButtonText.tsx | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/components/buttons/AnswerButtonText.tsx b/components/buttons/AnswerButtonText.tsx index 6dbc8b4..57427b4 100644 --- a/components/buttons/AnswerButtonText.tsx +++ b/components/buttons/AnswerButtonText.tsx @@ -19,14 +19,16 @@ interface Props{ */ export default function AnswerButtonText({text, handleButtonPressed, buttonStyle, buttonText, indicator}: Props){ return( - <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> - {indicator !== undefined && ( - <View style={styles.indicator}> - <Text style={styles.indicatorText}>{indicator}</Text> - </View> - )} - <Text style={[TextsStyles.defaultButtonText, buttonText]}>{text}</Text> - </TouchableOpacity> + <View style={styles.containerButton}> + <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> + {indicator !== undefined && ( + <View style={styles.indicator}> + <Text style={styles.indicatorText}>{indicator}</Text> + </View> + )} + <Text style={[TextsStyles.defaultButtonText, buttonText]}>{text}</Text> + </TouchableOpacity> + </View> ) } @@ -55,4 +57,11 @@ const styles = StyleSheet.create({ fontWeight: "bold", fontSize: 14, }, + containerButton: { + height: 150, + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + } }) \ No newline at end of file -- GitLab From 063991ba682ea3d6762d296360e9464105c8a0cf Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 12 Jan 2025 11:20:04 +0100 Subject: [PATCH 246/283] temp: add test value --- screens/PlayingQuiz/PlayingQuiz.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 677fb69..9bfdfda 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -69,7 +69,7 @@ export default function PlayingQuiz({route, navigation}:Props) { const actualQuestionFetched: Question = { id: 1, text: "Quelle image correspond à une étoile ?", - type: "image", + type: "imae", categoryId: 2, quizId: "quiz123", order: 1, -- GitLab From 33c6015d28b81178eff640cc7d09c347bdf08517 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 12 Jan 2025 13:40:18 +0100 Subject: [PATCH 247/283] feat: add shuffle question --- helper/QuizHelper.ts | 30 ++++----- screens/PlayingQuiz/PlayingQuiz.tsx | 96 +++++++++++++++++++++++------ 2 files changed, 88 insertions(+), 38 deletions(-) diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index e2ee3aa..2035db1 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -1,21 +1,15 @@ -import {Quiz} from "../models/Quiz"; -export const shuffleAnswersOfQuiz = (quiz: Quiz): Quiz => { - // Clone le quiz pour éviter de modifier l'original - const shuffledQuiz: Quiz = { - ...quiz, - questions: quiz.questions.map((question) => ({ - ...question, - answers: [...question.answers], // Clone les réponses pour éviter de modifier l'original - })), - }; +import {Question} from '../models/Question'; + +export const shuffleAnswers = (question: Question): Question => { + const shuffledAnswers = [...question.answers]; - // Mélange les réponses de chaque question - shuffledQuiz.questions.forEach((question) => { - for (let i = question.answers.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [question.answers[i], question.answers[j]] = [question.answers[j], question.answers[i]]; - } - }); + for (let i = shuffledAnswers.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffledAnswers[i], shuffledAnswers[j]] = [shuffledAnswers[j], shuffledAnswers[i]]; + } - return shuffledQuiz; + return { + ...question, + answers: shuffledAnswers, + }; }; \ No newline at end of file diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index df699f9..6b2dd76 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -7,6 +7,7 @@ import {useQuizService} from "../../services/QuizService"; import HttpError from "../../services/HttpError"; import {Question} from "../../models/Question"; import PlayingQuizBody from "./PlayingQuizBody"; +import { shuffleAnswers } from "../../helper/QuizHelper"; type RoutePropsType = { @@ -23,38 +24,93 @@ interface Props { export default function PlayingQuiz({route, navigation}:Props) { const {runId, quizId} = route.params; - const { getQuizInformations, getRunsInfo, getActualQuestionOfAQuiz } = useQuizService(); + const { getQuizInformations, getActualQuestionOfAQuiz } = useQuizService(); const [quizInformations, setQuizInformations] = useState<QuizInformations | undefined>(undefined); const [actualQuestion, setActualQuestion] = useState<Question | undefined>(undefined); const [score, setScore] = useState(0); - const fetchRunInformations = async () => { - const runInformationsFetched = await getRunsInfo(runId); - if (HttpError.isHttpError(runInformationsFetched)) { - return; - } - setScore(runInformationsFetched.score); - } - const fetchQuizInformations = async () => { - const quizInformationsFetched = await getQuizInformations(quizId); - if (HttpError.isHttpError(quizInformationsFetched)) { - return; - } + // const quizInformationsFetched = await getQuizInformations(quizId); + // if (HttpError.isHttpError(quizInformationsFetched)) { + // return; + // } + const quizInformationsFetched: QuizInformations = { + id: 1, + name: "Quiz de Test", + description: "Ceci est un quiz de test pour vérifier l'interface.", + isCommunity: true, + questionCount: 10, + categoryId: 2, + difficultyId: 3, + authorId: { + id: 42, + username: "TestAuthor", + email: "testauthor@example.com" + }, + createdAt: "2025-01-10T10:00:00Z", + updatedAt: "2025-01-11T12:00:00Z", + category: { + id: 2, + name: "Science" + }, + difficulty: { + id: 3, + name: "Moyenne" + } + }; + setQuizInformations(quizInformationsFetched); } const fetchActualQuestion = async () => { - const actualQuestionFetched = await getActualQuestionOfAQuiz(runId); - if (HttpError.isHttpError(actualQuestionFetched)) { - return; - } - setActualQuestion(actualQuestionFetched); + // const actualQuestionFetched = await getActualQuestionOfAQuiz(runId); + // if (HttpError.isHttpError(actualQuestionFetched)) { + // return; + // } + const actualQuestionFetched: Question = { + id: 1, + text: "Quelle image correspond à une étoile ?", + type: "imae", + categoryId: 2, + quizId: "quiz123", + order: 1, + answers: [ + { + id: 1, + text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + isCorrect: true, + questionId: 1 + }, + { + id: 2, + text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + isCorrect: false, + questionId: 1 + }, + { + id: 3, + text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + isCorrect: false, + questionId: 1 + }, + { + id: 4, + text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + isCorrect: false, + questionId: 1 + } + ], + category: { + id: 2, + name: "Astronomie" + } + }; + const actualQuestionFetchedShuffle = shuffleAnswers(actualQuestionFetched); + setActualQuestion(actualQuestionFetchedShuffle); } useEffect(() => { fetchQuizInformations(); - fetchRunInformations(); }, []); useEffect(() => { @@ -64,7 +120,7 @@ export default function PlayingQuiz({route, navigation}:Props) { return ( <TemplateDuo childrenHeader={<PlayingQuizHeader quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} score={score} navigation={navigation}></PlayingQuizHeader>} - childrenBody={<PlayingQuizBody quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} fetchActualQuestion={fetchActualQuestion} setScore={setScore} score={score} navigation={navigation}></PlayingQuizBody>} + childrenBody={<PlayingQuizBody quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} fetchActualQuestion={fetchActualQuestion} setScore={setScore} navigation={navigation}></PlayingQuizBody>} /> ); } \ No newline at end of file -- GitLab From e9c6f6ec0f0a0ac0166da4c2f2ab542517c03c52 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 12 Jan 2025 13:49:49 +0100 Subject: [PATCH 248/283] feat: lobby --- hooks/UseCookie.ts | 55 +++++++++++++++++++++++++++++ screens/Multiplayer/Lobby/Lobby.tsx | 20 +++++++---- services/UserService.ts | 20 +++++++++-- 3 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 hooks/UseCookie.ts diff --git a/hooks/UseCookie.ts b/hooks/UseCookie.ts new file mode 100644 index 0000000..050d762 --- /dev/null +++ b/hooks/UseCookie.ts @@ -0,0 +1,55 @@ +import { useState, useEffect, useCallback } from 'react'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const useCookie = (cookieName: string) => { + const [error, setError] = useState<Error | null>(null); + + // Fonction pour sauvegarder le cookie + const saveCookie = useCallback(async (value: string, expirationInDays = 7) => { + try { + await AsyncStorage.setItem(cookieName, value); + setError(null); + } catch (e) { + if(e instanceof Error) { + setError(e); + } + setError(new Error('An error occurred while saving the cookie')); + } + }, [cookieName]); + + // Fonction pour supprimer le cookie + const removeCookie = useCallback(async () => { + try { + await AsyncStorage.removeItem(cookieName); + setError(null); + } catch (e) { + if(e instanceof Error) { + setError(e); + } + setError(new Error('An error occurred while removing the cookie')); + } + }, [cookieName]); + + const getCookie = useCallback(async () => { + try { + const value = await AsyncStorage.getItem(cookieName); + setError(null); + return value; + } catch (e) { + if(e instanceof Error) { + setError(e); + } + setError(new Error('An error occurred while getting the cookie')); + } + }, [cookieName]); + + + return { + error, + saveCookie, + removeCookie, + getCookie, + }; +}; + +export default useCookie; \ No newline at end of file diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index 2f9a512..c5a24eb 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -9,6 +9,7 @@ import ModalInvitePlayer from "../../../components/Multiplayer/ModalInvitePlayer import {User} from "../../../models/User"; import {QuizGenerateAnswer} from "../../../models/QuizGenerateAnswer"; import {io, Socket} from "socket.io-client"; +import useCookie from "../../../hooks/UseCookie"; interface Props { navigation: NavigationProp<any>; @@ -28,16 +29,17 @@ export default function Lobby({navigation, route}: Props) { const [isHost, setIsHost] = useState<boolean>(true); const [showModal, setShowModal] = useState<boolean>(false); const [players, setPlayers] = useState<User[]>([{ id: 0, username: 'Invite', email: 'invite@email.com'}]); - - - + const {getCookie} = useCookie('access_token'); const [socket, setSocket] = useState<Socket | null>(null); - useEffect(() => { - // Initialise Socket.IO connection + const userInvite = { id: 0, username: 'Invite', email: 'invite@email.com'} + const initSocket = async () => { + const cookie = await getCookie(); const newSocket = io("http://klebert-host.com:33053/party", { transports: ["websocket"], // Utilise WebSocket uniquement - withCredentials: true + extraHeaders: { + Cookie: `access_token=${cookie}`, + }, }); setSocket(newSocket); @@ -58,7 +60,7 @@ export default function Lobby({navigation, route}: Props) { // Mettez à jour la liste des joueurs lorsque quelqu'un rejoint newSocket.on("userJoined", (data: User[]) => { console.log("Updated user list:", data); - setPlayers(data); + setPlayers([userInvite, ...data]); }); // Nettoyez la connexion lorsque le composant est démonté @@ -66,6 +68,10 @@ export default function Lobby({navigation, route}: Props) { newSocket.disconnect(); setSocket(null); }; + } + + useEffect(() => { + initSocket(); }, [runId]); const item = ({ item }: { item: User }) => { diff --git a/services/UserService.ts b/services/UserService.ts index 374a311..22675d4 100644 --- a/services/UserService.ts +++ b/services/UserService.ts @@ -1,10 +1,13 @@ import HttpError from "./HttpError"; -import User from "../models/User"; import axios from "axios"; import {transformToUserModel} from "../helper/UserHelper"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import useCookie from "../hooks/UseCookie"; +import {User} from "../models/User"; export const useUserService = () => { const url = "https://klebert-host.com:33037"; + const { saveCookie, removeCookie } = useCookie('access_token'); const loginUser = async (login: string, password: string): Promise<boolean | HttpError> => { @@ -14,10 +17,22 @@ export const useUserService = () => { password: password, }; - await axios.post(`${url}/auth/login`, requestBody, { + const response =await axios.post(`${url}/auth/login`, requestBody, { withCredentials: true, // Autorise Axios à inclure les cookies }); + const cookies = response.headers['set-cookie']; + if (cookies) { + const accessToken = cookies + .find(cookie => cookie.startsWith('access_token=')) + ?.split(';')[0] + ?.split('=')[1]; + + if (accessToken) { + saveCookie(accessToken); + } + } + return true; } catch (error: any) { @@ -57,6 +72,7 @@ export const useUserService = () => { const logoutUser = async (): Promise<boolean | HttpError> => { try { + removeCookie(); await axios.post(`${url}/auth/logout`, null, { withCredentials: true, // Inclut les cookies dans la requête }); -- GitLab From fdb7ef0f4b7727f9fca1d588d39b843166794dd6 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 12 Jan 2025 14:24:20 +0100 Subject: [PATCH 249/283] feat: add route create run --- services/QuizService.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index f3fc6c1..f424b3d 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -286,7 +286,27 @@ export const useQuizService = () => { } } + const createRun = async (quizId: string): Promise<string | HttpError> => { + try { + const response = await axios.post(`${url}/run/start`, { + quizId: quizId, + }, { + headers: { + "Content-Type": "application/json", + }, + }); + + return response.data.id; + } catch (error: any) { + console.log("Error while getQuizInformations quiz:", error); + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + } return { generateQuiz: generateQuiz, @@ -299,6 +319,7 @@ export const useQuizService = () => { answerQuestion: answerQuestion, getActualQuestionOfAQuiz: getActualQuestionOfAQuiz, getQuizInformations: getQuizInformations, - getRunsInfo: getRunsInfo + getRunsInfo: getRunsInfo, + createRun: createRun, } } \ No newline at end of file -- GitLab From 21943fec9f25cd345368c23cc649ad54656dba6d Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 12 Jan 2025 14:57:28 +0100 Subject: [PATCH 250/283] feat: start quiz from community --- .../InformationsOfQuiz/InformationsOfQuiz.tsx | 8 +++++-- services/QuizService.ts | 23 ++++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx index 3e6461c..6e6c3dd 100644 --- a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -3,6 +3,7 @@ import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; import TemplateMenu from "../../../templates/TemplateMenu"; import {NavigationProp, RouteProp} from "@react-navigation/native"; import {Quiz} from "../../../models/Quiz"; +import { useQuizService } from "../../../services/QuizService"; type RoutePropsType = { InformationsOfQuiz: { @@ -16,9 +17,12 @@ interface Props { export default function InformationsOfQuiz({ navigation, route }: Props) { const { quiz } = route.params; + const { createRun } = useQuizService(); - const onPlay = () => { - navigation.navigate('PlayingQuiz', {quizRecovered: quiz}); + const onPlay = async () => { + const quizId = quiz.id.toString(); + const runId = await createRun(quizId); + navigation.navigate('PlayingQuiz', { runId: runId, quizId: quizId }); } return ( diff --git a/services/QuizService.ts b/services/QuizService.ts index f3fc6c1..f424b3d 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -286,7 +286,27 @@ export const useQuizService = () => { } } + const createRun = async (quizId: string): Promise<string | HttpError> => { + try { + const response = await axios.post(`${url}/run/start`, { + quizId: quizId, + }, { + headers: { + "Content-Type": "application/json", + }, + }); + + return response.data.id; + } catch (error: any) { + console.log("Error while getQuizInformations quiz:", error); + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + } return { generateQuiz: generateQuiz, @@ -299,6 +319,7 @@ export const useQuizService = () => { answerQuestion: answerQuestion, getActualQuestionOfAQuiz: getActualQuestionOfAQuiz, getQuizInformations: getQuizInformations, - getRunsInfo: getRunsInfo + getRunsInfo: getRunsInfo, + createRun: createRun, } } \ No newline at end of file -- GitLab From c94b568b98ce913ebe2b3825757eb912ad26b198 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 12 Jan 2025 15:00:10 +0100 Subject: [PATCH 251/283] fix: conflits --- helper/QuizHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/QuizHelper.ts b/helper/QuizHelper.ts index 2035db1..85b1137 100644 --- a/helper/QuizHelper.ts +++ b/helper/QuizHelper.ts @@ -1,6 +1,6 @@ import {Question} from '../models/Question'; -export const shuffleAnswers = (question: Question): Question => { +export const shuffleAnswersOfQuiz = (question: Question): Question => { const shuffledAnswers = [...question.answers]; for (let i = shuffledAnswers.length - 1; i > 0; i--) { -- GitLab From 7f1813f351f76a4285fbf97840fc82ce244a2269 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 12 Jan 2025 16:34:48 +0100 Subject: [PATCH 252/283] feat: starting playing quiz --- .../PlayingQuizMultiMode.tsx | 126 ++++++++++ .../PlayingQuizMultiModeBody.tsx | 225 ++++++++++++++++++ .../PlayingQuizMultiModeHeader.tsx | 95 ++++++++ 3 files changed, 446 insertions(+) create mode 100644 screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiMode.tsx create mode 100644 screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx create mode 100644 screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeHeader.tsx diff --git a/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiMode.tsx b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiMode.tsx new file mode 100644 index 0000000..e2e6bfc --- /dev/null +++ b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiMode.tsx @@ -0,0 +1,126 @@ +import {NavigationProp, RouteProp} from "@react-navigation/native"; +import {useQuizService} from "../../../../services/QuizService"; +import React, {useEffect, useState} from "react"; +import {QuizInformations} from "../../../../models/QuizInformations"; +import {Question} from "../../../../models/Question"; +import {shuffleAnswers} from "../../../../helper/QuizHelper"; +import TemplateDuo from "../../../../templates/TemplateDuo"; +import HttpError from "../../../../services/HttpError"; +import PlayingQuizMultiModeHeader from "./PlayingQuizMultiModeHeader"; +import PlayingQuizMultiModeBody from "./PlayingQuizMultiModeBody"; + + +type RoutePropsType = { + PlayingQuiz: { + runId: string; + quizId: string; + }; +}; + +interface Props { + route: RouteProp<RoutePropsType, "PlayingQuiz">; + navigation: NavigationProp<any>; +} + +export default function PlayingQuizMultiMode({route, navigation}:Props) { + const {runId, quizId} = route.params; + const { getQuizInformations, getActualQuestionOfAQuiz } = useQuizService(); + const [quizInformations, setQuizInformations] = useState<QuizInformations | undefined>(undefined); + const [actualQuestion, setActualQuestion] = useState<Question | undefined>(undefined); + const [score, setScore] = useState(0); + + const fetchQuizInformations = async () => { + // const quizInformationsFetched = await getQuizInformations(quizId); + // if (HttpError.isHttpError(quizInformationsFetched)) { + // return; + // } + const quizInformationsFetched: QuizInformations = { + id: 1, + name: "Quiz de Test", + description: "Ceci est un quiz de test pour vérifier l'interface.", + isCommunity: true, + questionCount: 10, + categoryId: 2, + difficultyId: 3, + authorId: { + id: 42, + username: "TestAuthor", + email: "testauthor@example.com" + }, + createdAt: "2025-01-10T10:00:00Z", + updatedAt: "2025-01-11T12:00:00Z", + category: { + id: 2, + name: "Science" + }, + difficulty: { + id: 3, + name: "Moyenne" + } + }; + + setQuizInformations(quizInformationsFetched); + } + + const fetchActualQuestion = async () => { + const actualQuestionFetched = await getActualQuestionOfAQuiz(runId); + if (HttpError.isHttpError(actualQuestionFetched)) { + return; + } + // const actualQuestionFetched: Question = { + // id: 1, + // text: "Quelle image correspond à une étoile ?", + // type: "imae", + // categoryId: 2, + // quizId: "quiz123", + // order: 1, + // answers: [ + // { + // id: 1, + // text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + // isCorrect: true, + // questionId: 1 + // }, + // { + // id: 2, + // text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + // isCorrect: false, + // questionId: 1 + // }, + // { + // id: 3, + // text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + // isCorrect: false, + // questionId: 1 + // }, + // { + // id: 4, + // text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", + // isCorrect: false, + // questionId: 1 + // } + // ], + // category: { + // id: 2, + // name: "Astronomie" + // } + // }; + const actualQuestionFetchedShuffle = shuffleAnswers(actualQuestionFetched); + setActualQuestion(actualQuestionFetchedShuffle); + } + + useEffect(() => { + fetchQuizInformations(); + }, []); + + useEffect(() => { + fetchActualQuestion(); + }, [quizId]); + + return ( + <TemplateDuo + childrenHeader={<PlayingQuizMultiModeHeader quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} score={score} navigation={navigation}></PlayingQuizMultiModeHeader>} + childrenBody={<PlayingQuizMultiModeBody quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} fetchActualQuestion={fetchActualQuestion} setScore={setScore} navigation={navigation}></PlayingQuizMultiModeBody>} + /> + ); +} \ No newline at end of file diff --git a/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx new file mode 100644 index 0000000..f6fc5f5 --- /dev/null +++ b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx @@ -0,0 +1,225 @@ +import {QuizInformations} from "../../../../models/QuizInformations"; +import {Question} from "../../../../models/Question"; +import {NavigationProp} from "@react-navigation/native"; +import React, {useState} from "react"; +import {useQuizService} from "../../../../services/QuizService"; +import {Answer} from "../../../../models/Answer"; +import HttpError from "../../../../services/HttpError"; +import AnswerButton from "../../../../components/buttons/AnswerButton"; +import {ActivityIndicator, View, Text, StyleSheet} from "react-native"; +import BlueButton from "../../../../components/buttons/BlueButton"; +import {ButtonsStyles} from "../../../../styles/ButtonsStyles"; + + +interface Props { + quizInformations?: QuizInformations; + runId?: string; + actualQuestion: Question; + fetchActualQuestion: () => void; + score: number; + setScore: (score: number) => void; + navigation: NavigationProp<any>; +} + +enum QuizState { + ANSWERING= "Answering", + LOADING= "Loading", + SHOWING_RESULTS= "ShowingResults", +} + +const getStyleOfAnswerButtonText = (answerId: number, selectedAnswerIndex: number | null, correctAnswerId: number | null, quizState: QuizState) => { + if(quizState === QuizState.LOADING) + { + return styles.loadingText; + } + if(quizState === QuizState.ANSWERING) + { + return styles.answerText; + + } + if(quizState === QuizState.SHOWING_RESULTS) + { + if(answerId === correctAnswerId) return styles.correctText; + if(answerId !== correctAnswerId && selectedAnswerIndex === answerId) return styles.incorrectText; + } + return styles.answerText; +} + +const getStyleOfAnswerButton = (answerId: number, selectedAnswerIndex: number | null, correctAnswerId: number | null, quizState: QuizState) => { + if(quizState === QuizState.LOADING) + { + return styles.loadingAnswer; + } + if(quizState === QuizState.ANSWERING) + { + if(selectedAnswerIndex === null) return styles.answerButton; + if(selectedAnswerIndex === answerId) return styles.selectedAnswer; + + } + if(quizState === QuizState.SHOWING_RESULTS) + { + if(answerId === correctAnswerId) return styles.correctAnswer; + if(answerId !== correctAnswerId && selectedAnswerIndex === answerId) return styles.incorrectAnswer; + } + return styles.answerButton; +} + +export default function PlayingQuizMultiModeBody({ quizInformations, runId, actualQuestion, fetchActualQuestion, score, setScore, navigation }: Props) { + const [selectedAnswerId, setSelectedAnswerId] = useState<number | null>(null); + const [quizState, setQuizState] = useState(QuizState.ANSWERING); + const [correctAnswerId, setCorrectAnswerId] = useState<number | null>(null); + const [isLastQuestion, setIsLastQuestion] = useState(false); + + const {answerQuestion} = useQuizService(); + const getCorrectAnswer = (answers: Answer[]): Answer => { + const correctAnswer = answers.find((answer) => answer.isCorrect); + if (!correctAnswer) throw new Error("No correct answer found"); + return correctAnswer; + }; + const onValidation = async () => { + if(selectedAnswerId === null) return; + setQuizState(QuizState.LOADING); + const answerId = actualQuestion.answers.find((answer) => answer.id === selectedAnswerId)?.id; + if(!answerId || !runId) return; + const answereFetched = await answerQuestion(runId, actualQuestion.id, answerId); + + if (HttpError.isHttpError(answereFetched)) { + return; + } + setScore(answereFetched.score); + const correctAnswerIdFetched = getCorrectAnswer(answereFetched.answers.answers).id; + setCorrectAnswerId(correctAnswerIdFetched); + setQuizState(QuizState.SHOWING_RESULTS); + + if (quizInformations === undefined) return; + // Check si c'est la dernière question + if(answereFetched.answers.order === quizInformations.questionCount - 1) { + setIsLastQuestion(true); + return; + } + + }; + const onContinue = () => { + if(isLastQuestion) { + navigation.reset({ + index: 0, + routes: [ + { + name: "EndQuiz", + params: {quizInformations: quizInformations, score: score}, + }, + ] + }); + return; + } + fetchActualQuestion(); + setQuizState(QuizState.ANSWERING); + setSelectedAnswerId(null); + setCorrectAnswerId(null); + } + + const onAnsweredButtonClicked = (answerId: number) => { + if(quizState === QuizState.SHOWING_RESULTS || quizState === QuizState.LOADING) { + return; + } + setSelectedAnswerId(answerId); + } + + return ( + <View style={styles.buttonContainer}> + {actualQuestion ? ( + <> + {actualQuestion.answers.map((answer, index) => ( + <AnswerButton + key={index} + text={answer.text} + handleButtonPressed={() => onAnsweredButtonClicked(answer.id)} + buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)} + buttonText={getStyleOfAnswerButtonText(answer.id, selectedAnswerId, correctAnswerId, quizState)} + /> + ))} + <View style={styles.playButtonContainer}> + <BlueButton onPress={() => quizState === QuizState.ANSWERING ? onValidation() : onContinue()} text={quizState === QuizState.ANSWERING ? "CONFIRM" : "CONTINUE"} buttonStyle={{borderRadius: 50}} isDisabled={false}/> + </View> + </> + ) : ( + <View style={styles.loadingContainer}> + <ActivityIndicator size="large" color="#2b73fe" /> + <Text style={styles.loadingText}>Loading...</Text> + </View> + )} + </View> + ); + +} + +const styles = StyleSheet.create({ + buttonContainer: { + display: 'flex', + flex: 1, + alignItems: 'center', + justifyContent: 'flex-start', + width: '100%', + marginVertical: '3%', + }, + loadingContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + playButtonContainer: { + marginTop: 20, // Ajoute un espacement au-dessus du bouton Play + width: '80%', // S'assure que le bouton prend toute la largeur disponible + alignItems: 'center', // Centre le bouton horizontalement + }, + correctAnswer: { + ...ButtonsStyles.answerButton, + borderColor: 'green', + borderWidth: 2, + }, + incorrectAnswer: { + ...ButtonsStyles.answerButton, + borderColor: 'red', + borderWidth: 2, + }, + loadingAnswer: { + ...ButtonsStyles.answerButton, + backgroundColor: '#4F6367', + borderWidth: 2, + }, + selectedAnswer: { + ...ButtonsStyles.answerButton, + borderColor: 'grey', + borderWidth: 2, + }, + answerText: { + color: 'black', + textAlign: 'center', + }, + correctText: { + fontWeight: 'bold', + color: 'green', + }, + incorrectText: { + fontWeight: 'bold', + color: 'red', + }, + loadingText: { + color: 'black', + textAlign: 'center', + }, + answerButton: { + backgroundColor: "#F3F3F3", + height: '15%', + alignItems: 'center', + width: '80%', + marginVertical: '3%', + justifyContent: "center", + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.25, + shadowRadius: 3.5, + elevation: 5, + }, +}); + diff --git a/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeHeader.tsx b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeHeader.tsx new file mode 100644 index 0000000..c5da7d2 --- /dev/null +++ b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeHeader.tsx @@ -0,0 +1,95 @@ +import {QuizInformations} from "../../../../models/QuizInformations"; +import {Question} from "../../../../models/Question"; +import {NavigationProp} from "@react-navigation/native"; +import {useTranslation} from "react-i18next"; +import {useState} from "react"; +import {View, Text, StyleSheet} from "react-native"; +import GoHomeButton from "../../../../components/PlayingQuiz/GoHomeButton"; +import {TextsStyles} from "../../../../styles/TextsStyles"; +import ConfirmModal from "../../../../components/PlayingQuiz/ComfirmModal"; + +interface Props { + quizInformations?: QuizInformations; + runId?: string; + actualQuestion: Question; + score: number; + navigation: NavigationProp<any> +} + +export default function PlayingQuizMultiModeHeader({quizInformations, runId, actualQuestion, score, navigation}: Props) { + const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); + const {t} = useTranslation(); + + const handleConfirmModalConfirm = () => { + navigation.reset({ + index: 0, + routes: [ + { + name: "TabNavigator", + }, + ] + }); + } + + return ( + <View style={styles.container}> + <View style={styles.header}> + <GoHomeButton navigation={navigation} onPress={setIsConfirmModalVisible}/> + <Text style={styles.codeQuizText}>ID QUIZZ : {runId}</Text> + </View> + <View style={styles.questionAndScoreContainer}> + <View style={styles.InformationsContainer}> + <Text style={TextsStyles.titleText}>{t("app.screens.question.question")}</Text> + <Text style={TextsStyles.titleText}>{actualQuestion ? actualQuestion.order + 1 :"?"}/{quizInformations ? quizInformations.questionCount : "?"}</Text> + </View> + <View style={styles.InformationsContainer}> + <Text style={TextsStyles.titleText}>{t("app.screens.question.score")}</Text> + <Text style={TextsStyles.titleText}>{score}/{quizInformations ? quizInformations.questionCount : "?"}</Text> + </View> + </View> + <View style={styles.QuizQuestionContainer}> + <Text style={TextsStyles.subtitleText}>{actualQuestion ? actualQuestion.text : "Loading..."}</Text> + </View> + <ConfirmModal visible={isConfirmModalVisible} onClose={()=>setIsConfirmModalVisible(false)} onConfirm={handleConfirmModalConfirm}/> + + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + marginHorizontal: '5%', + }, + questionAndScoreContainer: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-around', + alignItems: 'center', + marginVertical: '2%', + }, + InformationsContainer: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + gap: '5%', + }, + QuizNumContainer: { + }, + QuizQuestionContainer: { + marginVertical: "2.5%", + }, + infoQuizContainer: { + marginBottom: '5%', + }, + codeQuizText: { + fontSize: 18, + color: '#000000', + }, + header: { + display: "flex", + flexDirection: "row", + justifyContent: "flex-start", + gap: '5%', + } +}); \ No newline at end of file -- GitLab From d40bcad4226b58b168c6e1cfee301240e9a6fce5 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 12 Jan 2025 18:31:40 +0100 Subject: [PATCH 253/283] fix: shuffle import --- screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx | 26 +++----------------- screens/PlayingQuiz/PlayingQuiz.tsx | 4 +-- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx index a9059f6..6e11276 100644 --- a/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx +++ b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx @@ -16,30 +16,12 @@ interface Props { export default function EndQuizChild({ navigation, quizInformations, score }: Props) { - const {restartQuiz, remainingQuiz} = useQuizService() + const {restartQuiz, remainingQuiz, createRun} = useQuizService() const handleRestartPress = async () => { - const isRestarted = await restartQuiz(quiz.id); - if(HttpError.isHttpError(isRestarted)){ - return - } - if(!isRestarted) { - return; - } - const quizRestarted = await remainingQuiz(quiz.id); - if(HttpError.isHttpError(quizRestarted)){ - return - } - - navigation.reset({ - index: 0, - routes: [ - { - name: "PlayingQuiz", - params: {quizRecovered: quizRestarted}, - }, - ] - }); + const quizId = quizInformations.id.toString(); + const runId = await createRun(quizId); + navigation.navigate('PlayingQuiz', {runId: runId, quizId: quizId}); }; const handleBackToMenu = () => { diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 6b2dd76..123586d 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -7,7 +7,7 @@ import {useQuizService} from "../../services/QuizService"; import HttpError from "../../services/HttpError"; import {Question} from "../../models/Question"; import PlayingQuizBody from "./PlayingQuizBody"; -import { shuffleAnswers } from "../../helper/QuizHelper"; +import { shuffleAnswersOfQuiz } from "../../helper/QuizHelper"; type RoutePropsType = { @@ -105,7 +105,7 @@ export default function PlayingQuiz({route, navigation}:Props) { name: "Astronomie" } }; - const actualQuestionFetchedShuffle = shuffleAnswers(actualQuestionFetched); + const actualQuestionFetchedShuffle = shuffleAnswersOfQuiz(actualQuestionFetched); setActualQuestion(actualQuestionFetchedShuffle); } -- GitLab From e899008d1659df4665588ea67751701b5eab0d93 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Sun, 12 Jan 2025 18:40:42 +0100 Subject: [PATCH 254/283] feat: restart quiz at the end --- screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx | 2 +- screens/PlayingQuiz/PlayingQuiz.tsx | 79 ++------------------ 2 files changed, 9 insertions(+), 72 deletions(-) diff --git a/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx index 6e11276..767dce5 100644 --- a/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx +++ b/screens/PlayingQuiz/EndQuiz/EndQuizChild.tsx @@ -37,7 +37,7 @@ export default function EndQuizChild({ navigation, quizInformations, score }: Pr <Text style={styles.TextBodyTitle}>{((score/quizInformations.questionCount)*100 > 50 ) ? 'Victory' : 'Defeat'}</Text> <Text style={styles.TextBody}>you finish the test with a score of {`${score}/${quizInformations.questionCount}`} ! </Text> <Image source={require('../../../assets/uWin.png')} /> - {/*<DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton>*/} + <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton> <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu} buttonStyle={styles.blueButton} buttonText={styles.blueButtonText}></DefaultButton> </View> </View> diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 123586d..50ca0a4 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -30,81 +30,18 @@ export default function PlayingQuiz({route, navigation}:Props) { const [score, setScore] = useState(0); const fetchQuizInformations = async () => { - // const quizInformationsFetched = await getQuizInformations(quizId); - // if (HttpError.isHttpError(quizInformationsFetched)) { - // return; - // } - const quizInformationsFetched: QuizInformations = { - id: 1, - name: "Quiz de Test", - description: "Ceci est un quiz de test pour vérifier l'interface.", - isCommunity: true, - questionCount: 10, - categoryId: 2, - difficultyId: 3, - authorId: { - id: 42, - username: "TestAuthor", - email: "testauthor@example.com" - }, - createdAt: "2025-01-10T10:00:00Z", - updatedAt: "2025-01-11T12:00:00Z", - category: { - id: 2, - name: "Science" - }, - difficulty: { - id: 3, - name: "Moyenne" - } - }; - + const quizInformationsFetched = await getQuizInformations(quizId); + if (HttpError.isHttpError(quizInformationsFetched)) { + return; + } setQuizInformations(quizInformationsFetched); } const fetchActualQuestion = async () => { - // const actualQuestionFetched = await getActualQuestionOfAQuiz(runId); - // if (HttpError.isHttpError(actualQuestionFetched)) { - // return; - // } - const actualQuestionFetched: Question = { - id: 1, - text: "Quelle image correspond à une étoile ?", - type: "imae", - categoryId: 2, - quizId: "quiz123", - order: 1, - answers: [ - { - id: 1, - text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - isCorrect: true, - questionId: 1 - }, - { - id: 2, - text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - isCorrect: false, - questionId: 1 - }, - { - id: 3, - text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - isCorrect: false, - questionId: 1 - }, - { - id: 4, - text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - isCorrect: false, - questionId: 1 - } - ], - category: { - id: 2, - name: "Astronomie" - } - }; + const actualQuestionFetched = await getActualQuestionOfAQuiz(runId); + if (HttpError.isHttpError(actualQuestionFetched)) { + return; + } const actualQuestionFetchedShuffle = shuffleAnswersOfQuiz(actualQuestionFetched); setActualQuestion(actualQuestionFetchedShuffle); } -- GitLab From 7a2795fa95ddd3ccdcae3057badbf3c1c77d8f8e Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 12 Jan 2025 18:58:41 +0100 Subject: [PATCH 255/283] feat: new model --- models/{ => Response}/QuestionResponse.ts | 2 +- models/Response/RunsInformationResponse.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) rename models/{ => Response}/QuestionResponse.ts (62%) create mode 100644 models/Response/RunsInformationResponse.ts diff --git a/models/QuestionResponse.ts b/models/Response/QuestionResponse.ts similarity index 62% rename from models/QuestionResponse.ts rename to models/Response/QuestionResponse.ts index 4e1cee6..ccfd9ab 100644 --- a/models/QuestionResponse.ts +++ b/models/Response/QuestionResponse.ts @@ -1,4 +1,4 @@ -import {Question} from "./Question"; +import {Question} from "../Question"; export interface QuestionResponse { question: Question; diff --git a/models/Response/RunsInformationResponse.ts b/models/Response/RunsInformationResponse.ts new file mode 100644 index 0000000..a02f92a --- /dev/null +++ b/models/Response/RunsInformationResponse.ts @@ -0,0 +1,5 @@ +import {RunInformations} from "../RunInformations"; + +export interface RunsInformationResponse { + runs: RunInformations[]; +} \ No newline at end of file -- GitLab From 702bf2618cd5bdf0ad758976625623a53a1cfbbe Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 12 Jan 2025 18:58:46 +0100 Subject: [PATCH 256/283] feat: new model --- models/{ => Response}/AnswerResponse.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename models/{ => Response}/AnswerResponse.ts (79%) diff --git a/models/AnswerResponse.ts b/models/Response/AnswerResponse.ts similarity index 79% rename from models/AnswerResponse.ts rename to models/Response/AnswerResponse.ts index 24a83f1..ab22c33 100644 --- a/models/AnswerResponse.ts +++ b/models/Response/AnswerResponse.ts @@ -1,5 +1,5 @@ -import {Answer} from "./Answer"; -import {Category} from "./Quiz"; +import {Answer} from "../Answer"; +import {Category} from "../Quiz"; export interface AnswerResponse { score: number; -- GitLab From 260c2c0b41d6adc1e8c026a5acf3786cfd9106d4 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 12 Jan 2025 18:59:19 +0100 Subject: [PATCH 257/283] feat: My quizzes --- components/PlayQuiz/RunStartedList.tsx | 134 ++++++++++++++++++ screens/Home/MyQuizzes/InformationsOfRuns.tsx | 124 ++++++++++++++++ screens/Home/MyQuizzes/MyQuizzes.tsx | 29 +++- templates/TemplateRunList.tsx | 70 +++++++++ 4 files changed, 352 insertions(+), 5 deletions(-) create mode 100644 components/PlayQuiz/RunStartedList.tsx create mode 100644 screens/Home/MyQuizzes/InformationsOfRuns.tsx create mode 100644 templates/TemplateRunList.tsx diff --git a/components/PlayQuiz/RunStartedList.tsx b/components/PlayQuiz/RunStartedList.tsx new file mode 100644 index 0000000..6a7da51 --- /dev/null +++ b/components/PlayQuiz/RunStartedList.tsx @@ -0,0 +1,134 @@ +import React from "react"; +import { FlatList, View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from "react-native"; +import {Quiz} from "../../models/Quiz"; +import { NavigationProp } from "@react-navigation/native"; +import {RunInformations} from "../../models/RunInformations"; + + + +/** + * QuizList : Un composant affichant une liste de quiz avec leur titre, nombre de questions, et catégorie. + * + * @component + * + * @param navigation - Propriété de navigation utilisée pour naviguer vers l'écran "InformationsOfQuiz". + * @param quizList - Liste des quiz à afficher. Peut être null ou undefined. + * @param isLoadingData - Boolean indiquant si les données sont en cours de chargement. + * + * @example + * <QuizList + * navigation={navigation} + * quizList={quizzes} + * isLoadingData={isLoading} + * /> + * + * @description + * - Ce composant gère trois états : + * 1. Chargement : Affiche un spinner si `isLoadingData` est true. + * 2. Liste des quiz : Affiche une liste interactive des quiz si `quizList` contient des éléments. + * 3. Liste vide : Affiche un message si `quizList` est vide. + * + * - Chaque quiz affiche : + * - Son code en tant que titre. + * - Son nombre total de questions. + * - Sa catégorie, affichée en majuscules. + * + * @returns Un composant React contenant une liste interactive de quiz ou des messages en fonction de l'état. + */ +interface Props { + runList: RunInformations[] | undefined; + onRunPressed:(runInfo: RunInformations) => void; + isLoadingData: boolean; +} +export default function RunStartedList({ runList, isLoadingData, onRunPressed }: Props) { + const renderQuizItem = ({ item }: { item: RunInformations }) => ( + <TouchableOpacity style={styles.quizItem} onPress={() => onRunPressed(item)}> + <Text style={styles.quizTitle}>{item.id}</Text> + <Text style={styles.quizDetails}> + {item.score} + </Text> + </TouchableOpacity> + ); + + return ( + <View style={styles.container}> + {isLoadingData ? ( + <View style={styles.loadingContainer}> + <ActivityIndicator size="large" color="#2b73fe" /> + <Text style={styles.loadingText}>Loading quizzes...</Text> + </View> + ) : runList && runList.length > 0 ? ( + <FlatList + data={runList} + renderItem={renderQuizItem} + keyExtractor={(item) => item.id} + showsVerticalScrollIndicator={true} + contentContainerStyle={styles.listContent} + /> + ) : ( + <Text style={styles.emptyMessage}>No quizzes available.</Text> + )} + </View> + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "white", + padding: 10, + borderRadius: 10, + borderColor: "#fafafa", + borderWidth: 3, + elevation: 5, // Ombre pour Android + shadowColor: "#000", // Ombre pour iOS + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + width: "100%", // Assurez-vous que le conteneur occupe toute la largeur + }, + listContent: { + }, + quizItem: { + backgroundColor: "#f3f3f3", + padding: 15, + borderRadius: 10, + marginVertical: 5, + elevation: 3, // Ombre pour Android + shadowColor: "#000", // Ombre pour iOS + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 2, + width: "100%", + alignSelf: "center", // Centre les éléments dans leur conteneur + }, + quizTitle: { + fontSize: 22, + fontWeight: "bold", + color: "#000", + marginBottom: 5, + textAlign: "center", + }, + quizDetails: { + fontSize: 18, + color: "#555", + textAlign: "center", + }, + emptyMessage: { + fontSize: 18, + color: "#888", + textAlign: "center", + marginTop: 20, + }, + loadingContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + loadingText: { + fontSize: 16, + color: "#555", + marginTop: 10, + textAlign: "center", + }, +}); diff --git a/screens/Home/MyQuizzes/InformationsOfRuns.tsx b/screens/Home/MyQuizzes/InformationsOfRuns.tsx new file mode 100644 index 0000000..5ca934b --- /dev/null +++ b/screens/Home/MyQuizzes/InformationsOfRuns.tsx @@ -0,0 +1,124 @@ +import React, {useEffect, useState} from "react"; +import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import TemplateMenu from "../../../templates/TemplateMenu"; +import {NavigationProp, RouteProp} from "@react-navigation/native"; +import {Quiz} from "../../../models/Quiz"; +import {RunInformations} from "../../../models/RunInformations"; +import {QuizInformations} from "../../../models/QuizInformations"; +import HttpError from "../../../services/HttpError"; +import {useQuizService} from "../../../services/QuizService"; + +type RoutePropsType = { + InformationsOfRuns: { + runInformations: RunInformations; + }; +}; +interface Props { + navigation: NavigationProp<any>; + route: RouteProp<RoutePropsType, "InformationsOfRuns">; +} + +export default function InformationsOfRuns({ navigation, route }: Props) { + const { runInformations, } = route.params; + const {getQuizInformations} = useQuizService(); + const [quizInformations, setQuizInformations] = useState<QuizInformations>(); + + const fetchQuizInformations = async () => { + const quizInformations = await getQuizInformations(runInformations.quizId); + if (quizInformations instanceof HttpError) { + return; + } + setQuizInformations(quizInformations); + } + + useEffect(() => { + fetchQuizInformations(); + }, []); + + const onPlay = () => { + navigation.reset({ + index: 0, + routes: [ + { + name: "PlayingQuiz", + params: {runId: runInformations?.id, quizId: runInformations?.quizId }, + }, + ] + }); + } + + return ( + <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> + <View style={styles.container}> + <Text style={styles.quizTitle}>{quizInformations ? quizInformations.name : "Loading..."}</Text> + <Text style={styles.quizDescription}>{quizInformations ? quizInformations.description :"Loading..."}</Text> + + <View style={styles.aboutContainer}> + <Text style={styles.aboutTitle}>About this quiz :</Text> + <Text style={styles.aboutDetails}>{runInformations.questionIndex}/{quizInformations ? quizInformations.questionCount : "Loading..."} questions, {quizInformations ? quizInformations.difficulty.name : "Loading..."}</Text> + <Text style={styles.aboutDetails}>By : {quizInformations?.authorId && "Unknown"}</Text> + </View> + + <TouchableOpacity style={styles.playButton} onPress={onPlay}> + <Text style={styles.playButtonText}>PLAY</Text> + </TouchableOpacity> + </View> + </TemplateMenu> + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingHorizontal: 20, + paddingVertical: 20, + justifyContent: "flex-start", + }, + quizTitle: { + fontSize: 24, + fontWeight: "bold", + color: "#00b3f4", + marginBottom: 10, + }, + quizDescription: { + fontSize: 16, + color: "#555", + marginBottom: 20, + height: "15%", + }, + aboutContainer: { + height: "50%", + backgroundColor: "white", + borderRadius: 10, + padding: 15, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 3, + elevation: 5, + }, + aboutTitle: { + fontSize: 18, + fontWeight: "bold", + marginBottom: 10, + color: "#000", + }, + aboutDetails: { + fontSize: 16, + color: "#555", + marginBottom: 10, + }, + playButton: { + backgroundColor: "#007BFF", + paddingVertical: 15, + borderRadius: 10, + alignItems: "center", + justifyContent: "center", + marginTop: 20, + }, + playButtonText: { + fontSize: 18, + fontWeight: "bold", + color: "white", + }, +}); diff --git a/screens/Home/MyQuizzes/MyQuizzes.tsx b/screens/Home/MyQuizzes/MyQuizzes.tsx index 66004ca..1e5ce51 100644 --- a/screens/Home/MyQuizzes/MyQuizzes.tsx +++ b/screens/Home/MyQuizzes/MyQuizzes.tsx @@ -1,17 +1,36 @@ -import TemplateQuizList from "../../../templates/TemplateQuizList"; import {NavigationProp} from "@react-navigation/native"; -import {useState} from "react"; -import {Quiz} from "../../../models/Quiz"; +import React, {useEffect, useState} from "react"; +import TemplateRunList from "../../../templates/TemplateRunList"; +import {RunInformations} from "../../../models/RunInformations"; +import {useQuizService} from "../../../services/QuizService"; +import HttpError from "../../../services/HttpError"; interface Props { navigation: NavigationProp<any> } export default function MyQuizzes({navigation}: Props) { - const [quizList, setQuizList] = useState<Quiz[] | null>([]); + const [runList, setRunList] = useState<RunInformations[] | undefined>(undefined) const [input, setInput] = useState<string>("") + const {getAllRuns} = useQuizService(); + const getRuns = async () => { + const runs = await getAllRuns(); + if(HttpError.isHttpError(runs)) { + console.log("Error while getting runs:", runs); + return + } + setRunList(runs); + } + + useEffect(() => { + getRuns(); + },[]); + + const onQuizPressed = (runInfo: RunInformations) => { + navigation.navigate("InformationsOfRuns", { runInformations: runInfo }); + }; return ( - <TemplateQuizList title={"My quizzes"} setTextInInput={setInput} quizList={quizList} navigation={navigation} buttonGoBack={true} isLoadingData={false}/> + <TemplateRunList title={"My quizzes"} setTextInInput={setInput} runList={runList} navigation={navigation} buttonGoBack={true} isLoadingData={false} onRunPressed={onQuizPressed}/> ); } \ No newline at end of file diff --git a/templates/TemplateRunList.tsx b/templates/TemplateRunList.tsx new file mode 100644 index 0000000..20125c1 --- /dev/null +++ b/templates/TemplateRunList.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import {View, Text, StyleSheet} from "react-native"; +import TemplateMenu from "./TemplateMenu"; +import {NavigationProp} from "@react-navigation/native"; +import InputPlayQuiz from "../components/PlayQuiz/InputPlayQuiz"; +import QuizList from "../components/lists/QuizList"; +import {Quiz} from "../models/Quiz"; +import RunStartedList from "../components/PlayQuiz/RunStartedList"; +import {RunInformations} from "../models/RunInformations"; + + +/** + * TemplateQuizList : Un composant principal pour afficher une liste de quiz. + * Il inclut un menu, un titre, un champ d'entrée pour les recherches et une liste de quiz. + * + * @param navigation - L'objet de navigation pour naviguer entre les écrans + * @param title - Le titre affiché en haut de la liste + * @param setTextInInput - Fonction pour gérer les modifications dans le champ d'entrée + * @param quizList - La liste des quiz à afficher + * @param buttonGoBack - boolean qui dit si le bouton de retour doit être affiché + */ + +interface Props { + navigation: NavigationProp<any> + title: string + setTextInInput: (text: string) => void + runList: RunInformations[] | undefined + buttonGoBack: boolean + isLoadingData: boolean + nextPage?: () => void; + onRunPressed:(runInfo: RunInformations) => void; +} + +export default function TemplateRunList({navigation, title, setTextInInput, runList, buttonGoBack, isLoadingData, onRunPressed }: Props) { + return ( + <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={buttonGoBack}> + <View style={styles.globalContainer}> + <Text style={styles.title}>{title}</Text> + <View style={styles.contentContainer}> + <RunStartedList runList={runList} isLoadingData={isLoadingData} onRunPressed={onRunPressed}/> + </View> + </View> + </TemplateMenu> + ); +} + +const styles = StyleSheet.create({ + globalContainer: { + display: "flex", + height: "100%", + width: "100%", + paddingHorizontal: "3%", // Ajoute une marge de 5% à gauche et à droite + alignItems: "center", // Centre tous les enfants horizontalement + }, + title: { + fontSize: 40, + fontWeight: "bold", + color: "#00b3f4", + marginBottom: 10, + alignSelf: "center", // Centre uniquement le titre + }, + contentContainer: { + display: "flex", + flexDirection: "column", + width: "100%", // Utilise tout l'espace restant à l'intérieur de `globalContainer` + height: "75%", + alignItems: "center", // Centre tous les enfants horizontalement + gap: "2%", + }, +}); -- GitLab From d4ae1c4a6fade65b2e42c7197f6194740ccbfe00 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 12 Jan 2025 18:59:30 +0100 Subject: [PATCH 258/283] feat: new services --- screens/Home/HomeChild.tsx | 2 +- services/QuizService.ts | 82 ++++++++++++++++++++++++-------------- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 85120e4..9532c96 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -55,7 +55,7 @@ export default function HomeChild({navigation}: Props) { <View style={styles.buttonContainer}> <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonQuickGamePressed}/> <MenuButton text={"PLAY"} handleButtonPressed={handleButtonGeneratePressed}/> - {/*<MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/>*/} + <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/> </View> <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> </View> diff --git a/services/QuizService.ts b/services/QuizService.ts index a243443..07b74d9 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -2,13 +2,13 @@ import HttpError from "./HttpError"; import {Quiz} from "../models/Quiz"; import axios from "axios"; import {Answer} from "../models/Answer"; -import {shuffleAnswersOfQuiz} from "../helper/QuizHelper"; import {Question} from "../models/Question"; -import {AnswerResponse} from "../models/AnswerResponse"; +import {AnswerResponse} from "../models/Response/AnswerResponse"; import {QuizInformations} from "../models/QuizInformations"; -import {QuestionResponse} from "../models/QuestionResponse"; +import {QuestionResponse} from "../models/Response/QuestionResponse"; import {QuizGenerateAnswer} from "../models/QuizGenerateAnswer"; import {RunInformations} from "../models/RunInformations"; +import {RunsInformationResponse} from "../models/Response/RunsInformationResponse"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API @@ -117,32 +117,32 @@ export const useQuizService = () => { } }; - const remainingQuiz = async (id: string): Promise<Quiz | HttpError> => { - const requestBody = { - id, - }; - - try { - const response = await axios.post<Quiz>(`${url}/quiz/remaining`, requestBody, { - headers: { - "Content-Type": "application/json", - }, - }); - - // Récupération des données retournées par l'API - return shuffleAnswersOfQuiz(response.data); - } catch (error: any) { - console.log("Error while remaining quiz:", error); - - if (error.response) { - // Gestion des erreurs HTTP - return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); - } else { - // Gestion des erreurs réseau ou autres - return new HttpError(500, "Unexpected error: " + error.message); - } - } - }; + // const remainingQuiz = async (id: string): Promise<Quiz | HttpError> => { + // const requestBody = { + // id, + // }; + // + // try { + // const response = await axios.post<Quiz>(`${url}/quiz/remaining`, requestBody, { + // headers: { + // "Content-Type": "application/json", + // }, + // }); + // + // // Récupération des données retournées par l'API + // return shuffleAnswersOfQuiz(response.data); + // } catch (error: any) { + // console.log("Error while remaining quiz:", error); + // + // if (error.response) { + // // Gestion des erreurs HTTP + // return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + // } else { + // // Gestion des erreurs réseau ou autres + // return new HttpError(500, "Unexpected error: " + error.message); + // } + // } + // }; const getAnswerQuiz = async ( codeQuiz: string, @@ -285,12 +285,31 @@ export const useQuizService = () => { } } + const getAllRuns = async (): Promise<RunInformations[] | HttpError> => { + try { + const response = await axios.get<RunsInformationResponse>(`${url}/run/userRuns`, { + headers: { + "Content-Type": "application/json", + }, + }); + + return response.data.runs; + } catch (error: any) { + console.log("Error while getAllRuns:", error); + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + } + return { generateQuiz: generateQuiz, getAnswerQuiz: getAnswerQuiz, - remainingQuiz: remainingQuiz, + // remainingQuiz: remainingQuiz, getRandomQuiz: getRandomQuiz, getCommunityQuiz: getCommunityQuiz, restartQuiz: restartQuiz, @@ -298,6 +317,7 @@ export const useQuizService = () => { answerQuestion: answerQuestion, getActualQuestionOfAQuiz: getActualQuestionOfAQuiz, getQuizInformations: getQuizInformations, - getRunsInfo: getRunsInfo + getRunsInfo: getRunsInfo, + getAllRuns: getAllRuns } } \ No newline at end of file -- GitLab From 3ec74ecbf6d2f4c22bb5316beeead3e11452a9ba Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Sun, 12 Jan 2025 18:59:56 +0100 Subject: [PATCH 259/283] feat: navigator --- routes/StackNavigator.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index 377abc3..86ab70b 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -14,6 +14,7 @@ import PlayQuiz from "../screens/Home/PlayQuiz/PlayQuiz"; import MyQuizzes from "../screens/Home/MyQuizzes/MyQuizzes"; import InformationsOfQuiz from "../screens/Community/InformationsOfQuiz/InformationsOfQuiz"; import MultiInformationsOfQuiz from "../screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz"; +import InformationsOfRuns from "../screens/Home/MyQuizzes/InformationsOfRuns"; const Stack = createNativeStackNavigator(); @@ -29,6 +30,7 @@ export default function StackNavigator() { <Stack.Screen name="EndQuiz" component={EndQuiz} options={{ headerShown: false }}/> <Stack.Screen name="MyQuizzes" component={MyQuizzes} options={{ headerShown: false }}/> <Stack.Screen name="InformationsOfQuiz" component={InformationsOfQuiz} options={{ headerShown: false }}/> + <Stack.Screen name="InformationsOfRuns" component={InformationsOfRuns} options={{ headerShown: false }}/> <Stack.Screen name="Multiplayer" component={Multiplayer} options={{ headerShown: false }}/> <Stack.Screen name="MultiplayerCommunity" component={MultiplayerCommunity} options={{ headerShown: false }}/> -- GitLab From 646d6dfff279ade7c9a768daa0727cb8ca20accf Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Mon, 13 Jan 2025 02:40:45 +0100 Subject: [PATCH 260/283] feat: play game with websockets --- components/Multiplayer/ModalInvitePlayer.tsx | 7 +- models/Response/AnswerWsResponse.ts | 5 + routes/StackNavigator.tsx | 3 +- screens/Multiplayer/Lobby/Lobby.tsx | 69 +++++++-- .../PlayingQuizMultiMode.tsx | 134 ++++++------------ .../PlayingQuizMultiModeBody.tsx | 95 +++++++------ .../PlayingQuizMultiModeHeader.tsx | 8 +- .../MultiInformationsOfQuiz.tsx | 2 +- .../OnlineQuiz/OnlineCreateLobby.tsx | 3 +- .../Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx | 47 +++--- .../OnlineQuiz/OnlineSelectMode.tsx | 2 +- screens/PlayingQuiz/PlayingQuizHeader.tsx | 2 +- 12 files changed, 192 insertions(+), 185 deletions(-) create mode 100644 models/Response/AnswerWsResponse.ts diff --git a/components/Multiplayer/ModalInvitePlayer.tsx b/components/Multiplayer/ModalInvitePlayer.tsx index dbf2294..2c5fab8 100644 --- a/components/Multiplayer/ModalInvitePlayer.tsx +++ b/components/Multiplayer/ModalInvitePlayer.tsx @@ -4,9 +4,10 @@ import WhiteButton from "../buttons/WhiteButton"; interface Props { showModal: boolean; onClosePressed: () => void; + roomId: string; } -export default function ModalInvitePlayer({showModal, onClosePressed,}: Props) { +export default function ModalInvitePlayer({showModal, onClosePressed, roomId}: Props) { return ( <Modal visible={showModal} animationType="slide" transparent={true}> <View style={styles.centeredView}> @@ -14,7 +15,7 @@ export default function ModalInvitePlayer({showModal, onClosePressed,}: Props) { <Text style={styles.title}>JOIN</Text> <Image source={require('../../assets/favicon.png')}/> <Text style={styles.or}>OR</Text> - <Text style={styles.link}>https://klebert-host.com/join_quiz/6975</Text> + <Text style={styles.link}>Room ID: {roomId}</Text> <View style={styles.buttonGroup}> <WhiteButton text="CLOSE" onPress={onClosePressed} isDisabled={false}/> </View> @@ -68,5 +69,7 @@ const styles = StyleSheet.create({ }, link: { color: '#0000EE', + fontSize: 22, + fontWeight: "bold", } }); \ No newline at end of file diff --git a/models/Response/AnswerWsResponse.ts b/models/Response/AnswerWsResponse.ts new file mode 100644 index 0000000..41d72db --- /dev/null +++ b/models/Response/AnswerWsResponse.ts @@ -0,0 +1,5 @@ +import {Answer} from "../Answer"; + +export interface AnswerWsResponse { + answers: Answer[]; +} \ No newline at end of file diff --git a/routes/StackNavigator.tsx b/routes/StackNavigator.tsx index 86ab70b..58dd246 100644 --- a/routes/StackNavigator.tsx +++ b/routes/StackNavigator.tsx @@ -15,6 +15,7 @@ import MyQuizzes from "../screens/Home/MyQuizzes/MyQuizzes"; import InformationsOfQuiz from "../screens/Community/InformationsOfQuiz/InformationsOfQuiz"; import MultiInformationsOfQuiz from "../screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz"; import InformationsOfRuns from "../screens/Home/MyQuizzes/InformationsOfRuns"; +import PlayingQuizMultiMode from "../screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiMode"; const Stack = createNativeStackNavigator(); @@ -31,7 +32,7 @@ export default function StackNavigator() { <Stack.Screen name="MyQuizzes" component={MyQuizzes} options={{ headerShown: false }}/> <Stack.Screen name="InformationsOfQuiz" component={InformationsOfQuiz} options={{ headerShown: false }}/> <Stack.Screen name="InformationsOfRuns" component={InformationsOfRuns} options={{ headerShown: false }}/> - + <Stack.Screen name="PlayingQuizMultiMode" component={PlayingQuizMultiMode} options={{ headerShown: false }}/> <Stack.Screen name="Multiplayer" component={Multiplayer} options={{ headerShown: false }}/> <Stack.Screen name="MultiplayerCommunity" component={MultiplayerCommunity} options={{ headerShown: false }}/> <Stack.Screen name="OnlineQuiz" component={OnlineQuiz} options={{ headerShown: false }}/> diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index c5a24eb..bdcdb42 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -18,19 +18,24 @@ interface Props { type RoutePropsType = { Lobby: { - runId: string; + quizIdOrRoomId: string; nbPlayers: number; + isHost: boolean; }; }; + + export default function Lobby({navigation, route}: Props) { - const { runId, nbPlayers} = route.params; + const { quizIdOrRoomId, nbPlayers, isHost} = route.params; - const [isHost, setIsHost] = useState<boolean>(true); + // const [isHost, setIsHost] = useState<boolean>(true); const [showModal, setShowModal] = useState<boolean>(false); const [players, setPlayers] = useState<User[]>([{ id: 0, username: 'Invite', email: 'invite@email.com'}]); const {getCookie} = useCookie('access_token'); const [socket, setSocket] = useState<Socket | null>(null); + const [roomId, setRoomId] = useState<string>(""); + const [runId, setRunId] = useState<string>(""); const userInvite = { id: 0, username: 'Invite', email: 'invite@email.com'} const initSocket = async () => { @@ -43,11 +48,39 @@ export default function Lobby({navigation, route}: Props) { }); setSocket(newSocket); + console.log(isHost); - newSocket.on("connect", () => { - console.log("Connected to the socket server id :",runId); - newSocket.emit("joinRoom", { roomId: runId }); - }); + if(isHost) { + newSocket.on("roomCreated", (data) => { + console.log("Room created : ", data.id); + console.log("Connected to the server"); + + setRoomId(data.id); + console.log("Host id room : ", data.id); + newSocket.emit("joinRoom", { roomId: data.id }); + + setTimeout(() => { + console.log("Sending generateRun"); + newSocket.emit("generateRun"); + },1000); + }); + + newSocket.on("connect", () => { + newSocket.emit("createRoom", { quizId: quizIdOrRoomId }); + }); + console.log("Host"); + + } + else { + console.log("pas Host id room : ", quizIdOrRoomId); + newSocket.emit("joinRoom", { roomId: quizIdOrRoomId }); + + setTimeout(() => { + console.log("Sending generateRun"); + newSocket.emit("generateRun"); + },1000); + + } newSocket.on("connect_error", (err) => { console.error("Connection error: ", err.message); @@ -63,16 +96,22 @@ export default function Lobby({navigation, route}: Props) { setPlayers([userInvite, ...data]); }); - // Nettoyez la connexion lorsque le composant est démonté - return () => { - newSocket.disconnect(); - setSocket(null); - }; + newSocket.on("runId", (data) => { + console.log("Run ID:", data); + setRunId(data); + }); } + + useEffect(() => { initSocket(); - }, [runId]); + // return () => { + // if(socket === null) return; + // socket.disconnect(); + // setSocket(null); + // }; + }, []); const item = ({ item }: { item: User }) => { return ( @@ -101,7 +140,7 @@ export default function Lobby({navigation, route}: Props) { }; const onPlayPressed = () => { - navigation.navigate("PlayingQuiz", {quizId: 1});//TODO : changer le quizId + navigation.navigate("PlayingQuizMultiMode", {runId: runId, socket: socket, roomId: roomId}); }; const onCancelPressed = () => { @@ -142,7 +181,7 @@ export default function Lobby({navigation, route}: Props) { } </View> </TemplateMenu> - <ModalInvitePlayer showModal={showModal} onClosePressed={() => setShowModal(false)}/> + <ModalInvitePlayer showModal={showModal} onClosePressed={() => setShowModal(false)} roomId={roomId} /> </View> ) } diff --git a/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiMode.tsx b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiMode.tsx index e2e6bfc..78c1760 100644 --- a/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiMode.tsx +++ b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiMode.tsx @@ -3,124 +3,82 @@ import {useQuizService} from "../../../../services/QuizService"; import React, {useEffect, useState} from "react"; import {QuizInformations} from "../../../../models/QuizInformations"; import {Question} from "../../../../models/Question"; -import {shuffleAnswers} from "../../../../helper/QuizHelper"; import TemplateDuo from "../../../../templates/TemplateDuo"; import HttpError from "../../../../services/HttpError"; import PlayingQuizMultiModeHeader from "./PlayingQuizMultiModeHeader"; import PlayingQuizMultiModeBody from "./PlayingQuizMultiModeBody"; +import {Socket} from "socket.io-client"; +import {QuestionResponse} from "../../../../models/Response/QuestionResponse"; +import {RunInformations} from "../../../../models/RunInformations"; type RoutePropsType = { - PlayingQuiz: { + PlayingQuizMultiMode: { runId: string; - quizId: string; + socket: Socket; + roomId: string; }; }; interface Props { - route: RouteProp<RoutePropsType, "PlayingQuiz">; + route: RouteProp<RoutePropsType, "PlayingQuizMultiMode">; navigation: NavigationProp<any>; } export default function PlayingQuizMultiMode({route, navigation}:Props) { - const {runId, quizId} = route.params; - const { getQuizInformations, getActualQuestionOfAQuiz } = useQuizService(); + const {runId, socket, roomId} = route.params; + const { getQuizInformations, getRunsInfo } = useQuizService(); const [quizInformations, setQuizInformations] = useState<QuizInformations | undefined>(undefined); - const [actualQuestion, setActualQuestion] = useState<Question | undefined>(undefined); + const [runInformations, setRunInformations] = useState<RunInformations>(); + const [actualQuestion, setActualQuestion] = useState<QuestionResponse | undefined>(undefined); const [score, setScore] = useState(0); - const fetchQuizInformations = async () => { - // const quizInformationsFetched = await getQuizInformations(quizId); - // if (HttpError.isHttpError(quizInformationsFetched)) { - // return; - // } - const quizInformationsFetched: QuizInformations = { - id: 1, - name: "Quiz de Test", - description: "Ceci est un quiz de test pour vérifier l'interface.", - isCommunity: true, - questionCount: 10, - categoryId: 2, - difficultyId: 3, - authorId: { - id: 42, - username: "TestAuthor", - email: "testauthor@example.com" - }, - createdAt: "2025-01-10T10:00:00Z", - updatedAt: "2025-01-11T12:00:00Z", - category: { - id: 2, - name: "Science" - }, - difficulty: { - id: 3, - name: "Moyenne" - } - }; + const initSocket = async () => { + socket.on("gameStarted", (data) => { + console.log("Game started:", data); + }); + + socket.on("newQuestion", (data) => { + const question: QuestionResponse = data as QuestionResponse + setActualQuestion(question); + // console.log(JSON.stringify(question, null, 2)); // Beautifie le JSON avec 2 espaces d'indentation + }); - setQuizInformations(quizInformationsFetched); } - const fetchActualQuestion = async () => { - const actualQuestionFetched = await getActualQuestionOfAQuiz(runId); - if (HttpError.isHttpError(actualQuestionFetched)) { + const fetchRuns = async () => { + if(runId === undefined) return; + const runFetched = await getRunsInfo(runId); + if (runFetched instanceof HttpError) { + console.log("Error fetching runs info", runFetched); return; } - // const actualQuestionFetched: Question = { - // id: 1, - // text: "Quelle image correspond à une étoile ?", - // type: "imae", - // categoryId: 2, - // quizId: "quiz123", - // order: 1, - // answers: [ - // { - // id: 1, - // text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - // isCorrect: true, - // questionId: 1 - // }, - // { - // id: 2, - // text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - // isCorrect: false, - // questionId: 1 - // }, - // { - // id: 3, - // text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - // isCorrect: false, - // questionId: 1 - // }, - // { - // id: 4, - // text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - // isCorrect: false, - // questionId: 1 - // } - // ], - // category: { - // id: 2, - // name: "Astronomie" - // } - // }; - const actualQuestionFetchedShuffle = shuffleAnswers(actualQuestionFetched); - setActualQuestion(actualQuestionFetchedShuffle); - } + const quizInformationsFetched = await getQuizInformations(runFetched.quizId); + if (quizInformationsFetched instanceof HttpError) { + console.log("Error fetching quiz info", quizInformationsFetched); + return; + } + setQuizInformations(quizInformationsFetched); + setRunInformations(runFetched); + }; useEffect(() => { - fetchQuizInformations(); - }, []); + console.log("PlayingQuizMultiMode starting"); + initSocket(); + fetchRuns(); + setTimeout(() => { + socket.emit("startGame") + },500); - useEffect(() => { - fetchActualQuestion(); - }, [quizId]); + setTimeout(() => { + socket.emit("nextQuestion"); + },2000); + }, []); return ( <TemplateDuo - childrenHeader={<PlayingQuizMultiModeHeader quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} score={score} navigation={navigation}></PlayingQuizMultiModeHeader>} - childrenBody={<PlayingQuizMultiModeBody quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} fetchActualQuestion={fetchActualQuestion} setScore={setScore} navigation={navigation}></PlayingQuizMultiModeBody>} + childrenHeader={<PlayingQuizMultiModeHeader quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} score={score} navigation={navigation}></PlayingQuizMultiModeHeader>} + childrenBody={<PlayingQuizMultiModeBody runId={runId} actualQuestion={actualQuestion} socket={socket} fetchActualQuestion={()=>{}} roomId={roomId} navigation={navigation}></PlayingQuizMultiModeBody>} /> ); } \ No newline at end of file diff --git a/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx index f6fc5f5..1a5d713 100644 --- a/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx +++ b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx @@ -1,7 +1,7 @@ import {QuizInformations} from "../../../../models/QuizInformations"; import {Question} from "../../../../models/Question"; import {NavigationProp} from "@react-navigation/native"; -import React, {useState} from "react"; +import React, {useEffect, useState} from "react"; import {useQuizService} from "../../../../services/QuizService"; import {Answer} from "../../../../models/Answer"; import HttpError from "../../../../services/HttpError"; @@ -9,15 +9,18 @@ import AnswerButton from "../../../../components/buttons/AnswerButton"; import {ActivityIndicator, View, Text, StyleSheet} from "react-native"; import BlueButton from "../../../../components/buttons/BlueButton"; import {ButtonsStyles} from "../../../../styles/ButtonsStyles"; +import {QuestionResponse} from "../../../../models/Response/QuestionResponse"; +import {RunInformations} from "../../../../models/RunInformations"; +import {Socket} from "socket.io-client"; +import {AnswerWsResponse} from "../../../../models/Response/AnswerWsResponse"; interface Props { - quizInformations?: QuizInformations; runId?: string; - actualQuestion: Question; + actualQuestion?: QuestionResponse; fetchActualQuestion: () => void; - score: number; - setScore: (score: number) => void; + socket: Socket; + roomId: string; navigation: NavigationProp<any>; } @@ -46,6 +49,9 @@ const getStyleOfAnswerButtonText = (answerId: number, selectedAnswerIndex: numbe } const getStyleOfAnswerButton = (answerId: number, selectedAnswerIndex: number | null, correctAnswerId: number | null, quizState: QuizState) => { + console.log("answerId :", answerId); + console.log("selectedAnswerIndex :", selectedAnswerIndex); + console.log("correctAnswerId :", correctAnswerId); if(quizState === QuizState.LOADING) { return styles.loadingAnswer; @@ -64,58 +70,61 @@ const getStyleOfAnswerButton = (answerId: number, selectedAnswerIndex: number | return styles.answerButton; } -export default function PlayingQuizMultiModeBody({ quizInformations, runId, actualQuestion, fetchActualQuestion, score, setScore, navigation }: Props) { +export default function PlayingQuizMultiModeBody({runId, actualQuestion, fetchActualQuestion, roomId, socket, navigation }: Props) { const [selectedAnswerId, setSelectedAnswerId] = useState<number | null>(null); const [quizState, setQuizState] = useState(QuizState.ANSWERING); const [correctAnswerId, setCorrectAnswerId] = useState<number | null>(null); const [isLastQuestion, setIsLastQuestion] = useState(false); + const [quizInformations, setQuizInformations] = useState<QuizInformations>(); + const [runInformations, setRunInformations] = useState<RunInformations>(); - const {answerQuestion} = useQuizService(); + const {answerQuestion, getRunsInfo, getQuizInformations} = useQuizService(); const getCorrectAnswer = (answers: Answer[]): Answer => { + console.log("starting getCorrectAnswer"); const correctAnswer = answers.find((answer) => answer.isCorrect); - if (!correctAnswer) throw new Error("No correct answer found"); + if (!correctAnswer) return answers[0]; return correctAnswer; }; - const onValidation = async () => { - if(selectedAnswerId === null) return; - setQuizState(QuizState.LOADING); - const answerId = actualQuestion.answers.find((answer) => answer.id === selectedAnswerId)?.id; - if(!answerId || !runId) return; - const answereFetched = await answerQuestion(runId, actualQuestion.id, answerId); - if (HttpError.isHttpError(answereFetched)) { - return; - } - setScore(answereFetched.score); - const correctAnswerIdFetched = getCorrectAnswer(answereFetched.answers.answers).id; - setCorrectAnswerId(correctAnswerIdFetched); - setQuizState(QuizState.SHOWING_RESULTS); - - if (quizInformations === undefined) return; - // Check si c'est la dernière question - if(answereFetched.answers.order === quizInformations.questionCount - 1) { - setIsLastQuestion(true); - return; - } + const initSocket = async () => { + console.log("init socket in body"); + socket.on("answersRevealed", (data: AnswerWsResponse) => { + const correctAnswerIdFetched = getCorrectAnswer(data.answers).id; + console.log("correctAnswerIdFetched", correctAnswerIdFetched); + setCorrectAnswerId(correctAnswerIdFetched); + setQuizState(QuizState.SHOWING_RESULTS); + }); + // socket.on("score", (data) => { + // console.log("Score:", data); + // }); + } + + useEffect(() => { + initSocket(); + }, []); + + + const onValidation = async () => { + + if(selectedAnswerId === null || actualQuestion === undefined) return; + setQuizState(QuizState.LOADING); + socket.emit("submitAnswer", { + roomId, + runId, + questionId: actualQuestion.question.id, + answerId: selectedAnswerId, + }); + + setTimeout(() => { + socket.emit("revealAnswer", {runId: runId}); + },500); }; const onContinue = () => { - if(isLastQuestion) { - navigation.reset({ - index: 0, - routes: [ - { - name: "EndQuiz", - params: {quizInformations: quizInformations, score: score}, - }, - ] - }); - return; - } - fetchActualQuestion(); + socket.emit("nextQuestion"); setQuizState(QuizState.ANSWERING); - setSelectedAnswerId(null); setCorrectAnswerId(null); + setSelectedAnswerId(null); } const onAnsweredButtonClicked = (answerId: number) => { @@ -129,7 +138,7 @@ export default function PlayingQuizMultiModeBody({ quizInformations, runId, actu <View style={styles.buttonContainer}> {actualQuestion ? ( <> - {actualQuestion.answers.map((answer, index) => ( + {actualQuestion.question.answers.map((answer, index) => ( <AnswerButton key={index} text={answer.text} diff --git a/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeHeader.tsx b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeHeader.tsx index c5da7d2..427c8cd 100644 --- a/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeHeader.tsx +++ b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeHeader.tsx @@ -7,11 +7,12 @@ import {View, Text, StyleSheet} from "react-native"; import GoHomeButton from "../../../../components/PlayingQuiz/GoHomeButton"; import {TextsStyles} from "../../../../styles/TextsStyles"; import ConfirmModal from "../../../../components/PlayingQuiz/ComfirmModal"; +import {QuestionResponse} from "../../../../models/Response/QuestionResponse"; interface Props { quizInformations?: QuizInformations; runId?: string; - actualQuestion: Question; + actualQuestion?: QuestionResponse; score: number; navigation: NavigationProp<any> } @@ -20,6 +21,7 @@ export default function PlayingQuizMultiModeHeader({quizInformations, runId, act const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); const {t} = useTranslation(); + const handleConfirmModalConfirm = () => { navigation.reset({ index: 0, @@ -40,7 +42,7 @@ export default function PlayingQuizMultiModeHeader({quizInformations, runId, act <View style={styles.questionAndScoreContainer}> <View style={styles.InformationsContainer}> <Text style={TextsStyles.titleText}>{t("app.screens.question.question")}</Text> - <Text style={TextsStyles.titleText}>{actualQuestion ? actualQuestion.order + 1 :"?"}/{quizInformations ? quizInformations.questionCount : "?"}</Text> + <Text style={TextsStyles.titleText}>{actualQuestion ? actualQuestion.question.order + 1 :"?"}/{quizInformations ? quizInformations.questionCount : "?"}</Text> </View> <View style={styles.InformationsContainer}> <Text style={TextsStyles.titleText}>{t("app.screens.question.score")}</Text> @@ -48,7 +50,7 @@ export default function PlayingQuizMultiModeHeader({quizInformations, runId, act </View> </View> <View style={styles.QuizQuestionContainer}> - <Text style={TextsStyles.subtitleText}>{actualQuestion ? actualQuestion.text : "Loading..."}</Text> + <Text style={TextsStyles.subtitleText}>{actualQuestion ? actualQuestion.question.text : "Loading..."}</Text> </View> <ConfirmModal visible={isConfirmModalVisible} onClose={()=>setIsConfirmModalVisible(false)} onConfirm={handleConfirmModalConfirm}/> diff --git a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx index 8f45d5c..14b3875 100644 --- a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx @@ -29,7 +29,7 @@ export default function MultiInformationsOfQuiz({ navigation, route }: Props) { const onPlayPressed = async (nbPlayer: number) => { setShowModal(false); - navigation.navigate("Lobby", {quiz: quiz, nbPlayer: nbPlayer}); + navigation.navigate("Lobby", {quiz: quiz, nbPlayer: nbPlayer, isHost: true}); }; return ( diff --git a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx index c31decb..f8f5d1a 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx @@ -61,8 +61,9 @@ export default function OnlineCreateLobby({navigation}: Props) { } navigation.navigate("Lobby", { - runId: quizGenerated.id, + quizIdOrRoomId: quizGenerated.quizId, nbPlayers: parseInt(nbPlayers), + isHost: true, }); }; diff --git a/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx b/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx index d298d8c..03aba42 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx @@ -14,38 +14,28 @@ interface Props { } export default function OnlinePlayQuiz({navigation}: Props) { const [codeQuiz, setCodeQuiz] = React.useState(""); - const [quiz, setQuiz] = React.useState<Quiz>(); const [error, setError] = React.useState<string>(""); + const {getRunsInfo} = useQuizService(); - const { remainingQuiz } = useQuizService(); - - useEffect(() => { - fetchQuiz(); - }, [codeQuiz]); - - const fetchQuiz = async () => { - if(codeQuiz === "") - { - return; + const checkRoomExist = async () : Promise<boolean> => { + const runFetched = await getRunsInfo(codeQuiz); + if(runFetched instanceof HttpError) { + return false; } - const quizFetched = await remainingQuiz(codeQuiz); - if (quizFetched instanceof HttpError) { - setError("The quiz does not exist"); + return true; + } + + const joinLobby = async () => { + const response = await checkRoomExist() + if(!response) { + setError("The room does not exist"); return; } setError(""); - setQuiz(quizFetched); - }; - - const handlePlayButtonPress = () => { - navigation.reset({ - index: 0, - routes: [ - { - name: "PlayingQuiz", - params: {quizRecovered: quiz}, - }, - ] + navigation.navigate("Lobby", { + quizIdOrRoomId: codeQuiz, + nbPlayers: 10, + isHost: false, }); }; @@ -54,11 +44,10 @@ export default function OnlinePlayQuiz({navigation}: Props) { <View style={styles.contentContainer}> <View style={styles.fieldContainer}> {error ? <Text style={styles.errorText}>{error}</Text> : null} - <InputPlayQuiz placeholder={"Enter quiz ID"} setText={setCodeQuiz} noTitle={true} /> - <AboutAQuiz quiz={quiz} /> + <InputPlayQuiz placeholder={"Enter room ID"} setText={setCodeQuiz} noTitle={true} /> </View> <View style={styles.buttonPlayContainer}> - <BlueButton onPress={handlePlayButtonPress} text={"JOIN"} isDisabled={codeQuiz==="" || error!==""}/> + <BlueButton onPress={joinLobby} text={"JOIN"} isDisabled={codeQuiz==="" || error!==""}/> </View> </View> </View> diff --git a/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx b/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx index bf33162..cc45226 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineSelectMode.tsx @@ -18,7 +18,7 @@ export default function OnlineSelectMode({ mode, setMode }: Props) { <View style={styles.buttonContainer}> <TouchableOpacity onPress={() => setMode("join")}> - <Text style={[styles.text, mode === "join" && styles.activeText]}>JOIN A QUIZ</Text> + <Text style={[styles.text, mode === "join" && styles.activeText]}>JOIN A ROOM</Text> </TouchableOpacity> <View style={[styles.bar, mode === "join" ? styles.activeBar : styles.inactiveBar]} /> </View> diff --git a/screens/PlayingQuiz/PlayingQuizHeader.tsx b/screens/PlayingQuiz/PlayingQuizHeader.tsx index ffba640..c03c6d0 100644 --- a/screens/PlayingQuiz/PlayingQuizHeader.tsx +++ b/screens/PlayingQuiz/PlayingQuizHeader.tsx @@ -13,7 +13,7 @@ import {Question} from "../../models/Question"; interface Props { quizInformations?: QuizInformations; runId?: string; - actualQuestion: Question; + actualQuestion?: Question; score: number; navigation: NavigationProp<any> } -- GitLab From 046b6d4dc24d63a3a5be18c4e30f05d2d2938232 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Mon, 13 Jan 2025 21:14:21 +0100 Subject: [PATCH 261/283] fix: stable version of mobile --- .../PlayingQuiz/EndQuiz/EndQuizListPlayer.tsx | 6 +-- components/PlayingQuiz/EndQuiz/UserScore.tsx | 4 +- components/lists/QuizList.tsx | 2 +- models/Quiz.ts | 5 ++- screens/Home/Home.tsx | 21 ---------- .../EndQuizMulti/EndQuizMultiChild.tsx | 40 +++---------------- services/QuizService.ts | 6 +-- 7 files changed, 19 insertions(+), 65 deletions(-) diff --git a/components/PlayingQuiz/EndQuiz/EndQuizListPlayer.tsx b/components/PlayingQuiz/EndQuiz/EndQuizListPlayer.tsx index a2378cb..819fabe 100644 --- a/components/PlayingQuiz/EndQuiz/EndQuizListPlayer.tsx +++ b/components/PlayingQuiz/EndQuiz/EndQuizListPlayer.tsx @@ -1,14 +1,14 @@ import React from "react"; import { FlatList, View, Text, StyleSheet, Image } from "react-native"; -import UserModel from "../../../models/UserModel"; +import {User} from "../../../models/User"; interface Props { - users: UserModel[]; + users: User[]; maxScore: number; } export default function EndQuizListPlayer({ users, maxScore }: Props) { - const renderUser = ({ item, index }: { item: UserModel; index: number }) => ( + const renderUser = ({ item, index }: { item: User; index: number }) => ( <View style={[styles.userContainer, index === 0 && styles.firstPlace]}> <Image source={require("../../../assets/ProfilBaseImage.png")} // Replace with actual profile picture if available diff --git a/components/PlayingQuiz/EndQuiz/UserScore.tsx b/components/PlayingQuiz/EndQuiz/UserScore.tsx index d57489c..6583622 100644 --- a/components/PlayingQuiz/EndQuiz/UserScore.tsx +++ b/components/PlayingQuiz/EndQuiz/UserScore.tsx @@ -1,8 +1,8 @@ -import UserModel from "../../../models/UserModel"; import { View, Text, StyleSheet, Image } from "react-native"; +import {User} from "../../../models/User"; interface Props { - user: UserModel; + user: User; score: number; maxScore: number; isFirst: boolean; diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index 6cb9f80..535915f 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -61,7 +61,7 @@ export default function QuizList({ quizList, isLoadingData, nextPage, onQuizPres <FlatList data={quizList} renderItem={renderQuizItem} - keyExtractor={(item) => item.id} + keyExtractor={(item,index) => item.id+ ""+ index} showsVerticalScrollIndicator={true} contentContainerStyle={styles.listContent} onEndReached={() => { diff --git a/models/Quiz.ts b/models/Quiz.ts index 1acc2ce..c717243 100644 --- a/models/Quiz.ts +++ b/models/Quiz.ts @@ -1,5 +1,8 @@ import {Question} from "./Question"; - +export interface QuizCommunityResponse { + quizzes: Quiz[]; + total: number; +} export interface Difficulty { id: number; name: string; diff --git a/screens/Home/Home.tsx b/screens/Home/Home.tsx index 1369070..4fb5203 100644 --- a/screens/Home/Home.tsx +++ b/screens/Home/Home.tsx @@ -10,28 +10,7 @@ interface Props { } export default function Home({navigation}: Props) { - const debugQuiz: Quiz = { - id: "debug-quiz-001", - name: "Debug Quiz", - description: "This is a basic quiz for debugging purposes.", - score: 0, - questionIndex: 0, - questionCount: 0, - categoryId: 1, - difficultyId: 1, - authorId: 1, - category: { - id: 1, - name: "General Knowledge", - }, - difficulty: { - id: 1, - name: "Easy", - }, - questions: [], // Tableau de questions vide - }; - navigation.navigate("EndQuizMulti", {quiz: debugQuiz}); return ( <View style={styles.containerGlobal}> <TemplateMenu navigation={navigation} headerNavigation={false} buttonGoBack={false}> diff --git a/screens/PlayingQuiz/EndQuizMulti/EndQuizMultiChild.tsx b/screens/PlayingQuiz/EndQuizMulti/EndQuizMultiChild.tsx index 3ba9918..b53eb9f 100644 --- a/screens/PlayingQuiz/EndQuizMulti/EndQuizMultiChild.tsx +++ b/screens/PlayingQuiz/EndQuizMulti/EndQuizMultiChild.tsx @@ -5,7 +5,6 @@ import {Quiz} from "../../../models/Quiz"; import {useQuizService} from "../../../services/QuizService"; import HttpError from "../../../services/HttpError"; import UserScore from "../../../components/PlayingQuiz/EndQuiz/UserScore"; -import UserModel from "../../../models/UserModel"; import EndQuizListPlayer from "../../../components/PlayingQuiz/EndQuiz/EndQuizListPlayer"; @@ -17,38 +16,11 @@ interface Props { export default function EndQuizMultiChild({ navigation, quiz }: Props) { - const {restartQuiz, remainingQuiz} = useQuizService() - - const handleRestartPress = async () => { - const isRestarted = await restartQuiz(quiz.id); - if(HttpError.isHttpError(isRestarted)){ - console.log(isRestarted.message); - return - } - if(!isRestarted) { - return; - } - const quizRestarted = await remainingQuiz(quiz.id); - if(HttpError.isHttpError(quizRestarted)){ - console.log(quizRestarted.message); - return - } - - navigation.reset({ - index: 0, - routes: [ - { - name: "PlayingQuiz", - params: {quizRecovered: quizRestarted}, - }, - ] - }); - }; + const userTest = {id: 1, email: "user1@email.com", username: "user1", stats: undefined}; const handleBackToMenu = () => { navigation.navigate('TabNavigator'); }; - const user = new UserModel(10,"Thomassss", "Thomas"); return ( <View style={styles.container}> @@ -61,13 +33,13 @@ export default function EndQuizMultiChild({ navigation, quiz }: Props) { style={styles.stars} /> <View style={styles.usersContainer}> - <UserScore user={user} score={10} maxScore={quiz.questionCount} isFirst={false}/> - <UserScore user={user} score={10} maxScore={quiz.questionCount} isFirst={true}/> - <UserScore user={user} score={10} maxScore={quiz.questionCount} isFirst={false}/> + <UserScore user={userTest} score={10} maxScore={quiz.questionCount} isFirst={false}/> + <UserScore user={userTest} score={10} maxScore={quiz.questionCount} isFirst={true}/> + <UserScore user={userTest} score={10} maxScore={quiz.questionCount} isFirst={false}/> </View> - <EndQuizListPlayer users={[new UserModel(1, "user1", "user1@email.com"), new UserModel(2, "user2", "user2@email.com")]} maxScore={quiz.questionCount}/> + <EndQuizListPlayer users={[userTest, userTest]} maxScore={quiz.questionCount}/> - <DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton> + {/*<DefaultButton text="RESTART" handleButtonPressed={handleRestartPress} buttonStyle={styles.whiteButton} buttonText={styles.whiteButtonText}></DefaultButton>*/} <DefaultButton text="BACK TO MENU" handleButtonPressed={handleBackToMenu} buttonStyle={styles.blueButton} buttonText={styles.blueButtonText}></DefaultButton> </View> </View> diff --git a/services/QuizService.ts b/services/QuizService.ts index 19cb0e9..8dbbc26 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -1,5 +1,5 @@ import HttpError from "./HttpError"; -import {Quiz} from "../models/Quiz"; +import {Quiz, QuizCommunityResponse} from "../models/Quiz"; import axios from "axios"; import {Answer} from "../models/Answer"; import {Question} from "../models/Question"; @@ -214,11 +214,11 @@ export const useQuizService = () => { const getCommunityQuiz = async (skip: number, take: number) => { try { - const response = await axios.get<Quiz[]>(`${url}/quiz/paginated?skip=${skip}&take=${take}`, { + const response = await axios.get<QuizCommunityResponse>(`${url}/quiz/paginated?skip=${skip}&take=${take}`, { withCredentials: true, // Inclut les cookies pour une session authentifiée }); - return response.data; + return response.data.quizzes; } catch (error: any) { if (error.response) { -- GitLab From dd179b6a4505ca42dd7a25a1bdf5d502f9b97101 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Mon, 13 Jan 2025 22:26:54 +0100 Subject: [PATCH 262/283] refactor: show good values --- components/lists/QuizList.tsx | 7 +++++-- models/Quiz.ts | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index 535915f..dfad0f6 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -41,11 +41,14 @@ interface Props { onQuizPressed:(quiz: Quiz) => void; } export default function QuizList({ quizList, isLoadingData, nextPage, onQuizPressed }: Props) { + if (quizList) { + console.log(quizList[0]); + } const renderQuizItem = ({ item }: { item: Quiz }) => ( <TouchableOpacity style={styles.quizItem} onPress={() => onQuizPressed(item)}> - <Text style={styles.quizTitle}>{item.id}</Text> + <Text style={styles.quizTitle}>{item.name}</Text> <Text style={styles.quizDetails}> - {item.questionCount} QUESTIONS | {item.category ? item.category.name.toUpperCase() : ""} + {item.questionCount} QUESTIONS | {item.difficulty.name} | {item.author.username} </Text> </TouchableOpacity> ); diff --git a/models/Quiz.ts b/models/Quiz.ts index c717243..83a6284 100644 --- a/models/Quiz.ts +++ b/models/Quiz.ts @@ -13,6 +13,12 @@ export interface Category { name: string; } +interface Author { + id: number; + username: string; +} + + export interface Quiz { id: string; name: string; @@ -22,6 +28,7 @@ export interface Quiz { questionCount: number; categoryId: number; difficultyId: number; + author: Author, authorId: number, category?: Category, difficulty: Difficulty, -- GitLab From e95d7ef9128d18859b08713f5da7713409d2fc63 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 14 Jan 2025 10:57:56 +0100 Subject: [PATCH 263/283] refactor: login and signup --- models/User.ts | 6 ++ screens/Multiplayer/MultiplayerChild.tsx | 62 ++++++++--- screens/Profil/Connection/Connection.tsx | 72 +++++++++++++ .../ConnectionLogin.tsx} | 6 +- .../ConnectionSignup.tsx} | 12 ++- screens/Profil/Modals/ProfilModal.tsx | 100 ------------------ screens/Profil/ProfilChild.tsx | 54 +++++----- screens/Profil/ProfilHeaderAccount.tsx | 51 --------- 8 files changed, 163 insertions(+), 200 deletions(-) create mode 100644 screens/Profil/Connection/Connection.tsx rename screens/Profil/{Modals/ProfilModalLogin.tsx => Connection/ConnectionLogin.tsx} (92%) rename screens/Profil/{Modals/ProfilModalSignup.tsx => Connection/ConnectionSignup.tsx} (93%) delete mode 100644 screens/Profil/Modals/ProfilModal.tsx delete mode 100644 screens/Profil/ProfilHeaderAccount.tsx diff --git a/models/User.ts b/models/User.ts index e572420..506d8f0 100644 --- a/models/User.ts +++ b/models/User.ts @@ -9,4 +9,10 @@ export interface User { email: string; username: string; stats?: Stats; +} + +export enum UserConnectionState { + LOADING = "Loading", + NOT_CONNECTED = "NotConnected", + CONNECTED = "Connected", } \ No newline at end of file diff --git a/screens/Multiplayer/MultiplayerChild.tsx b/screens/Multiplayer/MultiplayerChild.tsx index be9843a..f7eee12 100644 --- a/screens/Multiplayer/MultiplayerChild.tsx +++ b/screens/Multiplayer/MultiplayerChild.tsx @@ -1,18 +1,20 @@ -import { View, StyleSheet, Image, Modal, TextInput } from "react-native"; -import DefaultButton from "../../components/DefaultButton"; +import {Image, StyleSheet, Text, View} from "react-native"; import {useTranslation} from "react-i18next"; -import { useState } from "react"; -import { TextsStyles } from "../../styles/TextsStyles"; +import React, {useEffect, useState} from "react"; import ModalJoinQuiz from "../../components/ModalJoinQuiz"; import MenuButton from "../../components/buttons/MenuButton"; import {NavigationProp} from "@react-navigation/native"; -import {useQuizService} from "../../services/QuizService"; import HttpError from "../../services/HttpError"; +import {useUserService} from "../../services/UserService"; +import {UserConnectionState} from "../../models/User"; +import LoadingIndicator from "../../components/loading/LoadingIndicator"; +import Connection from "../Profil/Connection/Connection"; interface Props { navigation: NavigationProp<any>; } + /** * HomeChild : Enfant de l'écran Home, il contient le menu principal * @param navigation - navigation @@ -20,6 +22,22 @@ interface Props { export default function MultiplayerChild({navigation}: Props) { const [modalVisible, setModalVisible] = useState(false); const {t} = useTranslation(); + const [userConnectionState, setUserConnectionState] = useState(UserConnectionState.LOADING); + const { getInformationsUser, logoutUser } = useUserService(); + + const getUser = async () => { + setUserConnectionState(UserConnectionState.LOADING); + const user = await getInformationsUser(); + if(user instanceof HttpError){ + setUserConnectionState(UserConnectionState.NOT_CONNECTED); + return; + } + setUserConnectionState(UserConnectionState.CONNECTED); + } + + useEffect(() => { + getUser(); + }, []); const handleButtonCommunityPressed = async () => { navigation.navigate("MultiplayerCommunity"); @@ -35,15 +53,24 @@ export default function MultiplayerChild({navigation}: Props) { return ( <View style={styles.containerGlobal}> - <View style={styles.imageContainer}> - <Image source={require('../../assets/TitleApp.png')} style={styles.image} /> - </View> - <View style={styles.buttonContainer}> - <MenuButton text={"COMMUNITY"} handleButtonPressed={handleButtonCommunityPressed}/> - <MenuButton text={"PLAY AN ONLINE QUIZ"} handleButtonPressed={handleOnlineQuizPressed}/> - <MenuButton text={"ONGOING QUIZZES"} handleButtonPressed={handleButtonOngoingQuizzesPressed}/> - </View> - <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> + {userConnectionState === UserConnectionState.LOADING && ( + <LoadingIndicator/> + )} + {userConnectionState === UserConnectionState.CONNECTED && ( + <> + <View style={styles.imageContainer}> + <Image source={require('../../assets/TitleApp.png')} style={styles.image} /> + </View> + <View style={styles.buttonContainer}> + <MenuButton text={"COMMUNITY"} handleButtonPressed={handleButtonCommunityPressed}/> + <MenuButton text={"PLAY AN ONLINE QUIZ"} handleButtonPressed={handleOnlineQuizPressed}/> + <MenuButton text={"ONGOING QUIZZES"} handleButtonPressed={handleButtonOngoingQuizzesPressed}/> + </View> + </> + )} + {userConnectionState === UserConnectionState.NOT_CONNECTED && ( + <Connection title={"Multiplayer"} navigation={navigation}/> + )} </View> ); } @@ -58,6 +85,13 @@ const styles = StyleSheet.create({ }, image: { }, + title: { + fontSize: 40, + fontWeight: "bold", + color: "#00b3f4", + marginBottom: 10, + alignSelf: "center", // Centre uniquement le titre + }, button: { borderRadius: 40, height: '35%', diff --git a/screens/Profil/Connection/Connection.tsx b/screens/Profil/Connection/Connection.tsx new file mode 100644 index 0000000..34e5023 --- /dev/null +++ b/screens/Profil/Connection/Connection.tsx @@ -0,0 +1,72 @@ +import {Keyboard, StyleSheet, Text, TouchableOpacity, TouchableWithoutFeedback, View} from "react-native"; +import ProfilSelectMode from "../ProfilSelectMode"; +import React, {useState} from "react"; +import {ProfilSelectModeType} from "../../../models/SelectModeType"; +import {NavigationProp} from "@react-navigation/native"; +import ConnectionLogin from "./ConnectionLogin"; +import ConnectionSignup from "./ConnectionSignup"; + +interface Props { + title: string; + navigation: NavigationProp<any>; +} + +export default function Connection({title, navigation}: Props) { + const [mode, setMode] = useState<ProfilSelectModeType>("login"); + + return ( + <TouchableWithoutFeedback onPress={Keyboard.dismiss}> + <View style={styles.container}> + <Text style={styles.title}>{title}</Text> + <View style={styles.containerModal}> + <ProfilSelectMode mode={mode} setMode={setMode} /> + {mode === "login" ? ( + <ConnectionLogin navigation={navigation} /> + ) : ( + <ConnectionSignup navigation={navigation} /> + )} + </View> + </View> + </TouchableWithoutFeedback> + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + height: "100%", + width: "100%", + }, + title: { + fontSize: 40, + fontWeight: "bold", + color: "#00b3f4", + marginBottom: 10, + alignSelf: "center", // Centre uniquement le titre + }, + backgroundShadow: { + height: "100%", + width: "100%", + backgroundColor: "#000000", + opacity: 0.55, + position: "absolute", + }, + containerModal: { + height: 600, + width: "90%", + backgroundColor: "#D9D9D9", + borderRadius: 40, + padding: '7%', + }, + closeButton: { + position: "absolute", + top: '1%', + right: '1%', + zIndex: 1, + backgroundColor: "white", + borderRadius: 10, + margin: '5%', + }, +}); \ No newline at end of file diff --git a/screens/Profil/Modals/ProfilModalLogin.tsx b/screens/Profil/Connection/ConnectionLogin.tsx similarity index 92% rename from screens/Profil/Modals/ProfilModalLogin.tsx rename to screens/Profil/Connection/ConnectionLogin.tsx index 41c3553..0dcd824 100644 --- a/screens/Profil/Modals/ProfilModalLogin.tsx +++ b/screens/Profil/Connection/ConnectionLogin.tsx @@ -8,9 +8,8 @@ import BlueButton from "../../../components/buttons/BlueButton"; interface Props { navigation: NavigationProp<any>; - closeModal: () => void; } -export default function ProfilModalLogin({navigation, closeModal}: Props) { +export default function ConnectionLogin({navigation}: Props) { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState<string | null>(null); @@ -27,7 +26,6 @@ export default function ProfilModalLogin({navigation, closeModal}: Props) { return; } setError(null); - closeModal(); navigation.reset({ routes: [{name: "TabNavigator"}], }); @@ -54,6 +52,7 @@ const styles = StyleSheet.create({ justifyContent: 'flex-start', rowGap: '7%', marginTop: "3%", + marginBottom: "auto", // Espacement entre le texte d'erreur et les inputs }, container:{ height: 475, @@ -80,7 +79,6 @@ const styles = StyleSheet.create({ errorText: { color: 'red', fontSize: 16, - marginBottom: 10, // Espacement entre le texte d'erreur et les inputs textAlign: 'center', // Centrer le message d'erreur }, }); diff --git a/screens/Profil/Modals/ProfilModalSignup.tsx b/screens/Profil/Connection/ConnectionSignup.tsx similarity index 93% rename from screens/Profil/Modals/ProfilModalSignup.tsx rename to screens/Profil/Connection/ConnectionSignup.tsx index 124a42e..542012f 100644 --- a/screens/Profil/Modals/ProfilModalSignup.tsx +++ b/screens/Profil/Connection/ConnectionSignup.tsx @@ -9,9 +9,8 @@ import BlueButton from "../../../components/buttons/BlueButton"; interface Props { navigation: NavigationProp<any>; - closeModal: () => void; } -export default function ProfilModalSignup({navigation, closeModal}: Props) { +export default function ConnectionSignup({navigation}: Props) { const [email, setEmail] = useState(""); const [username, setUsername] = useState(""); @@ -25,6 +24,10 @@ export default function ProfilModalSignup({navigation, closeModal}: Props) { setError("Please fill in all fields"); return; } + if(password.length < 8){ + setError("Password must be at least 8 characters long"); + return; + } if(password !== repeatPassword){ setError("Passwords do not match"); return; @@ -35,7 +38,6 @@ export default function ProfilModalSignup({navigation, closeModal}: Props) { return; } setError(null); - closeModal(); navigation.reset({ routes: [{name: "TabNavigator"}], }); @@ -62,13 +64,13 @@ const styles = StyleSheet.create({ display: 'flex', flexDirection: 'column', justifyContent: 'flex-start', - rowGap: '7%', + rowGap: '5%', marginTop: "3%", }, container:{ height: 475, paddingTop: '5%', - isplay: 'flex', + display: 'flex', justifyContent: 'space-between' }, textInput: { diff --git a/screens/Profil/Modals/ProfilModal.tsx b/screens/Profil/Modals/ProfilModal.tsx deleted file mode 100644 index c24d3fc..0000000 --- a/screens/Profil/Modals/ProfilModal.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from "react"; -import { - Modal, - View, - StyleSheet, - TextInput, - TouchableOpacity, - Keyboard, - TouchableWithoutFeedback, -} from "react-native"; -import ProfilSelectMode from "../ProfilSelectMode"; -import { ProfilSelectModeType } from "../../../models/SelectModeType"; -import ProfilModalLogin from "./ProfilModalLogin"; -import ProfilModalSignup from "./ProfilModalSignup"; -import Icon from "react-native-vector-icons/MaterialIcons"; -import {NavigationProp} from "@react-navigation/native"; - -interface Props { - modalVisible: boolean; - setModalVisible: (visible: boolean) => void; - mode: ProfilSelectModeType; - setMode: (mode: ProfilSelectModeType) => void; - navigation: NavigationProp<any>; -} - -export default function ProfilModal({ - modalVisible, - setModalVisible, - mode, - setMode, - navigation - }: Props) { - const closeModal = () => { - setModalVisible(false); - } - - return ( - <Modal - animationType="slide" - transparent={true} - visible={modalVisible} - onRequestClose={() => { - setModalVisible(!modalVisible); - }} - > - <TouchableWithoutFeedback onPress={Keyboard.dismiss}> - <View style={styles.container}> - <TouchableOpacity - style={styles.closeButton} - onPress={() => setModalVisible(false)} // Ferme la modal - > - <Icon name="close" size={50} color="#000" /> - </TouchableOpacity> - <View style={styles.backgroundShadow} /> - <View style={styles.containerModal}> - <ProfilSelectMode mode={mode} setMode={setMode} /> - {mode === "login" ? ( - <ProfilModalLogin navigation={navigation} closeModal={closeModal}/> - ) : ( - <ProfilModalSignup navigation={navigation} closeModal={closeModal}/> - )} - </View> - </View> - </TouchableWithoutFeedback> - </Modal> - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: "center", - alignItems: "center", - height: "100%", - width: "100%", - }, - backgroundShadow: { - height: "100%", - width: "100%", - backgroundColor: "#000000", - opacity: 0.55, - position: "absolute", - }, - containerModal: { - height: 600, - width: "90%", - backgroundColor: "#D9D9D9", - borderRadius: 40, - padding: '7%', - }, - closeButton: { - position: "absolute", - top: '1%', - right: '1%', - zIndex: 1, - backgroundColor: "white", - borderRadius: 10, - margin: '5%', - }, -}); diff --git a/screens/Profil/ProfilChild.tsx b/screens/Profil/ProfilChild.tsx index dd89a53..bb601bf 100644 --- a/screens/Profil/ProfilChild.tsx +++ b/screens/Profil/ProfilChild.tsx @@ -1,24 +1,20 @@ import { NavigationProp } from "@react-navigation/native"; import { View, StyleSheet, Image, Text, TouchableOpacity } from "react-native"; -import ProfilModal from "./Modals/ProfilModal"; import React, { useEffect, useState } from "react"; -import ProfilHeaderAccount from "./ProfilHeaderAccount"; import { ProfilSelectModeType } from "../../models/SelectModeType"; import { useUserService } from "../../services/UserService"; import HttpError from "../../services/HttpError"; import Icon from "react-native-vector-icons/MaterialIcons"; -import {User} from "../../models/User"; +import {User, UserConnectionState} from "../../models/User"; import StatsOfUsers from "../../components/Profil/StatsOfUsers"; +import LoadingIndicator from "../../components/loading/LoadingIndicator"; +import Connection from "./Connection/Connection"; interface Props { navigation: NavigationProp<any>; } -enum UserConnectionState { - LOADING = "Loading", - NOT_CONNECTED = "NotConnected", - CONNECTED = "Connected", -} + export default function ProfilChild({ navigation }: Props) { const [isLogged, setIsLogged] = useState(false); @@ -28,6 +24,7 @@ export default function ProfilChild({ navigation }: Props) { const [userConnectionState, setUserConnectionState] = useState(UserConnectionState.LOADING); const { getInformationsUser, logoutUser } = useUserService(); + const getUser = async () => { setUserConnectionState(UserConnectionState.LOADING); const user = await getInformationsUser(); @@ -42,7 +39,6 @@ export default function ProfilChild({ navigation }: Props) { useEffect(() => { getUser(); - }, []); // Pour s'adapter quand l'état de connexion change @@ -55,33 +51,32 @@ export default function ProfilChild({ navigation }: Props) { if (!(result instanceof HttpError)) { setUser(undefined); // Réinitialise l'utilisateur après la déconnexion } + navigation.reset({ + routes: [{name: "TabNavigator"}], + }); }; return ( <View style={styles.containerGlobal}> + {userConnectionState === UserConnectionState.LOADING && ( + <LoadingIndicator/> + )} + {userConnectionState === UserConnectionState.CONNECTED && ( + <> <View style={styles.containerPlayerInfos}> <Image source={require('../../assets/TitleApp.png')} style={styles.titleImage} /> <Image source={require('../../assets/ProfilBaseImage.png')} style={styles.profilImage} /> <Text style={styles.pseudoText}>{user ? user.username : "?"}</Text> </View> - - {!user && ( - <ProfilHeaderAccount setModalVisible={setModalVisible} setMode={setMode} /> - )} - <ProfilModal - modalVisible={modalVisible} - setModalVisible={setModalVisible} - mode={mode} - setMode={setMode} - navigation={navigation} - /> <StatsOfUsers user={user}/> - - {user && ( - <TouchableOpacity style={styles.logoutButton} onPress={handleLogout}> - <Icon name="logout" size={40} color="#FF0000" /> - </TouchableOpacity> - )} + <TouchableOpacity style={styles.logoutButton} onPress={handleLogout}> + <Icon name="logout" size={40} color="#FF0000" /> + </TouchableOpacity> + </> + )} + {userConnectionState === UserConnectionState.NOT_CONNECTED && ( + <Connection title={"Profil"} navigation={navigation} /> + )} </View> ); } @@ -99,6 +94,13 @@ const styles = StyleSheet.create({ display: "flex", alignItems: "center", }, + title: { + fontSize: 40, + fontWeight: "bold", + color: "#00b3f4", + marginBottom: 10, + alignSelf: "center", // Centre uniquement le titre + }, titleImage: { width: "45%", resizeMode: "contain", diff --git a/screens/Profil/ProfilHeaderAccount.tsx b/screens/Profil/ProfilHeaderAccount.tsx deleted file mode 100644 index 6afcc5d..0000000 --- a/screens/Profil/ProfilHeaderAccount.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { View, Text, TouchableOpacity, StyleSheet } from "react-native"; -import { ProfilSelectModeType } from "../../models/SelectModeType"; - -interface Props { - setModalVisible: (visible:boolean) => void; - setMode: (mode: ProfilSelectModeType) => void; -} - -export default function ProfilHeaderAccount({ setModalVisible, setMode }: Props) { - const handleAccountPress = (mode:ProfilSelectModeType) => { - setMode(mode); - setModalVisible(true); - }; - - return ( - <View style={styles.containerHeader}> - <TouchableOpacity onPress={() => handleAccountPress('login')}> - <Text style={styles.buttonHeader}>LOGIN</Text> - </TouchableOpacity> - <Text style={styles.textHeader}>{' OR '}</Text> - <TouchableOpacity onPress={() => handleAccountPress('signup')}> - <Text style={styles.buttonHeader}>CREATE AN ACCOUNT</Text> - </TouchableOpacity> - </View> - ) -} - - -const styles = StyleSheet.create({ - containerHeader: { - backgroundColor: '#D9D9D9', - height: '10%', - width: '100%', - position: 'absolute', - display: 'flex', - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center' - }, - textHeader: { - color: '#000000', - fontSize: 20, - fontWeight: 'bold', - }, - buttonHeader: { - textDecorationLine: 'underline', - color: '#000000', - fontSize: 20, - fontWeight: 'bold', - }, -}); \ No newline at end of file -- GitLab From 1d2632ca3d6d69506ff4f756a53aa5cde363cf5a Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 14 Jan 2025 11:11:33 +0100 Subject: [PATCH 264/283] chore: clean code --- components/lists/QuizList.tsx | 1 - screens/Multiplayer/Lobby/Lobby.tsx | 1 - .../Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx | 5 ----- 3 files changed, 7 deletions(-) diff --git a/components/lists/QuizList.tsx b/components/lists/QuizList.tsx index dfad0f6..81051a6 100644 --- a/components/lists/QuizList.tsx +++ b/components/lists/QuizList.tsx @@ -42,7 +42,6 @@ interface Props { } export default function QuizList({ quizList, isLoadingData, nextPage, onQuizPressed }: Props) { if (quizList) { - console.log(quizList[0]); } const renderQuizItem = ({ item }: { item: Quiz }) => ( <TouchableOpacity style={styles.quizItem} onPress={() => onQuizPressed(item)}> diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index bdcdb42..fee0d0d 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -48,7 +48,6 @@ export default function Lobby({navigation, route}: Props) { }); setSocket(newSocket); - console.log(isHost); if(isHost) { newSocket.on("roomCreated", (data) => { diff --git a/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx index 1a5d713..f7cda1a 100644 --- a/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx +++ b/screens/Multiplayer/Lobby/PlayingQuizMultiMode/PlayingQuizMultiModeBody.tsx @@ -49,9 +49,6 @@ const getStyleOfAnswerButtonText = (answerId: number, selectedAnswerIndex: numbe } const getStyleOfAnswerButton = (answerId: number, selectedAnswerIndex: number | null, correctAnswerId: number | null, quizState: QuizState) => { - console.log("answerId :", answerId); - console.log("selectedAnswerIndex :", selectedAnswerIndex); - console.log("correctAnswerId :", correctAnswerId); if(quizState === QuizState.LOADING) { return styles.loadingAnswer; @@ -77,10 +74,8 @@ export default function PlayingQuizMultiModeBody({runId, actualQuestion, fetchAc const [isLastQuestion, setIsLastQuestion] = useState(false); const [quizInformations, setQuizInformations] = useState<QuizInformations>(); const [runInformations, setRunInformations] = useState<RunInformations>(); - const {answerQuestion, getRunsInfo, getQuizInformations} = useQuizService(); const getCorrectAnswer = (answers: Answer[]): Answer => { - console.log("starting getCorrectAnswer"); const correctAnswer = answers.find((answer) => answer.isCorrect); if (!correctAnswer) return answers[0]; return correctAnswer; -- GitLab From e9f62cca466e165e855bcbbcd0dc4a863b935c1d Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 14 Jan 2025 21:48:25 +0100 Subject: [PATCH 265/283] feat: new models --- models/Response/AllRunsResponse.ts | 5 +++++ models/RunsInformationsAllRuns.tsx | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 models/Response/AllRunsResponse.ts create mode 100644 models/RunsInformationsAllRuns.tsx diff --git a/models/Response/AllRunsResponse.ts b/models/Response/AllRunsResponse.ts new file mode 100644 index 0000000..8e7c71c --- /dev/null +++ b/models/Response/AllRunsResponse.ts @@ -0,0 +1,5 @@ +import {RunsInformationsAllRuns} from "../RunsInformationsAllRuns"; + +export interface AllRunsResponse{ + runs: RunsInformationsAllRuns[]; +} \ No newline at end of file diff --git a/models/RunsInformationsAllRuns.tsx b/models/RunsInformationsAllRuns.tsx new file mode 100644 index 0000000..f70c778 --- /dev/null +++ b/models/RunsInformationsAllRuns.tsx @@ -0,0 +1,20 @@ +import {Category, Difficulty} from "./Quiz"; + +export interface QuizInfoRun{ + category: Category; + difficulty: Difficulty; + name?: string; + questionCount: string; +} +export interface RunsInformationsAllRuns{ + id: string; + quizId: string; + originalQuizId: string; + score: number; + questionIndex: number; + startDate: string; + lastChange: string; + quizUserId: number; + roomId: number; + quiz: QuizInfoRun; +} \ No newline at end of file -- GitLab From 3bc4d53d465f04f182e7508ad8707834b16272a5 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 14 Jan 2025 21:48:44 +0100 Subject: [PATCH 266/283] feat: new packages --- package-lock.json | 7 +++++++ package.json | 1 + 2 files changed, 8 insertions(+) diff --git a/package-lock.json b/package-lock.json index d1035b2..2a605a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "react-native-progress": "^5.0.1", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", + "react-native-sse": "^1.2.1", "react-native-vector-icons": "^10.2.0", "react-query": "^3.39.3", "rxjs": "^7.8.1", @@ -9775,6 +9776,12 @@ "react-native": "*" } }, + "node_modules/react-native-sse": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-sse/-/react-native-sse-1.2.1.tgz", + "integrity": "sha512-zejanlScF+IB9tYnbdry0MT34qjBXbiV/E72qGz33W/tX1bx8MXsbB4lxiuPETc9v/008vYZ60yjIstW22VlVg==", + "license": "MIT" + }, "node_modules/react-native-svg": { "version": "15.10.1", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.10.1.tgz", diff --git a/package.json b/package.json index 7a0c0d2..fd3a2bf 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react-native-progress": "^5.0.1", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.1.0", + "react-native-sse": "^1.2.1", "react-native-vector-icons": "^10.2.0", "react-query": "^3.39.3", "rxjs": "^7.8.1", -- GitLab From f065f8a98176e4fa87e5d32f3bac1b413703c7bc Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 14 Jan 2025 21:49:02 +0100 Subject: [PATCH 267/283] feat: new service --- services/MultiService.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 services/MultiService.ts diff --git a/services/MultiService.ts b/services/MultiService.ts new file mode 100644 index 0000000..f0de634 --- /dev/null +++ b/services/MultiService.ts @@ -0,0 +1,36 @@ +import axios from "axios"; +import {QuestionResponse} from "../models/Response/QuestionResponse"; +import HttpError from "./HttpError"; + +export const useMultiService = () => { + const url = "https://klebert-host.com:33037" + + const createParty = async (quizId: string) : Promise<string | HttpError> => { + console.log("Create party with ID :", quizId); + console.log(url+"/party"); + const requestBody = { + quizId: quizId, + }; + + try { + const response = await axios.post(`${url}/party`, requestBody, { + withCredentials: true, + }); + console.log("Response data:", response.data.id); + return response.data.id; + } catch (error: any) { + console.log("Error while create party:", error); + + if (error.response) { + return new HttpError(error.response.status, error.response.data?.message || "HTTP error"); + } else { + return new HttpError(500, "Unexpected error: " + error.message); + } + } + } + + return { + createParty: createParty, + } + +} \ No newline at end of file -- GitLab From 4bd7cbb9fec9694f9fdd6053a52f5156d3931253 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 14 Jan 2025 21:52:14 +0100 Subject: [PATCH 268/283] refactor: edit service --- services/QuizService.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index 8dbbc26..18da399 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -1,5 +1,5 @@ import HttpError from "./HttpError"; -import {Quiz, QuizCommunityResponse} from "../models/Quiz"; +import {QuizCommunityResponse} from "../models/Quiz"; import axios from "axios"; import {Answer} from "../models/Answer"; import {Question} from "../models/Question"; @@ -8,7 +8,7 @@ import {QuizInformations} from "../models/QuizInformations"; import {QuestionResponse} from "../models/Response/QuestionResponse"; import {QuizGenerateAnswer} from "../models/QuizGenerateAnswer"; import {RunInformations} from "../models/RunInformations"; -import {RunsInformationResponse} from "../models/Response/RunsInformationResponse"; +import {AllRunsResponse} from "../models/Response/AllRunsResponse"; export const useQuizService = () => { const url = "https://klebert-host.com:33037"; // Remplace par l'URL de ton endpoint API @@ -200,7 +200,7 @@ export const useQuizService = () => { // Récupération du run ID return {id: runId, quizId: response.data.quizId}; } catch (error: any) { - console.log("Error while generating quiz:", error); + console.log("Error while generating random quiz:", error); if (error.response) { // Gestion des erreurs HTTP @@ -285,15 +285,15 @@ export const useQuizService = () => { } } - const getAllRuns = async (): Promise<RunInformations[] | HttpError> => { + const getAllRuns = async (): Promise<AllRunsResponse | HttpError> => { try { - const response = await axios.get<RunsInformationResponse>(`${url}/run/userRuns`, { + const response = await axios.get<AllRunsResponse>(`${url}/run/userRuns`, { headers: { "Content-Type": "application/json", }, }); - return response.data.runs; + return response.data; } catch (error: any) { console.log("Error while getAllRuns:", error); if (error.response) { -- GitLab From 09f0fcb6d23fa360acae6a8e299c0210b4adb175 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 14 Jan 2025 21:52:52 +0100 Subject: [PATCH 269/283] refactor: lobby --- screens/Multiplayer/Lobby/Lobby.tsx | 121 +++++++----------- .../OnlineQuiz/OnlineCreateLobby.tsx | 5 +- .../Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx | 7 +- 3 files changed, 53 insertions(+), 80 deletions(-) diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index fee0d0d..c20d61a 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -1,16 +1,17 @@ -import { View, Text, StyleSheet, Button, Image, ScrollView, FlatList } from "react-native"; -import TemplateMenu from "../../../templates/TemplateMenu"; +import {View, Text, StyleSheet, Image, FlatList} from "react-native"; import {NavigationProp, RouteProp} from "@react-navigation/native"; import React, {useEffect, useState} from "react"; -import WhiteButton from "../../../components/buttons/WhiteButton"; -import BlueButton from "../../../components/buttons/BlueButton"; import { TouchableOpacity } from "react-native"; -import ModalInvitePlayer from "../../../components/Multiplayer/ModalInvitePlayer"; import {User} from "../../../models/User"; -import {QuizGenerateAnswer} from "../../../models/QuizGenerateAnswer"; import {io, Socket} from "socket.io-client"; import useCookie from "../../../hooks/UseCookie"; - +import {useMultiService} from "../../../services/MultiService"; +import HttpError from "../../../services/HttpError"; +import TemplateMenu from "../../../templates/TemplateMenu"; +import BlueButton from "../../../components/buttons/BlueButton"; +import WhiteButton from "../../../components/buttons/WhiteButton"; +import ModalInvitePlayer from "../../../components/Multiplayer/ModalInvitePlayer"; +import EventSource from "react-native-sse"; interface Props { navigation: NavigationProp<any>; route: RouteProp<RoutePropsType, "Lobby">; @@ -18,98 +19,70 @@ interface Props { type RoutePropsType = { Lobby: { - quizIdOrRoomId: string; + quizId?: string; + roomId?: string; nbPlayers: number; isHost: boolean; }; }; +type CustomEventType = "userJoined" | "nextQuestion" export default function Lobby({navigation, route}: Props) { - const { quizIdOrRoomId, nbPlayers, isHost} = route.params; + const { quizId, roomId, nbPlayers, isHost} = route.params; - // const [isHost, setIsHost] = useState<boolean>(true); - const [showModal, setShowModal] = useState<boolean>(false); const [players, setPlayers] = useState<User[]>([{ id: 0, username: 'Invite', email: 'invite@email.com'}]); - const {getCookie} = useCookie('access_token'); const [socket, setSocket] = useState<Socket | null>(null); - const [roomId, setRoomId] = useState<string>(""); + const [roomIdCreated, setRoomIdCreated] = useState<string | undefined>(roomId); const [runId, setRunId] = useState<string>(""); + const [showModal, setShowModal] = useState<boolean>(false); - const userInvite = { id: 0, username: 'Invite', email: 'invite@email.com'} - const initSocket = async () => { - const cookie = await getCookie(); - const newSocket = io("http://klebert-host.com:33053/party", { - transports: ["websocket"], // Utilise WebSocket uniquement - extraHeaders: { - Cookie: `access_token=${cookie}`, - }, - }); + const {createParty} = useMultiService(); - setSocket(newSocket); + const userInvite = { id: 0, username: 'Invite', email: 'invite@email.com'} + let goodId = roomId; + const initSSE = async () => { + console.log("Init SSE for quiz ID:", quizId); + if(isHost && quizId) { + const response = await createParty(quizId); + if (HttpError.isHttpError(response)) { + console.error("Failed to create party:", response.message); + return; + } + console.log("Created party with ID:", response); + goodId = response; + setRoomIdCreated(response); + } + console.log("User try to joined party with ID:", goodId); + console.log(`https://klebert-host.com:33037/party/join/${goodId}`) - if(isHost) { - newSocket.on("roomCreated", (data) => { - console.log("Room created : ", data.id); - console.log("Connected to the server"); + const es = new EventSource<CustomEventType>(`https://klebert-host.com:33037/party/join/${goodId}`); - setRoomId(data.id); - console.log("Host id room : ", data.id); - newSocket.emit("joinRoom", { roomId: data.id }); + es.addEventListener("userJoined", (event) => { + console.log("User joined event data:", event.data); + if(!event.data) return; - setTimeout(() => { - console.log("Sending generateRun"); - newSocket.emit("generateRun"); - },1000); - }); + const newUsers: User[] = JSON.parse(event.data); - newSocket.on("connect", () => { - newSocket.emit("createRoom", { quizId: quizIdOrRoomId }); + setPlayers((prevPlayers) => { + const ids = new Set(prevPlayers.map((player) => player.id)); + const filteredNewUsers = newUsers.filter((user) => !ids.has(user.id)); + return [...prevPlayers, ...filteredNewUsers]; }); - console.log("Host"); - - } - else { - console.log("pas Host id room : ", quizIdOrRoomId); - newSocket.emit("joinRoom", { roomId: quizIdOrRoomId }); - setTimeout(() => { - console.log("Sending generateRun"); - newSocket.emit("generateRun"); - },1000); - } - - newSocket.on("connect_error", (err) => { - console.error("Connection error: ", err.message); }); - newSocket.on("error", (err) => { - console.error("Socket error: ", err.message); + es.addEventListener("error", (err) => { + console.error("Error with SSE:", err); }); - // Mettez à jour la liste des joueurs lorsque quelqu'un rejoint - newSocket.on("userJoined", (data: User[]) => { - console.log("Updated user list:", data); - setPlayers([userInvite, ...data]); - }); - - newSocket.on("runId", (data) => { - console.log("Run ID:", data); - setRunId(data); - }); - } - - + return () => es.close(); + }; useEffect(() => { - initSocket(); - // return () => { - // if(socket === null) return; - // socket.disconnect(); - // setSocket(null); - // }; + initSSE(); }, []); const item = ({ item }: { item: User }) => { @@ -139,7 +112,7 @@ export default function Lobby({navigation, route}: Props) { }; const onPlayPressed = () => { - navigation.navigate("PlayingQuizMultiMode", {runId: runId, socket: socket, roomId: roomId}); + navigation.navigate("PlayingQuizMultiMode", {runId: runId, socket: socket, roomId: roomIdCreated}); }; const onCancelPressed = () => { @@ -180,7 +153,7 @@ export default function Lobby({navigation, route}: Props) { } </View> </TemplateMenu> - <ModalInvitePlayer showModal={showModal} onClosePressed={() => setShowModal(false)} roomId={roomId} /> + <ModalInvitePlayer showModal={showModal} onClosePressed={() => setShowModal(false)} roomId={roomIdCreated} /> </View> ) } diff --git a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx index f8f5d1a..7f7e8ee 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlineCreateLobby.tsx @@ -1,4 +1,4 @@ -import { Text,View, StyleSheet } from "react-native"; +import { View, StyleSheet } from "react-native"; import React, {useEffect, useState} from "react"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; import {useQuizService} from "../../../services/QuizService"; @@ -61,7 +61,8 @@ export default function OnlineCreateLobby({navigation}: Props) { } navigation.navigate("Lobby", { - quizIdOrRoomId: quizGenerated.quizId, + quizId: quizGenerated.quizId, + roomId: null, nbPlayers: parseInt(nbPlayers), isHost: true, }); diff --git a/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx b/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx index 03aba42..c91647d 100644 --- a/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx +++ b/screens/Multiplayer/OnlineQuiz/OnlinePlayQuiz.tsx @@ -1,8 +1,6 @@ import { StyleSheet, View, Text } from "react-native"; -import AboutAQuiz from "../../../components/PlayQuiz/AboutAQuiz"; -import React, { useEffect } from "react"; +import React from "react"; import InputPlayQuiz from "../../../components/PlayQuiz/InputPlayQuiz"; -import {Quiz} from "../../../models/Quiz"; import { useQuizService } from "../../../services/QuizService"; import HttpError from "../../../services/HttpError"; // import {getIdOfCategory} from "../../../helper/QuizHelper"; @@ -33,7 +31,8 @@ export default function OnlinePlayQuiz({navigation}: Props) { } setError(""); navigation.navigate("Lobby", { - quizIdOrRoomId: codeQuiz, + quizId: null, + roomId: codeQuiz, nbPlayers: 10, isHost: false, }); -- GitLab From 6c0931cd19712a20ee709cebe32a432ab7ce24c9 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 14 Jan 2025 21:53:44 +0100 Subject: [PATCH 270/283] refactor: type of runs return --- components/Multiplayer/ModalInvitePlayer.tsx | 2 +- components/PlayQuiz/RunStartedList.tsx | 16 ++++----- screens/Home/MyQuizzes/InformationsOfRuns.tsx | 34 +++++-------------- screens/Home/MyQuizzes/MyQuizzes.tsx | 9 ++--- templates/TemplateRunList.tsx | 9 ++--- 5 files changed, 24 insertions(+), 46 deletions(-) diff --git a/components/Multiplayer/ModalInvitePlayer.tsx b/components/Multiplayer/ModalInvitePlayer.tsx index 2c5fab8..de1e14c 100644 --- a/components/Multiplayer/ModalInvitePlayer.tsx +++ b/components/Multiplayer/ModalInvitePlayer.tsx @@ -4,7 +4,7 @@ import WhiteButton from "../buttons/WhiteButton"; interface Props { showModal: boolean; onClosePressed: () => void; - roomId: string; + roomId?: string; } export default function ModalInvitePlayer({showModal, onClosePressed, roomId}: Props) { diff --git a/components/PlayQuiz/RunStartedList.tsx b/components/PlayQuiz/RunStartedList.tsx index 6a7da51..34da385 100644 --- a/components/PlayQuiz/RunStartedList.tsx +++ b/components/PlayQuiz/RunStartedList.tsx @@ -1,8 +1,6 @@ import React from "react"; import { FlatList, View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from "react-native"; -import {Quiz} from "../../models/Quiz"; -import { NavigationProp } from "@react-navigation/native"; -import {RunInformations} from "../../models/RunInformations"; +import {RunsInformationsAllRuns} from "../../models/RunsInformationsAllRuns"; @@ -36,17 +34,15 @@ import {RunInformations} from "../../models/RunInformations"; * @returns Un composant React contenant une liste interactive de quiz ou des messages en fonction de l'état. */ interface Props { - runList: RunInformations[] | undefined; - onRunPressed:(runInfo: RunInformations) => void; + runList: RunsInformationsAllRuns[] | undefined; + onRunPressed:(runInfo: RunsInformationsAllRuns) => void; isLoadingData: boolean; } export default function RunStartedList({ runList, isLoadingData, onRunPressed }: Props) { - const renderQuizItem = ({ item }: { item: RunInformations }) => ( + const renderQuizItem = ({ item }: { item: RunsInformationsAllRuns }) => ( <TouchableOpacity style={styles.quizItem} onPress={() => onRunPressed(item)}> - <Text style={styles.quizTitle}>{item.id}</Text> - <Text style={styles.quizDetails}> - {item.score} - </Text> + <Text style={styles.quizTitle}>{item.quiz.name ? item.quiz.name : item.id}</Text> + <Text style={styles.quizDetails}>{item.quiz.category ? item.quiz.category.name : "Community quiz"}</Text> </TouchableOpacity> ); diff --git a/screens/Home/MyQuizzes/InformationsOfRuns.tsx b/screens/Home/MyQuizzes/InformationsOfRuns.tsx index 5ca934b..367b942 100644 --- a/screens/Home/MyQuizzes/InformationsOfRuns.tsx +++ b/screens/Home/MyQuizzes/InformationsOfRuns.tsx @@ -1,16 +1,12 @@ -import React, {useEffect, useState} from "react"; +import React from "react"; import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; import TemplateMenu from "../../../templates/TemplateMenu"; import {NavigationProp, RouteProp} from "@react-navigation/native"; -import {Quiz} from "../../../models/Quiz"; -import {RunInformations} from "../../../models/RunInformations"; -import {QuizInformations} from "../../../models/QuizInformations"; -import HttpError from "../../../services/HttpError"; -import {useQuizService} from "../../../services/QuizService"; +import {RunsInformationsAllRuns} from "../../../models/RunsInformationsAllRuns"; type RoutePropsType = { InformationsOfRuns: { - runInformations: RunInformations; + runInformations: RunsInformationsAllRuns; }; }; interface Props { @@ -20,20 +16,6 @@ interface Props { export default function InformationsOfRuns({ navigation, route }: Props) { const { runInformations, } = route.params; - const {getQuizInformations} = useQuizService(); - const [quizInformations, setQuizInformations] = useState<QuizInformations>(); - - const fetchQuizInformations = async () => { - const quizInformations = await getQuizInformations(runInformations.quizId); - if (quizInformations instanceof HttpError) { - return; - } - setQuizInformations(quizInformations); - } - - useEffect(() => { - fetchQuizInformations(); - }, []); const onPlay = () => { navigation.reset({ @@ -50,13 +32,15 @@ export default function InformationsOfRuns({ navigation, route }: Props) { return ( <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> <View style={styles.container}> - <Text style={styles.quizTitle}>{quizInformations ? quizInformations.name : "Loading..."}</Text> - <Text style={styles.quizDescription}>{quizInformations ? quizInformations.description :"Loading..."}</Text> + <Text style={styles.quizTitle}>{runInformations.quiz.name ? runInformations.quiz.name : "Loading..."}</Text> + {/*<Text style={styles.quizDescription}>{runInformations ? runInformations.description :"Loading..."}</Text>*/} <View style={styles.aboutContainer}> <Text style={styles.aboutTitle}>About this quiz :</Text> - <Text style={styles.aboutDetails}>{runInformations.questionIndex}/{quizInformations ? quizInformations.questionCount : "Loading..."} questions, {quizInformations ? quizInformations.difficulty.name : "Loading..."}</Text> - <Text style={styles.aboutDetails}>By : {quizInformations?.authorId && "Unknown"}</Text> + <Text style={styles.aboutDetails}>Current score : {runInformations ? runInformations.score : "Loading..."}</Text> + <Text style={styles.aboutDetails}>Question : {runInformations.questionIndex}/{runInformations ? runInformations.quiz.questionCount : "Loading..."}</Text> + <Text style={styles.aboutDetails}>Difficulty : {runInformations ? runInformations.quiz.difficulty.name : "Loading..."}</Text> + {/*<Text style={styles.aboutDetails}>By : {runInformations.authorId && "Unknown"}</Text>*/} </View> <TouchableOpacity style={styles.playButton} onPress={onPlay}> diff --git a/screens/Home/MyQuizzes/MyQuizzes.tsx b/screens/Home/MyQuizzes/MyQuizzes.tsx index 1e5ce51..d7d94bc 100644 --- a/screens/Home/MyQuizzes/MyQuizzes.tsx +++ b/screens/Home/MyQuizzes/MyQuizzes.tsx @@ -1,15 +1,16 @@ import {NavigationProp} from "@react-navigation/native"; import React, {useEffect, useState} from "react"; import TemplateRunList from "../../../templates/TemplateRunList"; -import {RunInformations} from "../../../models/RunInformations"; import {useQuizService} from "../../../services/QuizService"; import HttpError from "../../../services/HttpError"; +import {AllRunsResponse} from "../../../models/Response/AllRunsResponse"; +import {RunsInformationsAllRuns} from "../../../models/RunsInformationsAllRuns"; interface Props { navigation: NavigationProp<any> } export default function MyQuizzes({navigation}: Props) { - const [runList, setRunList] = useState<RunInformations[] | undefined>(undefined) + const [runList, setRunList] = useState<AllRunsResponse | undefined>(undefined) const [input, setInput] = useState<string>("") const {getAllRuns} = useQuizService(); @@ -26,11 +27,11 @@ export default function MyQuizzes({navigation}: Props) { getRuns(); },[]); - const onQuizPressed = (runInfo: RunInformations) => { + const onQuizPressed = (runInfo: RunsInformationsAllRuns) => { navigation.navigate("InformationsOfRuns", { runInformations: runInfo }); }; return ( - <TemplateRunList title={"My quizzes"} setTextInInput={setInput} runList={runList} navigation={navigation} buttonGoBack={true} isLoadingData={false} onRunPressed={onQuizPressed}/> + <TemplateRunList title={"My quizzes"} setTextInInput={setInput} runList={runList?.runs} navigation={navigation} buttonGoBack={true} isLoadingData={false} onRunPressed={onQuizPressed}/> ); } \ No newline at end of file diff --git a/templates/TemplateRunList.tsx b/templates/TemplateRunList.tsx index 20125c1..8a542ab 100644 --- a/templates/TemplateRunList.tsx +++ b/templates/TemplateRunList.tsx @@ -2,11 +2,8 @@ import React from "react"; import {View, Text, StyleSheet} from "react-native"; import TemplateMenu from "./TemplateMenu"; import {NavigationProp} from "@react-navigation/native"; -import InputPlayQuiz from "../components/PlayQuiz/InputPlayQuiz"; -import QuizList from "../components/lists/QuizList"; -import {Quiz} from "../models/Quiz"; import RunStartedList from "../components/PlayQuiz/RunStartedList"; -import {RunInformations} from "../models/RunInformations"; +import {RunsInformationsAllRuns} from "../models/RunsInformationsAllRuns"; /** @@ -24,11 +21,11 @@ interface Props { navigation: NavigationProp<any> title: string setTextInInput: (text: string) => void - runList: RunInformations[] | undefined + runList: RunsInformationsAllRuns[] | undefined buttonGoBack: boolean isLoadingData: boolean nextPage?: () => void; - onRunPressed:(runInfo: RunInformations) => void; + onRunPressed:(runInfo: RunsInformationsAllRuns) => void; } export default function TemplateRunList({navigation, title, setTextInInput, runList, buttonGoBack, isLoadingData, onRunPressed }: Props) { -- GitLab From d5f858189982e3c5745a4b242849b6295e513fd4 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 14 Jan 2025 23:17:30 +0100 Subject: [PATCH 271/283] style: info of quiz or run --- .../InformationsOfQuiz/InformationsOfQuiz.tsx | 89 +++++++++++++++++-- screens/Home/MyQuizzes/InformationsOfRuns.tsx | 86 ++++++++++++++++-- 2 files changed, 160 insertions(+), 15 deletions(-) diff --git a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx index 6e6c3dd..379dffc 100644 --- a/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx +++ b/screens/Community/InformationsOfQuiz/InformationsOfQuiz.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import {View, Text, StyleSheet, TouchableOpacity, Dimensions} from "react-native"; import TemplateMenu from "../../../templates/TemplateMenu"; import {NavigationProp, RouteProp} from "@react-navigation/native"; import {Quiz} from "../../../models/Quiz"; @@ -14,6 +14,7 @@ interface Props { navigation: NavigationProp<any>; route: RouteProp<RoutePropsType, "InformationsOfQuiz">; } +const { width, height } = Dimensions.get('window'); export default function InformationsOfQuiz({ navigation, route }: Props) { const { quiz } = route.params; @@ -25,18 +26,47 @@ export default function InformationsOfQuiz({ navigation, route }: Props) { navigation.navigate('PlayingQuiz', { runId: runId, quizId: quizId }); } + const QuizStatistic = ({ label, value }: { label: string, value: string }) => ( + <View style={styles.statItem}> + <Text style={styles.statLabel}>{label}</Text> + <Text style={styles.statValue}>{value || "Loading..."}</Text> + </View> + ); + return ( <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> <View style={styles.container}> <Text style={styles.quizTitle}>{quiz.name}</Text> - <Text style={styles.quizDescription}>{quiz.description && "No Description"}</Text> - <View style={styles.aboutContainer}> - <Text style={styles.aboutTitle}>About this quiz :</Text> - <Text style={styles.aboutDetails}>{quiz.questionCount} questions, {quiz.difficulty.name}</Text> - <Text style={styles.aboutDetails}>By : {quiz.authorId && "Unknown"}</Text> + <View style={styles.infoCard}> + <View style={styles.statsContainer}> + {/*<QuizStatistic*/} + {/* label="Current Score"*/} + {/* value={quiz.score ? quiz.score.toString() : "Loading..."}*/} + {/*/>*/} + {/*<QuizStatistic*/} + {/* label="Progress"*/} + {/* value={`${quiz.questionIndex}/${quiz.questionCount}`}*/} + {/*/>*/} + <QuizStatistic + label="Difficulty" + value={quiz.difficulty.name} + /> + </View> + <View style={styles.descriptionContainer}> + <Text style={styles.descriptionLabel}>Description</Text> + <Text style={styles.descriptionText}> + {quiz.description} + </Text> + </View> </View> + {/*<View style={styles.aboutContainer}>*/} + {/* <Text style={styles.aboutTitle}>About this quiz :</Text>*/} + {/* <Text style={styles.aboutDetails}>{quiz.questionCount} questions, {quiz.difficulty.name}</Text>*/} + {/* <Text style={styles.aboutDetails}>By : {quiz.authorId && "Unknown"}</Text>*/} + {/*</View>*/} + <TouchableOpacity style={styles.playButton} onPress={onPlay}> <Text style={styles.playButtonText}>PLAY</Text> </TouchableOpacity> @@ -52,6 +82,35 @@ const styles = StyleSheet.create({ paddingVertical: 20, justifyContent: "flex-start", }, + descriptionContainer: { + marginTop: 8, + }, + descriptionLabel: { + fontSize: 16, + fontWeight: "600", + color: "#666", + marginBottom: 8, + }, + descriptionText: { + fontSize: 15, + color: "#4a4a4a", + lineHeight: 22, + }, + infoCard: { + height: '65%', + backgroundColor: "white", + borderRadius: width * 0.04, + paddingHorizontal: '5%', + shadowColor: "#000", + shadowOffset: { width: 0, height: width * 0.01 }, + shadowOpacity: 0.1, + shadowRadius: width * 0.03, + elevation: 5, + }, + statsContainer: { + height: '15%', + justifyContent: 'space-around', + }, quizTitle: { fontSize: 24, fontWeight: "bold", @@ -81,6 +140,24 @@ const styles = StyleSheet.create({ marginBottom: 10, color: "#000", }, + statItem: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: '3%', + borderBottomWidth: 1, + borderBottomColor: '#f0f0f0', + }, + statLabel: { + fontSize: width * 0.04, + color: "#666", + fontWeight: "500", + }, + statValue: { + fontSize: 16, + color: "#2d2d2d", + fontWeight: "600", + }, aboutDetails: { fontSize: 16, color: "#555", diff --git a/screens/Home/MyQuizzes/InformationsOfRuns.tsx b/screens/Home/MyQuizzes/InformationsOfRuns.tsx index 367b942..6a461fd 100644 --- a/screens/Home/MyQuizzes/InformationsOfRuns.tsx +++ b/screens/Home/MyQuizzes/InformationsOfRuns.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; +import {View, Text, StyleSheet, TouchableOpacity, Dimensions} from "react-native"; import TemplateMenu from "../../../templates/TemplateMenu"; import {NavigationProp, RouteProp} from "@react-navigation/native"; import {RunsInformationsAllRuns} from "../../../models/RunsInformationsAllRuns"; @@ -14,6 +14,8 @@ interface Props { route: RouteProp<RoutePropsType, "InformationsOfRuns">; } +const { width, height } = Dimensions.get('window'); + export default function InformationsOfRuns({ navigation, route }: Props) { const { runInformations, } = route.params; @@ -28,19 +30,38 @@ export default function InformationsOfRuns({ navigation, route }: Props) { ] }); } + const QuizStatistic = ({ label, value }: { label: string, value: string }) => ( + <View style={styles.statItem}> + <Text style={styles.statLabel}>{label}</Text> + <Text style={styles.statValue}>{value || "Loading..."}</Text> + </View> + ); return ( <TemplateMenu navigation={navigation} headerNavigation={true} buttonGoBack={true}> <View style={styles.container}> <Text style={styles.quizTitle}>{runInformations.quiz.name ? runInformations.quiz.name : "Loading..."}</Text> - {/*<Text style={styles.quizDescription}>{runInformations ? runInformations.description :"Loading..."}</Text>*/} - - <View style={styles.aboutContainer}> - <Text style={styles.aboutTitle}>About this quiz :</Text> - <Text style={styles.aboutDetails}>Current score : {runInformations ? runInformations.score : "Loading..."}</Text> - <Text style={styles.aboutDetails}>Question : {runInformations.questionIndex}/{runInformations ? runInformations.quiz.questionCount : "Loading..."}</Text> - <Text style={styles.aboutDetails}>Difficulty : {runInformations ? runInformations.quiz.difficulty.name : "Loading..."}</Text> - {/*<Text style={styles.aboutDetails}>By : {runInformations.authorId && "Unknown"}</Text>*/} + <View style={styles.infoCard}> + <View style={styles.statsContainer}> + <QuizStatistic + label="Current Score" + value={runInformations?.score.toString()} + /> + <QuizStatistic + label="Progress" + value={`${runInformations.questionIndex}/${runInformations?.quiz.questionCount}`} + /> + <QuizStatistic + label="Difficulty" + value={runInformations?.quiz.difficulty.name} + /> + </View> + <View style={styles.descriptionContainer}> + <Text style={styles.descriptionLabel}>Description</Text> + <Text style={styles.descriptionText}> + {runInformations.quiz.description} + </Text> + </View> </View> <TouchableOpacity style={styles.playButton} onPress={onPlay}> @@ -58,6 +79,20 @@ const styles = StyleSheet.create({ paddingVertical: 20, justifyContent: "flex-start", }, + descriptionContainer: { + marginTop: 8, + }, + descriptionLabel: { + fontSize: 16, + fontWeight: "600", + color: "#666", + marginBottom: 8, + }, + descriptionText: { + fontSize: 15, + color: "#4a4a4a", + lineHeight: 22, + }, quizTitle: { fontSize: 24, fontWeight: "bold", @@ -81,6 +116,39 @@ const styles = StyleSheet.create({ shadowRadius: 3, elevation: 5, }, + statsContainer: { + height: '40%', + justifyContent: 'space-around', + }, + statValue: { + fontSize: 16, + color: "#2d2d2d", + fontWeight: "600", + }, + infoCard: { + height: '65%', + backgroundColor: "white", + borderRadius: width * 0.04, + padding: '5%', + shadowColor: "#000", + shadowOffset: { width: 0, height: width * 0.01 }, + shadowOpacity: 0.1, + shadowRadius: width * 0.03, + elevation: 5, + }, + statItem: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: '3%', + borderBottomWidth: 1, + borderBottomColor: '#f0f0f0', + }, + statLabel: { + fontSize: width * 0.04, + color: "#666", + fontWeight: "500", + }, aboutTitle: { fontSize: 18, fontWeight: "bold", -- GitLab From a98ad176c73c408af8ed4f0859bced4af0fc2b14 Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Tue, 14 Jan 2025 23:18:00 +0100 Subject: [PATCH 272/283] refactor: clean code --- components/ModalJoinQuiz.tsx | 135 ----------------------- models/RunsInformationsAllRuns.tsx | 1 + screens/Home/HomeChild.tsx | 2 - screens/Multiplayer/MultiplayerChild.tsx | 1 - 4 files changed, 1 insertion(+), 138 deletions(-) delete mode 100644 components/ModalJoinQuiz.tsx diff --git a/components/ModalJoinQuiz.tsx b/components/ModalJoinQuiz.tsx deleted file mode 100644 index be899d2..0000000 --- a/components/ModalJoinQuiz.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { Modal, View, Text, StyleSheet, TextInput } from "react-native"; -import DefaultButton from "./buttons/DefaultButton"; -import {useState} from "react"; -import {useQuizService} from "../services/QuizService"; -import HttpError from "../services/HttpError"; - -interface Props { - navigation: any; - modalVisible: boolean; - setModalVisible: (newModalVisible: boolean) => void; -} - -export default function ModalJoinQuiz({ navigation, modalVisible, setModalVisible }: Props) { - - const [isError, setIsError] = useState<boolean>(false); - const [errorMessage, setErrorMessage] = useState<string>(""); - const [codeQuiz, setCodeQuiz] = useState<string>(""); - const {remainingQuiz} = useQuizService(); - const handleButtonClosePressed = () => { - setModalVisible(!modalVisible); - setIsError(false); - setErrorMessage(""); - } - - const handleButtonJoinQuizPressed = async () => { - const quiz = await remainingQuiz(codeQuiz); - if(HttpError.isHttpError(quiz)){ - setIsError(true); - setErrorMessage("Code quiz invalide"); - return; - } - - navigation.reset({ - index: 0, - routes: [ - { - name: "PlayingQuiz", - params: {quizRecovered: quiz}, - }, - ] - }); - - - } - - return ( - <Modal - animationType="slide" - transparent={true} - visible={modalVisible} - onRequestClose={() => { - setModalVisible(!modalVisible); - }} - > - <View style={styles.centeredView}> - <View style={styles.modalView}> - <View style={styles.containerMenu}> - <Text style={styles.textMenu}>PLAY YOUR QUIZ</Text> - <DefaultButton text="X" handleButtonPressed={handleButtonClosePressed} buttonStyle={styles.buttonMenu} buttonText={styles.buttonTextMenu}/> - </View> - <View style={styles.containerCode}> - <Text style={styles.textCode}>ENTER THE QUIZ ID</Text> - {isError && <Text style={styles.textError}>{errorMessage}</Text>} - <TextInput placeholder="TYPE A CODE" style={styles.textInputCode} onChangeText={(text) => setCodeQuiz(text)}></TextInput> - <DefaultButton text="PLAY" handleButtonPressed={handleButtonJoinQuizPressed} buttonStyle={styles.buttonCode}/> - </View> - </View> - </View> - </Modal> - ); -} - -const styles = StyleSheet.create({ - centeredView: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - modalView: { - height: '35%', - width: '80%', - margin: '10%', - padding: '10%', - backgroundColor: 'white', - borderRadius: 20, - alignItems: 'center', - shadowColor: '#000', - shadowOffset: { - width: 0, - height: 2, - }, - shadowOpacity: 0.25, - shadowRadius: 4, - elevation: 5, - }, - containerMenu: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - height: '30%', - }, - textMenu: { - flex: 1, - textAlign: 'center', - fontSize: 20, - fontWeight: 'bold', - }, - buttonMenu: { - padding: 10, - backgroundColor: '#FFFFFF', - }, - buttonTextMenu: { - color: '#D5D5D5', - fontSize: 24, - }, - containerCode: { - height: '100%', - width: '100%', - }, - textCode: { - fontSize: 14 - }, - textError: { - fontSize: 14, - color: "red" - }, - textInputCode: { - borderColor: '#1FA9FF', - borderRadius: 10, - borderWidth: 2, - }, - buttonCode: { - margin: 20 - } -}); \ No newline at end of file diff --git a/models/RunsInformationsAllRuns.tsx b/models/RunsInformationsAllRuns.tsx index f70c778..0022d66 100644 --- a/models/RunsInformationsAllRuns.tsx +++ b/models/RunsInformationsAllRuns.tsx @@ -5,6 +5,7 @@ export interface QuizInfoRun{ difficulty: Difficulty; name?: string; questionCount: string; + description: string; } export interface RunsInformationsAllRuns{ id: string; diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 9532c96..5aaf326 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -3,7 +3,6 @@ import DefaultButton from "../../components/buttons/DefaultButton"; import {useTranslation} from "react-i18next"; import { useState } from "react"; import { TextsStyles } from "../../styles/TextsStyles"; -import ModalJoinQuiz from "../../components/ModalJoinQuiz"; import MenuButton from "../../components/buttons/MenuButton"; import {NavigationProp} from "@react-navigation/native"; import {useQuizService} from "../../services/QuizService"; @@ -57,7 +56,6 @@ export default function HomeChild({navigation}: Props) { <MenuButton text={"PLAY"} handleButtonPressed={handleButtonGeneratePressed}/> <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/> </View> - <ModalJoinQuiz modalVisible={modalVisible} setModalVisible={setModalVisible} navigation={navigation}/> </View> ); } diff --git a/screens/Multiplayer/MultiplayerChild.tsx b/screens/Multiplayer/MultiplayerChild.tsx index f7eee12..c766368 100644 --- a/screens/Multiplayer/MultiplayerChild.tsx +++ b/screens/Multiplayer/MultiplayerChild.tsx @@ -1,7 +1,6 @@ import {Image, StyleSheet, Text, View} from "react-native"; import {useTranslation} from "react-i18next"; import React, {useEffect, useState} from "react"; -import ModalJoinQuiz from "../../components/ModalJoinQuiz"; import MenuButton from "../../components/buttons/MenuButton"; import {NavigationProp} from "@react-navigation/native"; import HttpError from "../../services/HttpError"; -- GitLab From 65f1465b41c91de80d7fb2d81e5f682cba6c783a Mon Sep 17 00:00:00 2001 From: GOEPP THOMAS <thomas@saturne-digital.fr> Date: Wed, 15 Jan 2025 14:18:24 +0100 Subject: [PATCH 273/283] create lobby by community list --- screens/Multiplayer/Lobby/Lobby.tsx | 4 ++-- .../InformationsOfQuiz/MultiInformationsOfQuiz.tsx | 2 +- screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/screens/Multiplayer/Lobby/Lobby.tsx b/screens/Multiplayer/Lobby/Lobby.tsx index c20d61a..8aaff52 100644 --- a/screens/Multiplayer/Lobby/Lobby.tsx +++ b/screens/Multiplayer/Lobby/Lobby.tsx @@ -108,7 +108,7 @@ export default function Lobby({navigation, route}: Props) { const onLeavePressed = () => { - navigation.navigate("Multiplayer"); + navigation.navigate("TabNavigator"); }; const onPlayPressed = () => { @@ -116,7 +116,7 @@ export default function Lobby({navigation, route}: Props) { }; const onCancelPressed = () => { - navigation.navigate("Multiplayer"); + navigation.navigate("TabNavigator"); }; const onInvitePressed = () => { diff --git a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx index 14b3875..ca222f4 100644 --- a/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx +++ b/screens/Multiplayer/MultiplayerCommunity/InformationsOfQuiz/MultiInformationsOfQuiz.tsx @@ -29,7 +29,7 @@ export default function MultiInformationsOfQuiz({ navigation, route }: Props) { const onPlayPressed = async (nbPlayer: number) => { setShowModal(false); - navigation.navigate("Lobby", {quiz: quiz, nbPlayer: nbPlayer, isHost: true}); + navigation.navigate("Lobby", {quizId: quiz.id, roomId: null, nbPlayer: nbPlayer, isHost: true}); }; return ( diff --git a/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx index f368297..6374b8b 100644 --- a/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx +++ b/screens/Multiplayer/OngoingQuizzes/OngoingQuizzes.tsx @@ -56,7 +56,7 @@ export default function Community({navigation}: Props) { <TemplateQuizList title={"Ongoing Quizzes"} setTextInInput={setInput} quizList={data?.pages.flat() ?? []} navigation={navigation} buttonGoBack={true} isLoadingData={loading} nextPage={fetchNextPage} onQuizPressed={onQuizPressed}/> </View> <View style={styles.buttonContainer}> - <BlueButton text="JOIN" onPress={onJoinPressed} isDisabled={false}/> + <BlueButton text="JOINa" onPress={onJoinPressed} isDisabled={false}/> </View> </View> ); -- GitLab From 3dca0cbbec5b411713e0af1a2d62b1944413fd47 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 16 Jan 2025 14:18:29 +0100 Subject: [PATCH 274/283] refactor: rename file answerbutton --- components/buttons/{AnswerButtonText.tsx => AnswerButton.tsx} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename components/buttons/{AnswerButtonText.tsx => AnswerButton.tsx} (88%) diff --git a/components/buttons/AnswerButtonText.tsx b/components/buttons/AnswerButton.tsx similarity index 88% rename from components/buttons/AnswerButtonText.tsx rename to components/buttons/AnswerButton.tsx index 57427b4..00e8876 100644 --- a/components/buttons/AnswerButtonText.tsx +++ b/components/buttons/AnswerButton.tsx @@ -11,13 +11,13 @@ interface Props{ } /** - * AnswerButtonText : Un bouton par défaut, il possède déjà un style mais on peut le surcharger grâce à la propriété buttonStyle + * AnswerButton : Un bouton par défaut, il possède déjà un style mais on peut le surcharger grâce à la propriété buttonStyle * @param text - Texte du bouton * @param handleButtonPressed - Fonction qui sera exécutée lorsque le bouton sera pressé * @param buttonStyle - Surcharge du style du bouton * @param buttonText - Surcharge du style du texte du bouton */ -export default function AnswerButtonText({text, handleButtonPressed, buttonStyle, buttonText, indicator}: Props){ +export default function AnswerButton({text, handleButtonPressed, buttonStyle, buttonText, indicator}: Props){ return( <View style={styles.containerButton}> <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> -- GitLab From 558c3f2f07a16abe496fea36d6e9583555486ccb Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 16 Jan 2025 14:19:11 +0100 Subject: [PATCH 275/283] feat: remove brut quiz --- screens/PlayingQuiz/PlayingQuiz.tsx | 86 ++++------------------------- 1 file changed, 11 insertions(+), 75 deletions(-) diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 8c6546d..50867a5 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -30,85 +30,21 @@ export default function PlayingQuiz({route, navigation}:Props) { const [score, setScore] = useState(0); const fetchQuizInformations = async () => { - // const quizInformationsFetched = await getQuizInformations(quizId); - // if (HttpError.isHttpError(quizInformationsFetched)) { - // return; - // } - const quizInformationsFetched: QuizInformations = { - id: 1, - name: "Quiz de Test", - description: "Ceci est un quiz de test pour vérifier l'interface.", - isCommunity: true, - questionCount: 10, - categoryId: 2, - difficultyId: 3, - authorId: { - id: 42, - username: "TestAuthor", - email: "testauthor@example.com" - }, - createdAt: "2025-01-10T10:00:00Z", - updatedAt: "2025-01-11T12:00:00Z", - category: { - id: 2, - name: "Science" - }, - difficulty: { - id: 3, - name: "Moyenne" - } - }; - + const quizInformationsFetched = await getQuizInformations(quizId); + if (HttpError.isHttpError(quizInformationsFetched)) { + return; + } + setQuizInformations(quizInformationsFetched); } const fetchActualQuestion = async () => { - // const actualQuestionFetched = await getActualQuestionOfAQuiz(runId); - // if (HttpError.isHttpError(actualQuestionFetched)) { - // return; - // } - // const actualQuestionFetchedShuffle = shuffleAnswersOfQuiz(actualQuestionFetched); - // setActualQuestion(actualQuestionFetchedShuffle); - const actualQuestionFetched: Question = { - id: 1, - text: "Quelle image correspond à une étoile ?", - type: "imae", - categoryId: 2, - quizId: "quiz123", - order: 1, - answers: [ - { - id: 1, - text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - isCorrect: true, - questionId: 1 - }, - { - id: 2, - text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - isCorrect: false, - questionId: 1 - }, - { - id: 3, - text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - isCorrect: false, - questionId: 1 - }, - { - id: 4, - text: "https://st3.depositphotos.com/4164031/15614/i/450/depositphotos_156147646-stock-photo-star-field-in-deep-space.jpg", - isCorrect: false, - questionId: 1 - } - ], - category: { - id: 2, - name: "Astronomie" - } - }; - - setActualQuestion(actualQuestionFetched); + const actualQuestionFetched = await getActualQuestionOfAQuiz(runId); + if (HttpError.isHttpError(actualQuestionFetched)) { + return; + } + const actualQuestionFetchedShuffle = shuffleAnswersOfQuiz(actualQuestionFetched); + setActualQuestion(actualQuestionFetchedShuffle); } useEffect(() => { -- GitLab From cd0abbfe55f5e6135dbb7bd487afa4d9bfcde942 Mon Sep 17 00:00:00 2001 From: HUSS THEOPHILE <theophile.huss@etu.unistra.fr> Date: Thu, 16 Jan 2025 13:34:14 +0000 Subject: [PATCH 276/283] feat: Update QuizService -> getRandomQuiz --- services/QuizService.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/QuizService.ts b/services/QuizService.ts index 18da399..f742340 100644 --- a/services/QuizService.ts +++ b/services/QuizService.ts @@ -184,10 +184,12 @@ export const useQuizService = () => { }; const getRandomQuiz = async (): Promise<QuizGenerateAnswer | HttpError> => { + const categories = await getCategories(); + const difficulties = ['easy', 'medium', 'hard']; const requestBody = { amount: 10, - category: 10, - difficulty: "easy", + category: categories[Math.floor(Math.random() * categories.length)].id, + difficulty: difficulties[Math.floor(Math.random() * difficulties.length)], }; try { -- GitLab From 2c6f197c9b850a0e5170b317621db4fedf939c7e Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 16 Jan 2025 14:39:48 +0100 Subject: [PATCH 277/283] feat: allow image and text --- components/buttons/AnswerButton.tsx | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/components/buttons/AnswerButton.tsx b/components/buttons/AnswerButton.tsx index 00e8876..5577e08 100644 --- a/components/buttons/AnswerButton.tsx +++ b/components/buttons/AnswerButton.tsx @@ -1,4 +1,4 @@ -import {TouchableOpacity, Text, StyleSheet, View} from "react-native"; +import {TouchableOpacity, Text, StyleSheet, View, Image} from "react-native"; import {ButtonsStyles} from "../../styles/ButtonsStyles"; import {TextsStyles} from "../../styles/TextsStyles"; @@ -23,10 +23,21 @@ export default function AnswerButton({text, handleButtonPressed, buttonStyle, bu <TouchableOpacity onPress={handleButtonPressed} style={[styles.defaultButton,buttonStyle]}> {indicator !== undefined && ( <View style={styles.indicator}> - <Text style={styles.indicatorText}>{indicator}</Text> + <Text style={styles.indicatorText}>{indicator}</Text> </View> )} - <Text style={[TextsStyles.defaultButtonText, buttonText]}>{text}</Text> + { + text.includes('data:image') ? + <Image + style={[styles.answerImage]} + source={{uri: text}} + /> + : + text.includes('data:sound') ? + '' + : + <Text style={[TextsStyles.defaultButtonText, buttonText]}>{text}</Text> + } </TouchableOpacity> </View> ) @@ -63,5 +74,9 @@ const styles = StyleSheet.create({ display: 'flex', alignItems: 'center', justifyContent: 'center' - } + }, + answerImage: { + width: '100%', + height: '100%', + }, }) \ No newline at end of file -- GitLab From 602694dbf5586d89b247d35b62638aab32d8b291 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 16 Jan 2025 14:40:00 +0100 Subject: [PATCH 278/283] feat: use answerbutton --- screens/PlayingQuiz/PlayingQuizBody.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screens/PlayingQuiz/PlayingQuizBody.tsx b/screens/PlayingQuiz/PlayingQuizBody.tsx index 8c4f472..619e3b7 100644 --- a/screens/PlayingQuiz/PlayingQuizBody.tsx +++ b/screens/PlayingQuiz/PlayingQuizBody.tsx @@ -9,7 +9,7 @@ import BlueButton from "../../components/buttons/BlueButton"; import {Question} from "../../models/Question"; import {Answer} from "../../models/Answer"; import {Quiz} from "../../models/Quiz"; -import AnswerButtonText from "../../components/buttons/AnswerButtonText"; +import AnswerButton from "../../components/buttons/AnswerButton"; import {QuizInformations} from "../../models/QuizInformations"; import AnswerButtonImage from "../../components/buttons/AnswerButtonImage"; @@ -144,7 +144,7 @@ export default function PlayingQuizBody({ quizInformations, runId, actualQuestio buttonStyle={getStyleOfAnswerButton(answer.id, selectedAnswerId, correctAnswerId, quizState)} /> : - <AnswerButtonText + <AnswerButton key={index} text={answer.text} handleButtonPressed={() => onAnsweredButtonClicked(answer.id)} -- GitLab From 4b80b76095090c6a6c0068aebd79fd87ddbaee1e Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 16 Jan 2025 15:00:15 +0100 Subject: [PATCH 279/283] feat: sound not supported --- components/buttons/AnswerButton.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/buttons/AnswerButton.tsx b/components/buttons/AnswerButton.tsx index 5577e08..5b861da 100644 --- a/components/buttons/AnswerButton.tsx +++ b/components/buttons/AnswerButton.tsx @@ -1,6 +1,7 @@ import {TouchableOpacity, Text, StyleSheet, View, Image} from "react-native"; import {ButtonsStyles} from "../../styles/ButtonsStyles"; import {TextsStyles} from "../../styles/TextsStyles"; +import React from "react"; interface Props{ text: string; @@ -33,8 +34,8 @@ export default function AnswerButton({text, handleButtonPressed, buttonStyle, bu source={{uri: text}} /> : - text.includes('data:sound') ? - '' + text.includes('data:audio') ? + <Text style={[TextsStyles.defaultButtonText, buttonText]}>sound: not supported</Text> : <Text style={[TextsStyles.defaultButtonText, buttonText]}>{text}</Text> } -- GitLab From 36ad94d31a017befa93b90c92d5c1496163f5c4a Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 16 Jan 2025 15:12:32 +0100 Subject: [PATCH 280/283] feat: hide multiplayer --- routes/TabNavigation.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/routes/TabNavigation.tsx b/routes/TabNavigation.tsx index 332a6cb..bc58c3e 100644 --- a/routes/TabNavigation.tsx +++ b/routes/TabNavigation.tsx @@ -37,9 +37,9 @@ export default function TabNavigator() { } else if (route.name === "Community") { iconName = "people"; } - else if (route.name === "Multiplayer") { - iconName = "diversity-2"; - } + // else if (route.name === "Multiplayer") { + // iconName = "diversity-2"; + // } return <Icon name={iconName ?? ''} size={64} color={color} />; }, })} @@ -52,10 +52,10 @@ export default function TabNavigator() { name="Community" component={Community} /> - <Tab.Screen + {/* <Tab.Screen name="Multiplayer" component={Multiplayer} - /> + /> */} <Tab.Screen name="Profil" component={Profil} -- GitLab From 5d4f6547124ec88a6d638eccb54598e2a97846de Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 16 Jan 2025 15:14:12 +0100 Subject: [PATCH 281/283] feat: hide my quizzes --- screens/Home/HomeChild.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index 718249e..f17f6fe 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -39,8 +39,8 @@ export default function HomeChild({navigation}: Props) { }); } const handleButtonMyQuizzesPressed = () => { - // navigation.navigate("MyQuizzes"); - navigation.navigate("PlayingQuiz", {runId: "1", quizId: "1"}); + navigation.navigate("MyQuizzes"); + // navigation.navigate("PlayingQuiz", {runId: "1", quizId: "1"}); } const handleButtonGeneratePressed = () => { @@ -55,7 +55,7 @@ export default function HomeChild({navigation}: Props) { <View style={styles.buttonContainer}> <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonQuickGamePressed}/> <MenuButton text={"PLAY"} handleButtonPressed={handleButtonGeneratePressed}/> - <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/> + {/* <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/> */} </View> </View> ); -- GitLab From c8fbf4c10a199f52658d040ccbdaeb6cccf1b8b6 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 16 Jan 2025 15:17:26 +0100 Subject: [PATCH 282/283] feat: add score to param playingquizbody --- screens/PlayingQuiz/PlayingQuiz.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/PlayingQuiz/PlayingQuiz.tsx b/screens/PlayingQuiz/PlayingQuiz.tsx index 50867a5..3f9134b 100644 --- a/screens/PlayingQuiz/PlayingQuiz.tsx +++ b/screens/PlayingQuiz/PlayingQuiz.tsx @@ -58,7 +58,7 @@ export default function PlayingQuiz({route, navigation}:Props) { return ( <TemplateDuo childrenHeader={<PlayingQuizHeader quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} score={score} navigation={navigation}></PlayingQuizHeader>} - childrenBody={<PlayingQuizBody quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} fetchActualQuestion={fetchActualQuestion} setScore={setScore} navigation={navigation}></PlayingQuizBody>} + childrenBody={<PlayingQuizBody score={score} quizInformations={quizInformations} runId={runId} actualQuestion={actualQuestion} fetchActualQuestion={fetchActualQuestion} setScore={setScore} navigation={navigation}></PlayingQuizBody>} /> ); } \ No newline at end of file -- GitLab From c55cf7f5a82de1270dd2e98a6a1f11ea47a69b07 Mon Sep 17 00:00:00 2001 From: "h.hartz" <henri.hartz@etu.unistra.fr> Date: Thu, 16 Jan 2025 15:19:11 +0100 Subject: [PATCH 283/283] feat: show my quizzes --- screens/Home/HomeChild.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/Home/HomeChild.tsx b/screens/Home/HomeChild.tsx index f17f6fe..fa17672 100644 --- a/screens/Home/HomeChild.tsx +++ b/screens/Home/HomeChild.tsx @@ -55,7 +55,7 @@ export default function HomeChild({navigation}: Props) { <View style={styles.buttonContainer}> <MenuButton text={"QUICK GAME"} handleButtonPressed={handleButtonQuickGamePressed}/> <MenuButton text={"PLAY"} handleButtonPressed={handleButtonGeneratePressed}/> - {/* <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/> */} + <MenuButton text={"MY QUIZZES"} handleButtonPressed={handleButtonMyQuizzesPressed}/> </View> </View> ); -- GitLab