diff --git a/backend/src/main/java/de/htwg_konstanz/mobilelearning/helper/moodle/MoodleInterface.java b/backend/src/main/java/de/htwg_konstanz/mobilelearning/helper/moodle/MoodleInterface.java index 8db219e1..2fbf36e5 100644 --- a/backend/src/main/java/de/htwg_konstanz/mobilelearning/helper/moodle/MoodleInterface.java +++ b/backend/src/main/java/de/htwg_konstanz/mobilelearning/helper/moodle/MoodleInterface.java @@ -1,5 +1,9 @@ package de.htwg_konstanz.mobilelearning.helper.moodle; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; @@ -7,9 +11,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.ArrayList; -import java.util.List; - /** * Moodle service that returns moodle courses of a user. */ @@ -35,13 +36,12 @@ public Boolean login() { // get the token from // https://moodle.htwg-konstanz.de/moodle/login/token.php?username=USERNAME&password=PASSWORD&service=SERVICESHORTNAME CloseableHttpClient client = HttpClients.createDefault(); - HttpGet request = new HttpGet("https://moodle.htwg-konstanz.de/moodle/login/token.php?username=" - + this.username + "&password=" + this.password + "&service=moodle_mobile_app"); + HttpGet request = new HttpGet("https://moodle.htwg-konstanz.de/moodle/login/token.php?username=" + this.username + "&password=" + URLEncoder.encode(this.password, "UTF-8") + "&service=moodle_mobile_app"); MoodleTokenResponse tokenResponse = mapper.readValue(client.execute(request).getEntity().getContent(), MoodleTokenResponse.class); this.token = tokenResponse.token; - // System.out.println("Successfully logged in as " + this.username + " with - // token " + this.token.substring(0, 5) + "..."); + System.out.println("Successfully logged in as " + this.username + " with token " + this.token.substring(0, 5) + "..."); + System.out.println(tokenResponse); // get user id String wsFunction = "core_webservice_get_site_info"; @@ -65,6 +65,7 @@ public Boolean login() { } catch (Exception e) { System.out.println("Error while logging into moodle: " + e.getMessage()); + System.out.println(e); return false; } @@ -82,7 +83,11 @@ public List getCourses() { // TEMP: mock the special users (Prof, Student, Admin) if (this.username.startsWith("Prof") || this.username.startsWith("Student") || this.username.startsWith("Admin")) { - return List.of(new MoodleCourse("1"), new MoodleCourse("2"), new MoodleCourse("3"), new MoodleCourse("940")); + List courses = new ArrayList(); + for (Integer i = 1; i <= 1000; i++) { + courses.add(new MoodleCourse(i.toString())); + } + return courses; } // if courses are already set return them if not fetch them diff --git a/backend/src/main/java/de/htwg_konstanz/mobilelearning/models/QuestionWrapper.java b/backend/src/main/java/de/htwg_konstanz/mobilelearning/models/QuestionWrapper.java index e320fb88..b37ab56f 100644 --- a/backend/src/main/java/de/htwg_konstanz/mobilelearning/models/QuestionWrapper.java +++ b/backend/src/main/java/de/htwg_konstanz/mobilelearning/models/QuestionWrapper.java @@ -48,9 +48,11 @@ public Boolean addResult(Result result) { // check if the user already submitted a result for (Result r : this.results) { if (r.hashedUserId != null && r.hashedUserId.equals(result.hashedUserId)) { + System.out.println("User already submitted a result for this question."); return false; } if (r.userId != null && r.userId.equals(result.userId)) { + System.out.println("User already submitted a result for this question."); return false; } } diff --git a/backend/src/main/java/de/htwg_konstanz/mobilelearning/models/quiz/QuizForm.java b/backend/src/main/java/de/htwg_konstanz/mobilelearning/models/quiz/QuizForm.java index e4dc9f26..3b10461f 100644 --- a/backend/src/main/java/de/htwg_konstanz/mobilelearning/models/quiz/QuizForm.java +++ b/backend/src/main/java/de/htwg_konstanz/mobilelearning/models/quiz/QuizForm.java @@ -471,7 +471,6 @@ public Integer getParticipantsAnsweredCorrectly(ObjectId id) { if (result.getGainedPoints() > 0) { count++; } - break; } break; } diff --git a/backend/src/main/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocket.java b/backend/src/main/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocket.java index ea9c5c33..00b82532 100644 --- a/backend/src/main/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocket.java +++ b/backend/src/main/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocket.java @@ -57,6 +57,10 @@ public class LiveQuizSocket { @Inject StatsService statsService; + private void log(String message) { + System.out.println("LiveQuizSocket: " + message); + } + /** * Method called when a new connection is opened. * User has to either (student & participant of the session) or owner of the @@ -81,6 +85,7 @@ public void onOpen( // userId from Jwt has to match userId from path if (!jwtService.getJwtClaims(jwt).getSubject().equals(userId)) { connections.remove(session.getId()); + log(String.format("User %s not authorized (jwt does not match userId)", userId)); session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "User not authorized")); return; } @@ -88,26 +93,26 @@ public void onOpen( // check if course, form and user exist Course course = courseRepository.findById(new ObjectId(courseId)); if (course == null) { - System.out.println("Course not found"); + log(String.format("Course %s not found", courseId)); session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "Course not found")); return; } QuizForm form = course.getQuizFormById(new ObjectId(formId)); if (form == null) { - System.out.println("Form not found"); + log(String.format("Form %s not found", formId)); session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "Form not found")); return; } User user = userRepository.findById(new ObjectId(userId)); if (user == null) { - System.out.println("User not found"); + log(String.format("User %s not found", userId)); session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "User not found")); return; } // check if user is student of the course if (!course.isStudent(userId) && !course.isOwner(userId)) { - System.out.println("User is not a student of the course"); + log(String.format("User %s is not a student of the course %s", user.getUsername(), course.getName())); session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "User is not a student of the course")); return; } @@ -116,7 +121,7 @@ public void onOpen( Boolean isParticipant = form.isParticipant(userId); Boolean isOwner = course.isOwner(userId); if (!isParticipant && !isOwner) { - System.out.println(String.format("User %s is not a participant or owner of the form", user.getUsername())); + log(String.format("User %s is not a participant or owner of the form", user.getUsername())); session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, String.format("User %s is not a participant or owner of the form", user.getUsername()))); return; } @@ -126,6 +131,7 @@ public void onOpen( // add the connection to the list SocketConnection socketMember = new SocketConnection(session, courseId, formId, userId, type); + log(String.format("User %s connected as %s", user.getUsername(), type)); connections.put(session.getId(), socketMember); // send a message to the owner to notify that a new participant has joined @@ -195,6 +201,7 @@ private void sendMessageToUser(User user, LiveQuizSocketMessage message) { @OnClose public void onClose(Session session, @PathParam("courseId") String courseId, @PathParam("formId") String formId, @PathParam("userId") String userId) { + log(String.format("User %s disconnected", userId)); connections.remove(session.getId()); } @@ -275,6 +282,7 @@ private void broadcast(LiveQuizSocketMessage message, String courseId, String fo // send the message String messageString = messageToSend.toJson(); + log("SEND: " + messageString); connection.session.getAsyncRemote().sendObject(messageString, result -> { if (result.getException() != null) { System.out.println("Unable to send message: " + result.getException()); @@ -287,20 +295,31 @@ private Boolean evaluateMessage(LiveQuizSocketMessage quizSocketMessage, String // evaluate action if (quizSocketMessage.action == null || quizSocketMessage.action.equals("")) { - System.out.println("Action is null"); + log("Action is invalid"); return false; } if (quizSocketMessage.action.equals("FUN")) { return this.fun(quizSocketMessage, formId); } + + log("RECEIVED: " + quizSocketMessage.toJson()); // if the user is not an owner or participant, return User user = userRepository.findById(new ObjectId(userId)); - if (user == null) { return false; } + if (user == null) { + log(String.format("User %s not found", userId)); + return false; + } Course course = courseRepository.findById(new ObjectId(courseId)); - if (course == null) { return false; } - if (!course.isOwner(userId) && !course.isStudent(userId)) { return false; } + if (course == null) { + log(String.format("Course %s not found", courseId)); + return false; + } + if (!course.isOwner(userId) && !course.isStudent(userId)) { + log(String.format("User %s is not a student or owner of the course", user.getUsername())); + return false; + } if (quizSocketMessage.action.equals("CHANGE_FORM_STATUS")) { return this.changeFormStatus(quizSocketMessage, course, formId, user); @@ -319,37 +338,36 @@ private Boolean evaluateMessage(LiveQuizSocketMessage quizSocketMessage, String private Boolean changeFormStatus(LiveQuizSocketMessage quizSocketMessage, Course course, String formId, User user) { - System.out.println("Change form status"); - // user must be owner of the course if (!course.isOwner(user.getId())) { - System.out.println("User is not owner of the course"); + log(String.format("User %s is not owner of the course", user.getUsername())); return false; } // evaluate formStatus if (quizSocketMessage.formStatus == null || quizSocketMessage.formStatus.equals("") || FormStatus.valueOf(quizSocketMessage.formStatus) == null) { - System.out.println("Form status is invalid"); + log(String.format("Form status %s is invalid", quizSocketMessage.formStatus)); return false; } // get the enum value of the formStatus FormStatus formStatusEnum = FormStatus.valueOf(quizSocketMessage.formStatus); - System.out.println("Form status enum: " + formStatusEnum); // get the form QuizForm form = course.getQuizFormById(new ObjectId(formId)); if (form == null) { - System.out.println("Form not found"); + log(String.format("Form %s not found", formId)); return false; } // change the form status form.setStatus(formStatusEnum); + log(String.format("Form status of form %s changed to %s", formId, formStatusEnum.toString())); // if it is set to NOT_STARTED, remove all results if (formStatusEnum == FormStatus.NOT_STARTED) { + log("Clear results and participants of quiz form"); form.clearResults(); form.clearParticipants(); form.currentQuestionIndex = 0; @@ -361,6 +379,7 @@ private Boolean changeFormStatus(LiveQuizSocketMessage quizSocketMessage, Course // if it is set to STARTED set the timestamp if (formStatusEnum == FormStatus.STARTED) { + log("Set start timestamp of quiz form"); form.setStartTimestamp(); } @@ -370,8 +389,7 @@ private Boolean changeFormStatus(LiveQuizSocketMessage quizSocketMessage, Course // update the userstats of the participants and the global stats if (formStatusEnum == FormStatus.FINISHED) { - System.out.println("Update user stats for quiz form"); - System.out.println(form.getParticipants().size()); + log("Update user stats and increment completed quiz forms"); this.userService.updateUserStatsByQuizForm(form); this.statsService.incrementCompletedQuizForms(); } @@ -385,25 +403,23 @@ private Boolean changeFormStatus(LiveQuizSocketMessage quizSocketMessage, Course private Boolean addResult(LiveQuizSocketMessage quizSocketMessage, Course course, String formId, User user) { - System.out.println("Add result"); - // evaluate resultElementId if (quizSocketMessage.resultElementId == null || quizSocketMessage.resultElementId.equals("")) { - System.out.println("Result questionwrapper ID is invalid"); + log(String.format("Result element ID %s is invalid", quizSocketMessage.resultElementId)); return false; } // evaluate resultValue if (quizSocketMessage.resultValues == null || quizSocketMessage.resultValues.size() < 1 || quizSocketMessage.resultValues.get(0).equals("")) { - System.out.println("Result value is invalid"); + log("Result values are invalid"); return false; } // get the form QuizForm form = course.getQuizFormById(new ObjectId(formId)); if (form == null) { - System.out.println("Form not found"); + log(String.format("Form %s not found", formId)); return false; } @@ -411,7 +427,7 @@ private Boolean addResult(LiveQuizSocketMessage quizSocketMessage, Course course System.out.println(quizSocketMessage.resultElementId); QuestionWrapper questionwrapper = form.getQuestionById(new ObjectId(quizSocketMessage.resultElementId)); if (questionwrapper == null) { - System.out.println("Element not found"); + log(String.format("Questionwrapper %s not found", quizSocketMessage.resultElementId)); return false; } @@ -420,14 +436,14 @@ private Boolean addResult(LiveQuizSocketMessage quizSocketMessage, Course course Result result = new Result(hashedUserId, quizSocketMessage.resultValues); Boolean wasResultAdded = questionwrapper.addResult(result); if (!wasResultAdded) { - System.out.println("Result was not added (user probably already submitted a result)"); + log(String.format("Result for user %s was not added", user.getUsername())); return false; } // check if the score of the user has to be updated QuizQuestion question = course.getQuizQuestionById(questionwrapper.getQuestionId()); if (question == null) { - System.out.println("Question not found"); + log(String.format("Question %s not found", questionwrapper.getQuestionId())); return false; } if (question.getHasCorrectAnswers()) { @@ -457,18 +473,16 @@ private Boolean addResult(LiveQuizSocketMessage quizSocketMessage, Course course private Boolean next(LiveQuizSocketMessage quizSocketMessage, Course course, String formId, User user) { - System.out.println("Next"); - // user must be owner of the course if (!course.isOwner(user.getId())) { - System.out.println("User is not owner of the course"); + log(String.format("User %s is not owner of the course", user.getUsername())); return false; } // get the form QuizForm form = course.getQuizFormById(new ObjectId(formId)); if (form == null) { - System.out.println("Form not found"); + log(String.format("Form %s not found", formId)); return false; } @@ -476,6 +490,9 @@ private Boolean next(LiveQuizSocketMessage quizSocketMessage, Course course, Str List events = form.next(); courseRepository.update(course); + log(String.format("NEXT produced events: %s", events)); + log(String.format("Current question index: %d; Current question finished: %b", form.currentQuestionIndex, form.currentQuestionFinished)); + // for all events, send a message events.forEach(event -> { LiveQuizSocketMessage outgoingMessage = new LiveQuizSocketMessage(event, form.status.toString(), null, null, form); @@ -486,8 +503,6 @@ private Boolean next(LiveQuizSocketMessage quizSocketMessage, Course course, Str } private Boolean fun(LiveQuizSocketMessage quizSocketMessage, String formId) { - - System.out.println("Throw paper plane"); quizSocketMessage = new LiveQuizSocketMessage(quizSocketMessage.action, quizSocketMessage.fun); diff --git a/backend/src/main/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocketMessage.java b/backend/src/main/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocketMessage.java index 8ed94e05..8db0acc1 100644 --- a/backend/src/main/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocketMessage.java +++ b/backend/src/main/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocketMessage.java @@ -6,6 +6,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; + import de.htwg_konstanz.mobilelearning.helper.ObjectIdTypeAdapter; import de.htwg_konstanz.mobilelearning.models.quiz.QuizForm; @@ -45,10 +46,10 @@ public LiveQuizSocketMessage(String message) { this.fun = quizSocketMessage.fun; this.form = null; - System.out.println("Action: " + this.action); - System.out.println("Form status: " + this.formStatus); - System.out.println("Result element ID: " + this.resultElementId); - System.out.println("Result value: " + this.resultValues); + // System.out.println("Action: " + this.action); + // System.out.println("Form status: " + this.formStatus); + // System.out.println("Result element ID: " + this.resultElementId); + // System.out.println("Result value: " + this.resultValues); } public LiveQuizSocketMessage(String action, Fun fun) { diff --git a/backend/src/test/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocketTest.java b/backend/src/test/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocketTest.java index 8dcae2c4..660eca05 100644 --- a/backend/src/test/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocketTest.java +++ b/backend/src/test/java/de/htwg_konstanz/mobilelearning/services/quiz/socket/LiveQuizSocketTest.java @@ -1,26 +1,27 @@ package de.htwg_konstanz.mobilelearning.services.quiz.socket; -import static io.restassured.RestAssured.given; - import java.io.IOException; import java.net.URI; -import java.util.List; +import java.util.ArrayList; import java.util.Date; +import java.util.List; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import de.htwg_konstanz.mobilelearning.Helper; -import de.htwg_konstanz.mobilelearning.SocketClient; import de.htwg_konstanz.mobilelearning.MockMongoTestProfile; import de.htwg_konstanz.mobilelearning.MockUser; +import de.htwg_konstanz.mobilelearning.SocketClient; import de.htwg_konstanz.mobilelearning.models.Course; import de.htwg_konstanz.mobilelearning.models.Result; import de.htwg_konstanz.mobilelearning.models.quiz.QuizForm; import de.htwg_konstanz.mobilelearning.services.CourseService; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; +import static io.restassured.RestAssured.given; import io.restassured.http.ContentType; import io.restassured.response.Response; import jakarta.inject.Inject; @@ -940,15 +941,17 @@ public void testScoreAndResult() throws DeploymentException, IOException, Interr List courses = Helper.createCourse("Prof-1"); Course course = courses.get(0); - // make 1 prof and 1 student + // make 1 prof and 3 students MockUser prof = Helper.createMockUser("Prof-1"); MockUser student1 = Helper.createMockUser("Student-1"); MockUser student2 = Helper.createMockUser("Student-2"); + MockUser student3 = Helper.createMockUser("Student-3"); // call the get courses endpoint for each user to update the course-user relation given().header("Authorization", "Bearer " + prof.getJwt()).when().get("/course").then().statusCode(200); given().header("Authorization", "Bearer " + student1.getJwt()).when().get("/course").then().statusCode(200); given().header("Authorization", "Bearer " + student2.getJwt()).when().get("/course").then().statusCode(200); + given().header("Authorization", "Bearer " + student3.getJwt()).when().get("/course").then().statusCode(200); // get course and quiz form id String courseId = course.getId().toString(); @@ -958,6 +961,7 @@ public void testScoreAndResult() throws DeploymentException, IOException, Interr SocketClient profClient = new SocketClient(); SocketClient studentClient1 = new SocketClient(); SocketClient studentClient2 = new SocketClient(); + SocketClient studentClient3 = new SocketClient(); // connect the prof to the quiz form Session profSession = ContainerProvider.getWebSocketContainer().connectToServer( @@ -1010,17 +1014,34 @@ public void testScoreAndResult() throws DeploymentException, IOException, Interr Thread.sleep(100); Assertions.assertTrue(studentSession2.isOpen()); + Response response3 = given() + .header("Authorization", "Bearer " + student3.getJwt()) + .pathParam("courseId", courseId) + .pathParam("formId", formId) + .body("alias-student-3") + .when() + .post("/course/{courseId}/quiz/form/{formId}/participate"); + Thread.sleep(100); + Assertions.assertEquals(200, response3.getStatusCode()); + Session studentSession3 = ContainerProvider.getWebSocketContainer().connectToServer( + studentClient3, + URI.create("ws://localhost:8081/course/" + courseId + "/quiz/form/" + formId + "/subscribe/" + student3.getId() + "/" + student3.getJwt()) + ); + Thread.sleep(100); + Assertions.assertTrue(studentSession3.isOpen()); // check if the quiz has now participants - Assertions.assertEquals(2, courseService.getCourse(courseId).getQuizForms().get(0).getParticipants().size()); + Assertions.assertEquals(3, courseService.getCourse(courseId).getQuizForms().get(0).getParticipants().size()); // check if the prof received the "PARTICIPANT_JOINED" messages and got the right amount of participants Assertions.assertEquals("PARTICIPANT_JOINED", LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 1)).action); - Assertions.assertEquals(2, LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 1)).form.participants.size()); + Assertions.assertEquals(3, LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 1)).form.participants.size()); Assertions.assertEquals("PARTICIPANT_JOINED", LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 2)).action); - Assertions.assertEquals(1, LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 2)).form.participants.size()); - Assertions.assertEquals("FORM_STATUS_CHANGED", LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 3)).action); - Assertions.assertEquals(0, LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 3)).form.participants.size()); + Assertions.assertEquals(2, LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 2)).form.participants.size()); + Assertions.assertEquals("PARTICIPANT_JOINED", LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 3)).action); + Assertions.assertEquals(1, LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 3)).form.participants.size()); + Assertions.assertEquals("FORM_STATUS_CHANGED", LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 4)).action); + Assertions.assertEquals(0, LiveQuizSocketMessage.getByJsonWithForm(profClient.getMessageQueue().get(profClient.getMessageQueue().size() - 4)).form.participants.size()); // change the form status to "STARTED" and check if it was set profClient.sendMessage(""" @@ -1052,16 +1073,26 @@ public void testScoreAndResult() throws DeploymentException, IOException, Interr } """.formatted(course.getQuizForms().get(0).questions.get(0).getId().toString())); Thread.sleep(100); + studentClient3.sendMessage(""" + { + "action": "ADD_RESULT", + "resultElementId": %s, + "resultValues": ["2"] + } + """.formatted(course.getQuizForms().get(0).questions.get(0).getId().toString())); + Thread.sleep(100); // check that the result was added - Assertions.assertEquals(2, courseService.getCourse(courseId).getQuizForms().get(0).questions.get(0).results.size()); + Assertions.assertEquals(3, courseService.getCourse(courseId).getQuizForms().get(0).questions.get(0).results.size()); Assertions.assertEquals("2", courseService.getCourse(courseId).getQuizForms().get(0).questions.get(0).results.get(0).values.get(0)); Assertions.assertEquals("2", courseService.getCourse(courseId).getQuizForms().get(0).questions.get(0).results.get(1).values.get(0)); + Assertions.assertEquals("2", courseService.getCourse(courseId).getQuizForms().get(0).questions.get(0).results.get(2).values.get(0)); // check that the score was updated Assertions.assertEquals(15, courseService.getCourse(courseId).getQuizForms().get(0).getParticipants().get(0).getScore()); Assertions.assertEquals(14, courseService.getCourse(courseId).getQuizForms().get(0).getParticipants().get(1).getScore()); + Assertions.assertEquals(13, courseService.getCourse(courseId).getQuizForms().get(0).getParticipants().get(2).getScore()); // let the prof stop the question profClient.sendMessage(""" @@ -1080,7 +1111,8 @@ public void testScoreAndResult() throws DeploymentException, IOException, Interr Assertions.assertTrue(LiveQuizSocketMessage.getByJsonWithForm(studentClient1.getMessageQueue().get(studentClient1.getMessageQueue().size() - 1)).userHasAnsweredCorrectly); Assertions.assertEquals("CLOSED_QUESTION", LiveQuizSocketMessage.getByJsonWithForm(studentClient2.getMessageQueue().get(studentClient2.getMessageQueue().size() - 1)).action); Assertions.assertTrue(LiveQuizSocketMessage.getByJsonWithForm(studentClient2.getMessageQueue().get(studentClient2.getMessageQueue().size() - 1)).userHasAnsweredCorrectly); - + Assertions.assertEquals("CLOSED_QUESTION", LiveQuizSocketMessage.getByJsonWithForm(studentClient3.getMessageQueue().get(studentClient3.getMessageQueue().size() - 1)).action); + Assertions.assertTrue(LiveQuizSocketMessage.getByJsonWithForm(studentClient3.getMessageQueue().get(studentClient3.getMessageQueue().size() - 1)).userHasAnsweredCorrectly); // let the prof start the next question profClient.sendMessage(""" @@ -1126,7 +1158,6 @@ public void testScoreAndResult() throws DeploymentException, IOException, Interr // close the websocket connections profSession.close(); studentSession1.close(); - } @Test @@ -1497,4 +1528,180 @@ public void testResultDownload() { } } -} + + @Test + public void stressTest() throws InterruptedException, DeploymentException, IOException { + + Integer AMOUNT_OF_STUDENTS = 50; + + // create & get course + List courses = Helper.createCourse(); + Course course = courses.get(0); + + // get course and quiz form id + String courseId = course.getId().toString(); + String formId = course.getQuizForms().get(0).getId().toString(); + + // create 1 prof and 500 students + MockUser prof = Helper.createMockUser("Prof"); + List students = new ArrayList<>(); + given().header("Authorization", "Bearer " + prof.getJwt()).when().get("/course").then().statusCode(200); + + for (int i = 0; i < AMOUNT_OF_STUDENTS; i++) { + MockUser student = Helper.createMockUser("Student" + i); + students.add(student); + + // call the get courses endpoint for each user to update the course-user relation + Thread.sleep(100); + given().header("Authorization", "Bearer " + student.getJwt()).when().get("/course").then().statusCode(200); + } + + // create a websocket client for the prof and the students + SocketClient profClient = new SocketClient(); + List studentClients = new ArrayList<>(); + for (MockUser student : students) { + SocketClient studentClient = new SocketClient(); + studentClients.add(studentClient); + Thread.sleep(100); + } + + // connect the prof to the quiz form + Session profSession = ContainerProvider.getWebSocketContainer().connectToServer( + profClient, + URI.create("ws://localhost:8081/course/" + courseId + "/quiz/form/" + formId + "/subscribe/" + prof.getId() + "/" + prof.getJwt()) + ); + Thread.sleep(100); + Assertions.assertTrue(profSession.isOpen()); + + // set the form status to "WAITING" and check if it was set + profClient.sendMessage(""" + { + "action": "CHANGE_FORM_STATUS", + "formStatus": "WAITING" + } + """); + Thread.sleep(100); + Assertions.assertEquals("WAITING", courseService.getCourse(courseId).getQuizForms().get(0).getStatus().toString()); + + Thread.sleep(1000); + + // make the participate requests for the students and connect them to the quiz form + for (int i = 0; i < AMOUNT_OF_STUDENTS; i++) { + given().header("Authorization", "Bearer " + students.get(i).getJwt()).pathParam("courseId", courseId).pathParam("formId", formId).body("alias-student-" + i).when().post("/course/{courseId}/quiz/form/{formId}/participate").then().statusCode(200); + Thread.sleep(100); + } + + Thread.sleep(5000); + + Assertions.assertEquals(AMOUNT_OF_STUDENTS, courseService.getCourse(courseId).getQuizForms().get(0).getParticipants().size()); + + // connect the students to the quiz form + List studentSessions = new ArrayList<>(); + for (int i = 0; i < AMOUNT_OF_STUDENTS; i++) { + Session studentSession = ContainerProvider.getWebSocketContainer().connectToServer( + studentClients.get(i), + URI.create("ws://localhost:8081/course/" + courseId + "/quiz/form/" + formId + "/subscribe/" + students.get(i).getId() + "/" + students.get(i).getJwt()) + ); + studentSessions.add(studentSession); + Thread.sleep(100); + Assertions.assertTrue(studentSession.isOpen()); + } + + // change the form status to "STARTED" and check if it was set + profClient.sendMessage(""" + { + "action": "CHANGE_FORM_STATUS", + "formStatus": "STARTED" + } + """); + Thread.sleep(5000); + Assertions.assertEquals("STARTED", courseService.getCourse(courseId).getQuizForms().get(0).getStatus().toString()); + + // make the students submit their answers + for (int i = 0; i < AMOUNT_OF_STUDENTS; i++) { + studentClients.get(i).sendMessage(""" + { + "action": "ADD_RESULT", + "resultElementId": %s, + "resultValues": ["2"] + } + """.formatted(course.getQuizForms().get(0).questions.get(0).getId().toString())); + Thread.sleep(100); + } + + Thread.sleep(15_000); + + // check that the results were added + Assertions.assertEquals(AMOUNT_OF_STUDENTS, courseService.getCourse(courseId).getQuizForms().get(0).getQuestions().get(0).getResults().size()); + + // check that the score was updated + for (int i = 0; i < AMOUNT_OF_STUDENTS; i++) { + Assertions.assertTrue(courseService.getCourse(courseId).getQuizForms().get(0).getParticipants().get(i).getScore() > 9); + } + + // let the prof stop the question + profClient.sendMessage(""" + { + "action": "NEXT" + } + """); + Thread.sleep(5000); + + // check that the currentQuestionIndex is 0 and the currentQuestionFinished is true + Assertions.assertEquals(0, courseService.getCourse(courseId).getQuizForms().get(0).getCurrentQuestionIndex()); + Assertions.assertTrue(courseService.getCourse(courseId).getQuizForms().get(0).getCurrentQuestionFinished()); + + // check that all students got the "CLOSED_QUESTION" message + for (int i = 0; i < AMOUNT_OF_STUDENTS; i++) { + Assertions.assertEquals("CLOSED_QUESTION", LiveQuizSocketMessage.getByJsonWithForm(studentClients.get(i).getMessageQueue().get(studentClients.get(i).getMessageQueue().size() - 1)).action); + Assertions.assertTrue(LiveQuizSocketMessage.getByJsonWithForm(studentClients.get(i).getMessageQueue().get(studentClients.get(i).getMessageQueue().size() - 1)).userHasAnsweredCorrectly); + } + + // let the prof start the next question + profClient.sendMessage(""" + { + "action": "NEXT" + } + """); + Thread.sleep(1000); + + // check that the students got the opened and closed question message and that it contains the correct result + for (int i = 0; i < AMOUNT_OF_STUDENTS; i++) { + // Assertions.assertEquals("CLOSED_QUESTION", LiveQuizSocketMessage.getByJsonWithForm(studentClients.get(i).getMessageQueue().get(studentClients.get(i).getMessageQueue().size() - 2)).action); + // Assertions.assertFalse(LiveQuizSocketMessage.getByJsonWithForm(studentClients.get(i).getMessageQueue().get(studentClients.get(i).getMessageQueue().size() - 2)).userHasAnsweredCorrectly); + Assertions.assertEquals("OPENED_NEXT_QUESTION", LiveQuizSocketMessage.getByJsonWithForm(studentClients.get(i).getMessageQueue().get(studentClients.get(i).getMessageQueue().size() - 1)).action); + } + + // add a wrong result to the quiz form + for (int i = 0; i < AMOUNT_OF_STUDENTS; i++) { + studentClients.get(i).sendMessage(""" + { + "action": "ADD_RESULT", + "resultElementId": %s, + "resultValues": ["1"] + } + """.formatted(course.getQuizForms().get(0).questions.get(1).getId().toString())); + Thread.sleep(100); + } + + Thread.sleep(5000); + + // check that the results were added + Assertions.assertEquals(AMOUNT_OF_STUDENTS, courseService.getCourse(courseId).getQuizForms().get(0).questions.get(1).results.size()); + Assertions.assertEquals("1", courseService.getCourse(courseId).getQuizForms().get(0).questions.get(1).results.get(0).values.get(0)); + + // check that the score was updated + for (int i = 0; i < AMOUNT_OF_STUDENTS; i++) { + Assertions.assertTrue(courseService.getCourse(courseId).getQuizForms().get(0).getParticipants().get(i).getScore() < 16); + } + + // close the websocket connections + profSession.close(); + for (int i = 0; i < AMOUNT_OF_STUDENTS; i++) { + studentSessions.get(i).close(); + } + + } + + +} \ No newline at end of file diff --git a/frontend/assets/animations/rive/animations.riv b/frontend/assets/animations/rive/animations.riv index 30878585..39011487 100644 Binary files a/frontend/assets/animations/rive/animations.riv and b/frontend/assets/animations/rive/animations.riv differ diff --git a/frontend/assets/icons/menu/meat.svg b/frontend/assets/icons/menu/meat.svg index 51019f20..5d93de2c 100644 --- a/frontend/assets/icons/menu/meat.svg +++ b/frontend/assets/icons/menu/meat.svg @@ -2,12 +2,15 @@ + + M0.8,54.4C0.8,44.8,21.1,33,21.1,33c1.3-0.8,27.2-16,52.8-16c7.2,0,17.7,2.9,24.5,6c10.3,4.7,20.7,17.4,20.7,26.7 + c0,12.1-22.9,23-41,23.7c-6.2,0.3-12.4,1.8-18.3,4.7c-15.5,7.6-34.9,7.5-46.1-0.4c-7.6-5.3-12.9-13.5-12.9-22V54.4z"/> diff --git a/frontend/lib/components/elements/quiz/single_choice_quiz_result.dart b/frontend/lib/components/elements/quiz/single_choice_quiz_result.dart index 9b5fb3b9..45774fa9 100644 --- a/frontend/lib/components/elements/quiz/single_choice_quiz_result.dart +++ b/frontend/lib/components/elements/quiz/single_choice_quiz_result.dart @@ -6,12 +6,14 @@ class SingleChoiceQuizResult extends StatefulWidget { final List results; final List options; final String correctAnswer; + final bool questionFinished; const SingleChoiceQuizResult({ super.key, required this.results, required this.options, required this.correctAnswer, + required this.questionFinished, }); @override @@ -88,9 +90,12 @@ class _SingleChoiceQuizResultState extends State { child: Text( optionDerivation.option, style: TextStyle( - color: correctAnswer ? Colors.green : colors.onBackground, - fontWeight: - correctAnswer ? FontWeight.bold : FontWeight.normal, + color: widget.questionFinished && correctAnswer + ? Colors.green + : colors.onBackground, + fontWeight: widget.questionFinished && correctAnswer + ? FontWeight.bold + : FontWeight.normal, fontSize: 20, ), ), @@ -103,8 +108,9 @@ class _SingleChoiceQuizResultState extends State { "${optionDerivation.count}", style: TextStyle( color: colors.onBackground, - fontWeight: - correctAnswer ? FontWeight.bold : FontWeight.normal, + fontWeight: widget.questionFinished && correctAnswer + ? FontWeight.bold + : FontWeight.normal, ), ), ), @@ -117,7 +123,9 @@ class _SingleChoiceQuizResultState extends State { value: optionDerivation.normalizedPercentage, backgroundColor: colors.secondary.withOpacity(0.1), valueColor: AlwaysStoppedAnimation( - correctAnswer ? Colors.green : colors.tertiary, + widget.questionFinished && correctAnswer + ? Colors.green + : colors.tertiary, ), ), ), diff --git a/frontend/lib/components/general/quiz/QuizScoreboard.dart b/frontend/lib/components/general/quiz/QuizScoreboard.dart index c49bdfbd..86d3bf92 100644 --- a/frontend/lib/components/general/quiz/QuizScoreboard.dart +++ b/frontend/lib/components/general/quiz/QuizScoreboard.dart @@ -2,10 +2,12 @@ import 'package:flutter/material.dart'; class QuizScoreboard extends StatefulWidget { final List scoreboard; + final String alias; const QuizScoreboard({ super.key, required this.scoreboard, + required this.alias, }); @override @@ -40,17 +42,33 @@ class _QuizScoreboardState extends State { physics: const NeverScrollableScrollPhysics(), itemCount: _scoreboard.length, itemBuilder: (BuildContext context, int index) { - return ListTile( - leading: Text( - _scoreboard[index]["rank"].toString(), - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20), - ), - title: Text(_scoreboard[index]["userAlias"]), - trailing: Text( - _scoreboard[index]["score"].toString(), - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20), - ), - ); + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.0), + border: (_scoreboard[index]["userAlias"] == widget.alias) + ? Border.all( + color: colors + .primary, // Change this color to whatever you want + width: 2.0, // Change this width to whatever you want + ) + : Border.all( + color: Colors + .transparent, // No border when condition is not met + ), + ), + child: ListTile( + leading: Text( + _scoreboard[index]["rank"].toString(), + style: + const TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), + title: Text(_scoreboard[index]["userAlias"]), + trailing: Text( + _scoreboard[index]["score"].toString(), + style: + const TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), + )); }, ); } diff --git a/frontend/lib/components/menu_card.dart b/frontend/lib/components/menu_card.dart index 9007135e..7a601465 100644 --- a/frontend/lib/components/menu_card.dart +++ b/frontend/lib/components/menu_card.dart @@ -74,7 +74,47 @@ class _MenuCardState extends State ? 'hin&weg' : widget.title == 'KombinierBar' ? 'Kombinierbar' - : 'Default', + : widget.title == 'Gemüse I' + ? 'vegetables' + : widget.title == 'Salat I' + ? 'salad i' + : widget.title == 'Salat II' + ? 'salad ii' + : widget.title == 'Dessert I' + ? 'dessert' + : widget.title == 'Pasta' + ? 'pasta meat' + : widget.title == + 'Pasta vegetarisch' + ? 'pasta veggie' + : widget.title == + 'koeriwerk' + ? 'currywurst' + : widget.description + .toLowerCase() + .contains( + 'penne') || + widget + .description + .toLowerCase() + .contains( + 'nudel') + ? 'pasta' + : widget.description.toLowerCase().contains( + 'potato') || + widget + .description + .toLowerCase() + .contains( + 'kartoffel') + ? 'potato' + : widget.description + .toLowerCase() + .contains('reis') + ? 'reis' + : widget.description.toLowerCase().contains('pommes') + ? 'pommes' + : 'Default', stateMachines: ['State Machine'], ), ), @@ -93,7 +133,7 @@ class _MenuCardState extends State ), ), SvgPicture.asset(widget.cardIcon, - width: 15, height: 15), + width: 20, height: 20), ], ), ), diff --git a/frontend/lib/pages/feedback/attend_feedback_page.dart b/frontend/lib/pages/feedback/attend_feedback_page.dart index 889ef288..40f7baec 100644 --- a/frontend/lib/pages/feedback/attend_feedback_page.dart +++ b/frontend/lib/pages/feedback/attend_feedback_page.dart @@ -124,6 +124,9 @@ class _AttendFeedbackPageState extends AuthState { case QuestionType.single_choice: _feedbackValues[element.id] = 0; break; + case QuestionType.fulltext: + _feedbackValues[element.id] = ""; + break; default: _feedbackValues[element.id] = 0; break; diff --git a/frontend/lib/pages/feedback/feedback_result_page.dart b/frontend/lib/pages/feedback/feedback_result_page.dart index 5000eab6..d9c1d96f 100644 --- a/frontend/lib/pages/feedback/feedback_result_page.dart +++ b/frontend/lib/pages/feedback/feedback_result_page.dart @@ -315,6 +315,11 @@ class _FeedbackResultPageState extends AuthState { ); } + var values = 0; + if (_results.isNotEmpty) { + values = _results[0]["values"].length; + } + return Scaffold( appBar: appbar, body: Stack( @@ -440,11 +445,30 @@ class _FeedbackResultPageState extends AuthState { right: 0, child: Container( color: colors.surfaceVariant, - child: Text( - "${_form.connectCode.substring(0, 3)} ${_form.connectCode.substring(3, 6)}", - style: Theme.of(context).textTheme.headlineSmall, - textAlign: TextAlign.center, - ), + child: Row(children: [ + Expanded( + child: Text( + "${_form.connectCode.substring(0, 3)} ${_form.connectCode.substring(3, 6)}", + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + )), + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("Abgestimmt:"), + const Icon(Icons.person), + Text( + "${values}/$_participantCounter", + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.right, + ), + ], + ), + )), + ]), ), ), ], diff --git a/frontend/lib/pages/quiz/attend_quiz_page.dart b/frontend/lib/pages/quiz/attend_quiz_page.dart index 4d2f120d..9c150792 100644 --- a/frontend/lib/pages/quiz/attend_quiz_page.dart +++ b/frontend/lib/pages/quiz/attend_quiz_page.dart @@ -405,6 +405,7 @@ class _AttendQuizPageState extends AuthState { } if (_fetchResult == 'success') { + final colors = Theme.of(context).colorScheme; final appBar = AppBar( title: const Text( 'Einem Quiz beitreten', //_form.name, TODO: find better solution @@ -515,7 +516,7 @@ class _AttendQuizPageState extends AuthState { child: Card( child: Padding( padding: const EdgeInsets.all(8), - child: QuizScoreboard(scoreboard: _scoreboard), + child: QuizScoreboard(scoreboard: _scoreboard, alias: _alias), ), ), ), @@ -567,139 +568,185 @@ class _AttendQuizPageState extends AuthState { return Scaffold( appBar: appBarWithProgress, - body: Column( - children: [ - Padding( - padding: - const EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0), - child: Column( - children: [ - const SizedBox(height: 16), - Text(element.name, - style: const TextStyle( - fontSize: 25, fontWeight: FontWeight.w700)), - Text(element.description, - style: const TextStyle( - fontSize: 20, fontWeight: FontWeight.w500), - textAlign: TextAlign.center), - const SizedBox(height: 16), - Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: element.type == QuestionType.single_choice - ? SingleChoiceQuiz( - correctAnswers: _correctAnswers, - currentQuestionFinished: - _form!.currentQuestionFinished, - voted: _voted, - value: _value, - options: element.options, - onSelectionChanged: (newValue) { - setState(() { - _value = newValue; - }); - }, + body: Stack(children: [ + SingleChildScrollView( + child: SizedBox( + width: double.infinity, + child: Column( + children: [ + const SizedBox(height: 32), + Padding( + padding: const EdgeInsets.all(16), + child: Container( + constraints: const BoxConstraints(maxWidth: 800), + child: Column( + children: [ + Text(element.name, + style: const TextStyle( + fontSize: 25, + fontWeight: FontWeight.w700)), + Text(element.description, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500), + textAlign: TextAlign.center), + const SizedBox(height: 16), + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: element.type == + QuestionType.single_choice + ? SingleChoiceQuiz( + correctAnswers: _correctAnswers, + currentQuestionFinished: + _form!.currentQuestionFinished, + voted: _voted, + value: _value, + options: element.options, + onSelectionChanged: (newValue) { + setState(() { + _value = newValue; + }); + }, + ) + : element.type == QuestionType.yes_no + ? YesNoQuiz( + correctAnswers: _correctAnswers, + currentQuestionFinished: _form! + .currentQuestionFinished, + voted: _voted, + value: _value, + onSelectionChanged: (newValue) { + setState(() { + _value = newValue; + }); + }, + ) + : Text(element.type.toString()), + ), + ), + ], + ), + )), + if (!_form!.currentQuestionFinished && _voted) + Container( + margin: const EdgeInsets.only( + top: 0.0, + bottom: + 10.0), // specify the top and bottom margin + + width: 130, + height: 130, + child: RiveAnimation.direct( + widget.riveFile!, + fit: BoxFit.cover, + artboard: 'true & false', + stateMachines: ['tf State Machine'], + onInit: _onRiveInit, + )), + if (_form!.currentQuestionFinished && _voted) + Container( + margin: const EdgeInsets.only( + top: 0.0, + bottom: 10.0), // specify the top and bottom margin + + width: 130, + height: 130, + child: RiveAnimation.direct( + widget.riveFile!, + fit: BoxFit.cover, + artboard: 'true & false', + stateMachines: ['tf State Machine'], + onInit: _onRiveInit, + )), + if (_form!.currentQuestionFinished && !_voted) + Container( + margin: const EdgeInsets.only( + top: 0.0, + bottom: 10.0), // specify the top and bottom margin + + width: 130, + height: 130, + child: RiveAnimation.direct( + widget.riveFile!, + fit: BoxFit.cover, + artboard: 'true & false', + animations: ['nicht abgestimmt'], + )), + // if the user gained point, show them + if (_form!.currentQuestionFinished && + _voted && + _userHasAnsweredCorrectly) + Text( + "Du hast $_gainedPoints Punkte erhalten.", + style: const TextStyle( + fontSize: 20, fontWeight: FontWeight.w700), + ), + const SizedBox(height: 10), + (!_form!.currentQuestionFinished && !_voted) + ? SizedBox( + height: 55, + width: buttonWidth, + child: ElevatedButton( + style: ButtonStyle( + elevation: + MaterialStateProperty.all(6.0), + shape: + MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14.0), + ), + ), + backgroundColor: MaterialStateProperty.all< + Color>( + Theme.of(context).colorScheme.surface), + foregroundColor: MaterialStateProperty.all< + Color>( + Theme.of(context).colorScheme.primary), + ), + child: const Text('Senden', + style: TextStyle(fontSize: 20)), + onPressed: () { + if (_value == null) { + return; + } + var message = { + "action": "ADD_RESULT", + "resultElementId": element.id, + "resultValues": [_value], + "role": "STUDENT" + }; + _socketChannel?.sink.add(jsonEncode(message)); + setState(() { + _voted = true; + }); + }, + ), ) - : element.type == QuestionType.yes_no - ? YesNoQuiz( - correctAnswers: _correctAnswers, - currentQuestionFinished: - _form!.currentQuestionFinished, - voted: _voted, - value: _value, - onSelectionChanged: (newValue) { - setState(() { - _value = newValue; - }); - }, - ) - : Text(element.type.toString()), + : Container(), + + const SizedBox(height: 20), + ], + ))), + Positioned( + top: 0, + left: 0, + right: 0, + child: Container( + color: colors.surfaceVariant, + child: Row( + children: [ + Expanded( + child: Text( + "Dein Alias: ${_alias}", + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), ), - ), - ], - ), - ), - if (!_form!.currentQuestionFinished && _voted) - Container( - margin: const EdgeInsets.only( - top: 0.0, - bottom: 10.0), // specify the top and bottom margin - - width: 130, - height: 130, - child: RiveAnimation.direct( - widget.riveFile!, - fit: BoxFit.cover, - artboard: 'true & false', - stateMachines: ['tf State Machine'], - onInit: _onRiveInit, - )), - if (_form!.currentQuestionFinished && _voted) - Container( - margin: const EdgeInsets.only( - top: 0.0, - bottom: 10.0), // specify the top and bottom margin - - width: 130, - height: 130, - child: RiveAnimation.direct( - widget.riveFile!, - fit: BoxFit.cover, - artboard: 'true & false', - stateMachines: ['tf State Machine'], - onInit: _onRiveInit, - )), - if (_form!.currentQuestionFinished && !_voted) - Container( - margin: const EdgeInsets.only( - top: 0.0, - bottom: 10.0), // specify the top and bottom margin - - width: 130, - height: 130, - child: RiveAnimation.direct( - widget.riveFile!, - fit: BoxFit.cover, - artboard: 'true & false', - animations: ['nicht abgestimmt'], - )), - // if the user gained point, show them - if (_form!.currentQuestionFinished && - _voted && - _userHasAnsweredCorrectly) - Text( - "Du hast $_gainedPoints Punkte erhalten.", - style: - const TextStyle(fontSize: 20, fontWeight: FontWeight.w700), - ), - ], - ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: (!_form!.currentQuestionFinished && !_voted) - ? SizedBox( - width: buttonWidth, - child: FloatingActionButton( - backgroundColor: Color(0xFFEDF5F3), - foregroundColor: Theme.of(context).colorScheme.primary, - child: const Text('Senden', style: TextStyle(fontSize: 20)), - onPressed: () { - if (_value == null) { - return; - } - var message = { - "action": "ADD_RESULT", - "resultElementId": element.id, - "resultValues": [_value], - "role": "STUDENT" - }; - _socketChannel?.sink.add(jsonEncode(message)); - setState(() { - _voted = true; - }); - }, + ], ), - ) - : null, + )), + ]), ); } else { _showErrorDialog(_fetchResult); diff --git a/frontend/lib/pages/quiz/quiz_control_page.dart b/frontend/lib/pages/quiz/quiz_control_page.dart index 3140c6df..b2f6e4cb 100644 --- a/frontend/lib/pages/quiz/quiz_control_page.dart +++ b/frontend/lib/pages/quiz/quiz_control_page.dart @@ -541,7 +541,7 @@ class _QuizControlPageState extends AuthState { child: Card( child: Padding( padding: const EdgeInsets.all(8), - child: QuizScoreboard(scoreboard: _scoreboard), + child: QuizScoreboard(scoreboard: _scoreboard, alias: ""), ), ), ), @@ -604,52 +604,66 @@ class _QuizControlPageState extends AuthState { fontSize: 20, fontWeight: FontWeight.w500), textAlign: TextAlign.center), const SizedBox(height: 16), - if (_form.currentQuestionFinished == true) - if (_showLeaderboard) - Center( - child: Container( - constraints: - const BoxConstraints(maxWidth: 800), - child: Card( - child: Padding( - padding: const EdgeInsets.all(8), - child: QuizScoreboard( - scoreboard: _scoreboard), - ), - ), - )) - else - Card( + if (_showLeaderboard) + Center( + child: Container( + constraints: + const BoxConstraints(maxWidth: 800), + child: Card( child: Padding( - padding: const EdgeInsets.all(16), - child: element.type == - QuestionType.single_choice - ? SingleChoiceQuizResult( - results: values - .map((e) => int.parse(e)) - .toList() - .cast(), - options: element.options, - correctAnswer: - element.correctAnswers[0], - ) - : element.type == QuestionType.yes_no - ? SingleChoiceQuizResult( - results: values - .map((e) => - e == "yes" ? 0 : 1) - .toList() - .cast(), - options: const ["Ja", "Nein"], - correctAnswer: - element.correctAnswers[0] == - "yes" - ? "0" - : "1", - ) - : Text(element.type.toString()), + padding: const EdgeInsets.all(8), + child: QuizScoreboard( + scoreboard: _scoreboard, + alias: "", + ), ), ), + )) + else + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: element.type == + QuestionType.single_choice + ? SingleChoiceQuizResult( + results: + (_form.currentQuestionFinished == + true) + ? values + .map( + (e) => int.parse(e)) + .toList() + .cast() + : [], + options: element.options, + questionFinished: _form.currentQuestionFinished, + correctAnswer: + element.correctAnswers[0], + ) + : element.type == QuestionType.yes_no + ? SingleChoiceQuizResult( + results: + (_form.currentQuestionFinished == + true) + ? values + .map((e) => + e == "yes" + ? 0 + : 1) + .toList() + .cast() + : [], + options: const ["Ja", "Nein"], + questionFinished: _form.currentQuestionFinished, + correctAnswer: + element.correctAnswers[0] == + "yes" + ? "0" + : "1", + ) + : Text(element.type.toString()), + ), + ), ], ), ), @@ -699,9 +713,6 @@ class _QuizControlPageState extends AuthState { color: colors.surfaceVariant, child: Row( children: [ - Expanded( - child: Container(), // Empty container to take up space - ), Expanded( child: Text( "${_form.connectCode.substring(0, 3)} ${_form.connectCode.substring(3, 6)}", @@ -713,8 +724,9 @@ class _QuizControlPageState extends AuthState { child: Padding( padding: const EdgeInsets.only(right: 8.0), child: Row( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.center, children: [ + Text("Abgestimmt:"), const Icon(Icons.person), Text( "${values.length}/$_participantCounter", diff --git a/frontend/lib/tabs/live_tab.dart b/frontend/lib/tabs/live_tab.dart index f5ae425f..da7132c2 100644 --- a/frontend/lib/tabs/live_tab.dart +++ b/frontend/lib/tabs/live_tab.dart @@ -67,10 +67,10 @@ class _LiveTabState extends State { _fetchResult = ''; }); // fetch forms every 5 seconds - Timer.periodic(const Duration(seconds: 5), (timer) { + Timer.periodic(const Duration(seconds: 1), (timer) { if (!mounted) { timer.cancel(); - } else { + } else if (ModalRoute.of(context)!.isCurrent) { fetchForms(); } }); diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index 811b3cc8..2ee1e4c5 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -574,5 +574,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/sonstiges/menu/arrows.svg b/sonstiges/menu/arrows.svg new file mode 100644 index 00000000..e92a4bcf --- /dev/null +++ b/sonstiges/menu/arrows.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/sonstiges/menu/currywurst.svg b/sonstiges/menu/currywurst.svg new file mode 100644 index 00000000..acd12f33 --- /dev/null +++ b/sonstiges/menu/currywurst.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sonstiges/menu/dessert.svg b/sonstiges/menu/dessert.svg new file mode 100644 index 00000000..521616f3 --- /dev/null +++ b/sonstiges/menu/dessert.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + diff --git a/sonstiges/menu/hin&weg.svg b/sonstiges/menu/hin&weg.svg new file mode 100644 index 00000000..f40dbb60 --- /dev/null +++ b/sonstiges/menu/hin&weg.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sonstiges/menu/kombinierbar.svg b/sonstiges/menu/kombinierbar.svg new file mode 100644 index 00000000..a3cdd4c4 --- /dev/null +++ b/sonstiges/menu/kombinierbar.svg @@ -0,0 +1,64 @@ + + + + + + + + + + diff --git a/sonstiges/menu/pasta.svg b/sonstiges/menu/pasta.svg new file mode 100644 index 00000000..1b27255d --- /dev/null +++ b/sonstiges/menu/pasta.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/sonstiges/menu/pasta_meat.svg b/sonstiges/menu/pasta_meat.svg new file mode 100644 index 00000000..17a7862d --- /dev/null +++ b/sonstiges/menu/pasta_meat.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/sonstiges/menu/pasta_veggie.svg b/sonstiges/menu/pasta_veggie.svg new file mode 100644 index 00000000..a41690bc --- /dev/null +++ b/sonstiges/menu/pasta_veggie.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + diff --git a/sonstiges/menu/plate.svg b/sonstiges/menu/plate.svg new file mode 100644 index 00000000..88bdc4a0 --- /dev/null +++ b/sonstiges/menu/plate.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + diff --git a/sonstiges/menu/potato.svg b/sonstiges/menu/potato.svg new file mode 100644 index 00000000..9177dcb9 --- /dev/null +++ b/sonstiges/menu/potato.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + diff --git a/sonstiges/menu/saettigung.svg b/sonstiges/menu/saettigung.svg new file mode 100644 index 00000000..5274f883 --- /dev/null +++ b/sonstiges/menu/saettigung.svg @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/sonstiges/menu/saettigung2.svg b/sonstiges/menu/saettigung2.svg new file mode 100644 index 00000000..92ded5e2 --- /dev/null +++ b/sonstiges/menu/saettigung2.svg @@ -0,0 +1,41 @@ + + + + + + + + diff --git a/sonstiges/menu/salad.svg b/sonstiges/menu/salad.svg new file mode 100644 index 00000000..fccffcc0 --- /dev/null +++ b/sonstiges/menu/salad.svg @@ -0,0 +1,40 @@ + + + + + + + + + + diff --git a/sonstiges/menu/salad2.svg b/sonstiges/menu/salad2.svg new file mode 100644 index 00000000..f8e530ab --- /dev/null +++ b/sonstiges/menu/salad2.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + diff --git a/sonstiges/menu/vegetables.svg b/sonstiges/menu/vegetables.svg new file mode 100644 index 00000000..2bee1706 --- /dev/null +++ b/sonstiges/menu/vegetables.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + +