Skip to content
Snippets Groups Projects
Commit 0623ef8d authored by GOEPP THOMAS's avatar GOEPP THOMAS
Browse files

:twisted_rightwards_arrows: merge Feature/138 refacto plaing quiz sse

Merge branch 'feature/138-refacto-plaing-quiz-sse' into 'develop'
parents beb22b8f 2cd4df11
Branches
1 merge request!136Feature/138 refacto plaing quiz sse
Pipeline #325833 passed with stages
in 11 seconds
Showing
with 298 additions and 83 deletions
import React from "react";
import { FlatList, View, Text, StyleSheet, Image } from "react-native";
import {User} from "../../../models/User";
import {UserEndQuiz} from "../../../models/UserEndQuiz";
interface Props {
users: User[];
users: UserEndQuiz[];
maxScore: number;
}
export default function EndQuizListPlayer({ users, maxScore }: Props) {
const renderUser = ({ item, index }: { item: User; index: number }) => (
const renderUser = ({ item, index }: { item: UserEndQuiz; index: number }) => (
<View style={[styles.userContainer, index === 0 && styles.firstPlace]}>
<Image
source={require("../../../assets/ProfilBaseImage.png")} // Replace with actual profile picture if available
......@@ -19,7 +20,7 @@ export default function EndQuizListPlayer({ users, maxScore }: Props) {
{item.username.toUpperCase()}
</Text>
<Text style={styles.score}>
{10 + index}/{maxScore} {/* Adjust score logic as needed */}
{item.score}/{maxScore} {/* Adjust score logic as needed */}
</Text>
</View>
</View>
......
import { View, Text, StyleSheet, Image } from "react-native";
import {User} from "../../../models/User";
import {UserEndQuiz} from "../../../models/UserEndQuiz";
interface Props {
user: User;
user: UserEndQuiz;
score: number;
maxScore: number;
isFirst: boolean;
}
export default function UserScore({ user, score, maxScore, isFirst }: Props) {
export default function UserScore({ user, maxScore, isFirst }: Props) {
function shortenString(input: string): string {
if (input.length <= 5) {
......@@ -27,7 +28,7 @@ export default function UserScore({ user, score, maxScore, isFirst }: Props) {
</View>
<Text style={styles.userName}>{shortenString(user.username.toUpperCase())}</Text>
<Text style={styles.userScore}>
{score}/{maxScore}
{user.score}/{maxScore}
</Text>
</View>
);
......
export type CustomEventType = "userJoined" | "nextQuestion" | "generateRun" | "gameStarted" | "answerRevealed" | "gameEnded";
import {Answer} from "../Answer";
export interface AnswerSSEResponse {
id: number;
text: string;
type:string;
categoryId: number;
quizId: string;
order: number;
answers: Answer[];
}
\ No newline at end of file
export interface UserEndQuiz{
id: number,
username: string,
score: number
}
\ No newline at end of file
......@@ -12,6 +12,8 @@ import BlueButton from "../../../components/buttons/BlueButton";
import WhiteButton from "../../../components/buttons/WhiteButton";
import ModalInvitePlayer from "../../../components/Multiplayer/ModalInvitePlayer";
import EventSource from "react-native-sse";
import {CustomEventType} from "../../../models/CustomEventType";
import {Question} from "../../../models/Question";
interface Props {
navigation: NavigationProp<any>;
route: RouteProp<RoutePropsType, "Lobby">;
......@@ -26,7 +28,10 @@ type RoutePropsType = {
};
};
type CustomEventType = "userJoined" | "nextQuestion"
interface GenerateRunData {
runId: string; // ou number selon votre cas
// autres propriétés si nécessaire
}
export default function Lobby({navigation, route}: Props) {
......@@ -37,8 +42,9 @@ export default function Lobby({navigation, route}: Props) {
const [roomIdCreated, setRoomIdCreated] = useState<string | undefined>(roomId);
const [runId, setRunId] = useState<string>("");
const [showModal, setShowModal] = useState<boolean>(false);
const [esUseState, setEsUseState] = useState<EventSource<CustomEventType>>();
const {createParty} = useMultiService();
const {createParty, generateRun, startParty} = useMultiService();
const userInvite = { id: 0, username: 'Invite', email: 'invite@email.com'}
let goodId = roomId;
......@@ -55,9 +61,24 @@ export default function Lobby({navigation, route}: Props) {
setRoomIdCreated(response);
}
console.log("User try to joined party with ID:", goodId);
console.log(`https://klebert-host.com:33037/party/join/${goodId}`)
const es = new EventSource<CustomEventType>(`https://klebert-host.com:33037/party/join/${goodId}`);
setEsUseState(es);
es.addEventListener("gameStarted", async (event) => {
if(!event.data) return;
console.log("Game started event data:",event.data);
const responseGenerate = await generateRun();
if(HttpError.isHttpError(responseGenerate)) {
console.log("zzzzzzzzz");
return;
}
console.log("generate run",responseGenerate);
setRunId(responseGenerate);
// navigation.navigate("PlayingQuizMultiMode", {runId: responseGenerate, questionCount:event.data.questionCount, es: es, roomId: roomId, isHost: true});
navigation.navigate("PlayingQuizMultiMode", {runId: responseGenerate, questionCount: 10, es: es, roomId: roomId, isHost: isHost});
});
es.addEventListener("userJoined", (event) => {
console.log("User joined event data:", event.data);
......@@ -70,15 +91,18 @@ export default function Lobby({navigation, route}: Props) {
const filteredNewUsers = newUsers.filter((user) => !ids.has(user.id));
return [...prevPlayers, ...filteredNewUsers];
});
});
es.addEventListener("error", (err) => {
console.error("Error with SSE:", err);
console.log("Error with SSE:", err);
});
return () => es.close();
useEffect(() => {
return () => {
es.close();
};
}, []);
};
useEffect(() => {
......@@ -112,7 +136,8 @@ export default function Lobby({navigation, route}: Props) {
};
const onPlayPressed = () => {
navigation.navigate("PlayingQuizMultiMode", {runId: runId, socket: socket, roomId: roomIdCreated});
console.log("playing es :" + esUseState);
startParty();
};
const onCancelPressed = () => {
......
......@@ -10,44 +10,67 @@ import PlayingQuizMultiModeBody from "./PlayingQuizMultiModeBody";
import {Socket} from "socket.io-client";
import {QuestionResponse} from "../../../../models/Response/QuestionResponse";
import {RunInformations} from "../../../../models/RunInformations";
import EventSource from "react-native-sse";
import {CustomEventType} from "../../../../models/CustomEventType";
import {AnswerSSEResponse} from "../../../../models/Response/AnswerSSEResponse";
import {useMultiService} from "../../../../services/MultiService";
import {UserEndQuiz} from "../../../../models/UserEndQuiz";
import {shuffleAnswersOfQuiz} from "../../../../helper/QuizHelper";
type RoutePropsType = {
PlayingQuizMultiMode: {
runId: string;
socket: Socket;
questionCount: number;
es: EventSource<CustomEventType>;
roomId: string;
isHost: boolean;
};
};
enum QuizState {
ANSWERING= "Answering",
LOADING= "Loading",
SHOWING_RESULTS= "ShowingResults",
}
interface Props {
route: RouteProp<RoutePropsType, "PlayingQuizMultiMode">;
navigation: NavigationProp<any>;
}
export default function PlayingQuizMultiMode({route, navigation}:Props) {
const {runId, socket, roomId} = route.params;
const { getQuizInformations, getRunsInfo } = useQuizService();
const {runId, es, roomId,questionCount, isHost} = route.params;
const {getQuizInformations, getRunsInfo} = useQuizService();
const [quizState, setQuizState] = useState(QuizState.ANSWERING);
const [quizInformations, setQuizInformations] = useState<QuizInformations | undefined>(undefined);
const [runInformations, setRunInformations] = useState<RunInformations>();
const [actualQuestion, setActualQuestion] = useState<QuestionResponse | undefined>(undefined);
const [actualQuestion, setActualQuestion] = useState<Question | undefined>(undefined);
const [score, setScore] = useState(0);
const {nextQuestion} = useMultiService();
const initSocket = async () => {
socket.on("gameStarted", (data) => {
console.log("Game started:", data);
});
const initES = () => {
es.addEventListener("nextQuestion", (event) => {
console.log("nextQuestion event data:");
if (!event.data) return;
const question: Question = JSON.parse(event.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
setActualQuestion(shuffleAnswersOfQuiz(question));
setQuizState(QuizState.ANSWERING);
});
es.addEventListener("gameEnded", (event) => {
console.log("gameEnded event data:");
if (!event.data) return;
const users: UserEndQuiz[] = JSON.parse(event.data);
console.log("users:", users);
navigation.navigate("EndQuizMulti", { users: users, questionCount: questionCount });
});
}
const fetchRuns = async () => {
if(runId === undefined) return;
console.log("run to get info :", runId);
if (runId === undefined) return;
const runFetched = await getRunsInfo(runId);
if (runFetched instanceof HttpError) {
console.log("Error fetching runs info", runFetched);
......@@ -63,22 +86,25 @@ export default function PlayingQuizMultiMode({route, navigation}:Props) {
};
useEffect(() => {
console.log("PlayingQuizMultiMode starting");
initSocket();
initES();
fetchRuns();
setTimeout(() => {
socket.emit("startGame")
},500);
setTimeout(() => {
socket.emit("nextQuestion");
},2000);
console.log("fetching first question");
nextQuestion();
}, []);
return (
<TemplateDuo
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>}
childrenHeader={<PlayingQuizMultiModeHeader quizInformations={quizInformations} runId={runId}
actualQuestion={actualQuestion} score={score}
navigation={navigation} questionCount={10}></PlayingQuizMultiModeHeader>}
childrenBody={<PlayingQuizMultiModeBody runId={runId} actualQuestion={actualQuestion} es={es}
fetchActualQuestion={() => {
}} roomId={roomId} isHost={isHost}
navigation={navigation}
quizState={quizState}
setScore={setScore}
setQuizState={setQuizState}>
</PlayingQuizMultiModeBody>}
/>
);
}
\ No newline at end of file
}
......@@ -13,14 +13,22 @@ import {QuestionResponse} from "../../../../models/Response/QuestionResponse";
import {RunInformations} from "../../../../models/RunInformations";
import {Socket} from "socket.io-client";
import {AnswerWsResponse} from "../../../../models/Response/AnswerWsResponse";
import EventSource from "react-native-sse";
import {CustomEventType} from "../../../../models/CustomEventType";
import {AnswerSSEResponse} from "../../../../models/Response/AnswerSSEResponse";
import {useMultiService} from "../../../../services/MultiService";
interface Props {
runId?: string;
actualQuestion?: QuestionResponse;
actualQuestion?: Question;
fetchActualQuestion: () => void;
socket: Socket;
es: EventSource<CustomEventType>;
roomId: string;
isHost: boolean;
quizState: QuizState;
setQuizState: (quizState: QuizState) => void;
setScore: (score: number) => void;
navigation: NavigationProp<any>;
}
......@@ -67,14 +75,14 @@ const getStyleOfAnswerButton = (answerId: number, selectedAnswerIndex: number |
return styles.answerButton;
}
export default function PlayingQuizMultiModeBody({runId, actualQuestion, fetchActualQuestion, roomId, socket, navigation }: Props) {
export default function PlayingQuizMultiModeBody({runId, actualQuestion, fetchActualQuestion, roomId, es,isHost, setScore, quizState,setQuizState, 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, getRunsInfo, getQuizInformations} = useQuizService();
const {getRunsInfo, getQuizInformations} = useQuizService();
const {revealAnswer, answerQuestion, nextQuestion, getScore} = useMultiService();
const getCorrectAnswer = (answers: Answer[]): Answer => {
const correctAnswer = answers.find((answer) => answer.isCorrect);
if (!correctAnswer) return answers[0];
......@@ -83,15 +91,17 @@ export default function PlayingQuizMultiModeBody({runId, actualQuestion, fetchAc
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);
es.addEventListener("answerRevealed", (event) => {
if(!event.data) return;
const answers: AnswerSSEResponse = JSON.parse(event.data);
setCorrectAnswerId(getCorrectAnswer(answers.answers).id);
setQuizState(QuizState.SHOWING_RESULTS);
});
// socket.on("score", (data) => {
// console.log("Score:", data);
// });
es.addEventListener("nextQuestion", (event) => {
});
}
useEffect(() => {
......@@ -104,22 +114,29 @@ export default function PlayingQuizMultiModeBody({runId, actualQuestion, fetchAc
if(selectedAnswerId === null || actualQuestion === undefined) return;
setQuizState(QuizState.LOADING);
socket.emit("submitAnswer", {
roomId,
runId,
questionId: actualQuestion.question.id,
answerId: selectedAnswerId,
});
await answerQuestion(actualQuestion.id, selectedAnswerId);
if(isHost)
{
await revealAnswer();
}
const scoreFetched = await getScore();
if(HttpError.isHttpError(scoreFetched))
{
return;
}
setScore(scoreFetched);
setTimeout(() => {
socket.emit("revealAnswer", {runId: runId});
},500);
};
const onContinue = () => {
socket.emit("nextQuestion");
setQuizState(QuizState.ANSWERING);
setCorrectAnswerId(null);
setSelectedAnswerId(null);
if(isHost)
{
nextQuestion();
setQuizState(QuizState.ANSWERING);
setCorrectAnswerId(null);
setSelectedAnswerId(null);
}
}
const onAnsweredButtonClicked = (answerId: number) => {
......@@ -133,7 +150,7 @@ export default function PlayingQuizMultiModeBody({runId, actualQuestion, fetchAc
<View style={styles.buttonContainer}>
{actualQuestion ? (
<>
{actualQuestion.question.answers.map((answer, index) => (
{actualQuestion.answers.map((answer, index) => (
<AnswerButton
key={index}
text={answer.text}
......
......@@ -12,12 +12,14 @@ import {QuestionResponse} from "../../../../models/Response/QuestionResponse";
interface Props {
quizInformations?: QuizInformations;
runId?: string;
actualQuestion?: QuestionResponse;
actualQuestion?: Question;
score: number;
questionCount: number;
navigation: NavigationProp<any>
}
export default function PlayingQuizMultiModeHeader({quizInformations, runId, actualQuestion, score, navigation}: Props) {
export default function PlayingQuizMultiModeHeader({quizInformations, runId, actualQuestion, score, questionCount, navigation}: Props) {
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);
const {t} = useTranslation();
......@@ -42,15 +44,15 @@ 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.question.order + 1 :"?"}/{quizInformations ? quizInformations.questionCount : "?"}</Text>
<Text style={TextsStyles.titleText}>{actualQuestion ? actualQuestion.order + 1 :"?"}/{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>
<Text style={TextsStyles.titleText}>{score}/{questionCount}</Text>
</View>
</View>
<View style={styles.QuizQuestionContainer}>
<Text style={TextsStyles.subtitleText}>{actualQuestion ? actualQuestion.question.text : "Loading..."}</Text>
<Text style={TextsStyles.subtitleText}>{actualQuestion ? actualQuestion.text : "Loading..."}</Text>
</View>
<ConfirmModal visible={isConfirmModalVisible} onClose={()=>setIsConfirmModalVisible(false)} onConfirm={handleConfirmModalConfirm}/>
......
......@@ -63,7 +63,6 @@ export default function MultiplayerChild({navigation}: Props) {
<View style={styles.buttonContainer}>
<MenuButton text={"COMMUNITY"} handleButtonPressed={handleButtonCommunityPressed}/>
<MenuButton text={"PLAY AN ONLINE QUIZ"} handleButtonPressed={handleOnlineQuizPressed}/>
<MenuButton text={"ONGOING QUIZZES"} handleButtonPressed={handleButtonOngoingQuizzesPressed}/>
</View>
</>
)}
......
......@@ -2,12 +2,13 @@ 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";
import {UserEndQuiz} from "../../../models/UserEndQuiz";
type RoutePropsType = {
EndQuizChild: {
quiz: Quiz;
users: UserEndQuiz[];
questionCount: number;
};
};
interface Props {
......@@ -16,12 +17,12 @@ interface Props {
}
export default function EndQuizMulti({route,navigation}: Props) {
const { quiz } = route.params;
const { users, questionCount } = route.params;
return (
<View style={styles.containerGlobal}>
<TemplateMenu navigation={navigation} headerNavigation={false} buttonGoBack={false}>
<View>
<EndQuizMultiChild navigation={navigation} quiz={quiz}/>
<EndQuizMultiChild navigation={navigation} users={users} questionCount={questionCount} />
</View>
</TemplateMenu>
</View>
......
......@@ -6,15 +6,17 @@ import {useQuizService} from "../../../services/QuizService";
import HttpError from "../../../services/HttpError";
import UserScore from "../../../components/PlayingQuiz/EndQuiz/UserScore";
import EndQuizListPlayer from "../../../components/PlayingQuiz/EndQuiz/EndQuizListPlayer";
import {UserEndQuiz} from "../../../models/UserEndQuiz";
interface Props {
navigation: NavigationProp<any>;
quiz: Quiz;
users: UserEndQuiz[]
questionCount: number
}
export default function EndQuizMultiChild({ navigation, quiz }: Props) {
export default function EndQuizMultiChild({ navigation, users, questionCount}: Props) {
const userTest = {id: 1, email: "user1@email.com", username: "user1", stats: undefined};
......@@ -33,11 +35,11 @@ export default function EndQuizMultiChild({ navigation, quiz }: Props) {
style={styles.stars}
/>
<View style={styles.usersContainer}>
<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}/>
{ users[1] && (<UserScore user={users[1]} score={10} maxScore={questionCount} isFirst={false}/>)}
<UserScore user={users[0]} score={10} maxScore={questionCount} isFirst={true}/>
{ users[2] &&(<UserScore user={users[2]} score={10} maxScore={questionCount} isFirst={false}/>)}
</View>
<EndQuizListPlayer users={[userTest, userTest]} maxScore={quiz.questionCount}/>
<EndQuizListPlayer users={users} maxScore={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>
......
......@@ -5,7 +5,7 @@ import HttpError from "./HttpError";
export const useMultiService = () => {
const url = "https://klebert-host.com:33037"
const createParty = async (quizId: string) : Promise<string | HttpError> => {
const createParty = async (quizId: string) : Promise<string | HttpError> => {
console.log("Create party with ID :", quizId);
console.log(url+"/party");
const requestBody = {
......@@ -29,8 +29,132 @@ export const useMultiService = () => {
}
}
const generateRun = async () : Promise<string | HttpError> => {
console.log("generateRun");
try {
const response = await axios.get(`${url}/party/generateRun`, {
withCredentials: true,
});
return response.data.runId;
} catch (error: any) {
console.log("Error while generate run:", 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 revealAnswer = async () : Promise<boolean | HttpError> => {
console.log("revealAnswer");
try {
const response = await axios.get(`${url}/party/revealAnswer`, {
withCredentials: true,
});
console.log("revealAnswer done");
return true;
} catch (error: any) {
console.log("Error while reveal Answer:", 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 answerQuestion = async (questionId: number, answerId: number) : Promise<boolean | HttpError> => {
console.log("answerQuestion");
const requestBody = {
questionId: questionId,
answerId: answerId,
};
try {
const response = await axios.post(`${url}/party/answer`, requestBody,{
withCredentials: true,
});
console.log("answerQuestion done");
return true;
} catch (error: any) {
console.log("Error while reveal Answer:", 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 nextQuestion = async () : Promise<boolean | HttpError> => {
console.log("nextQuestion");
try {
const response = await axios.get(`${url}/party/nextQuestion`, {
withCredentials: true,
});
console.log("nextQuestion done");
return true;
} catch (error: any) {
console.log("Error while nextQuestion:", 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 startParty = async () : Promise<boolean | HttpError> => {
console.log("startParty");
try {
const response = await axios.get(`${url}/party/start`, {
withCredentials: true,
});
console.log("startParty done");
return true;
} catch (error: any) {
console.log("Error while startParty:", 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 getScore = async () : Promise<number | HttpError> => {
console.log("getScore");
try {
const response = await axios.get(`${url}/party/score`, {
withCredentials: true,
});
console.log("getScore done");
return response.data.score;
} catch (error: any) {
console.log("Error while startParty:", 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,
generateRun: generateRun,
revealAnswer: revealAnswer,
nextQuestion: nextQuestion,
answerQuestion: answerQuestion,
startParty: startParty,
getScore: getScore
}
}
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment