From b7fb61fe40935c32be6ed5d0b1e34d569bac77c0 Mon Sep 17 00:00:00 2001 From: Rosyid Date: Thu, 21 Mar 2024 16:15:55 +0700 Subject: [PATCH 1/5] update navigation question --- app/ios/Gemfile | 3 + app/ios/Gemfile.lock | 217 +++++++++++++ app/ios/fastlane/Appfile | 8 + app/ios/fastlane/Fastfile | 33 ++ .../bloc/quiz_exercise_cubit.dart | 228 ++++++++------ .../bloc/quiz_exercise_state.dart | 18 +- .../presentation/model/answer.dart | 7 +- .../presentation/model/questions.dart | 9 +- .../model/quiz_exercise_answer.dart | 6 +- .../model/quiz_exercise_attempt.dart | 3 +- .../pages/quiz_exercise_page.dart | 13 +- .../presentation/pages/task_dialog.dart | 101 +++--- .../presentation/pages/task_view.dart | 287 +++++++++++------- .../repositories/quiz_exercise.dart | 66 +--- app/lib/models/quiz_participation.dart | 35 ++- app/pubspec.lock | 26 +- 16 files changed, 700 insertions(+), 360 deletions(-) create mode 100644 app/ios/Gemfile create mode 100644 app/ios/Gemfile.lock create mode 100644 app/ios/fastlane/Appfile create mode 100644 app/ios/fastlane/Fastfile diff --git a/app/ios/Gemfile b/app/ios/Gemfile new file mode 100644 index 00000000..7a118b49 --- /dev/null +++ b/app/ios/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/app/ios/Gemfile.lock b/app/ios/Gemfile.lock new file mode 100644 index 00000000..96df74f3 --- /dev/null +++ b/app/ios/Gemfile.lock @@ -0,0 +1,217 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.6) + rexml + addressable (2.8.5) + public_suffix (>= 2.0.2, < 6.0) + artifactory (3.0.15) + atomos (0.1.3) + aws-eventstream (1.2.0) + aws-partitions (1.848.0) + aws-sdk-core (3.186.0) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.5) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.72.0) + aws-sdk-core (~> 3, >= 3.184.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.136.0) + aws-sdk-core (~> 3, >= 3.181.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.6) + aws-sigv4 (1.6.1) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + declarative (0.0.20) + digest-crc (0.6.5) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.104.0) + faraday (1.10.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) + fastimage (2.2.7) + fastlane (2.216.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (~> 0.1.1) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.52.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.2) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + webrick + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.29.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.6.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.3.1) + google-cloud-storage (1.45.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.29.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.5) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.6.2) + json (2.6.3) + jwt (2.7.1) + mini_magick (4.12.0) + mini_mime (1.1.5) + multi_json (1.15.0) + multipart-post (2.3.0) + nanaimo (0.3.0) + naturally (2.2.1) + optparse (0.1.1) + os (1.1.4) + plist (3.7.0) + public_suffix (5.0.3) + rake (13.1.0) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.6) + rouge (2.0.7) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + security (0.1.3) + signet (0.18.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.9) + unicode-display_width (2.5.0) + webrick (1.8.1) + word_wrap (1.0.0) + xcodeproj (1.23.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + arm64-darwin-23 + +DEPENDENCIES + fastlane + +BUNDLED WITH + 2.4.10 diff --git a/app/ios/fastlane/Appfile b/app/ios/fastlane/Appfile new file mode 100644 index 00000000..569a5559 --- /dev/null +++ b/app/ios/fastlane/Appfile @@ -0,0 +1,8 @@ +app_identifier("com.toki.bebrasPandai") # The bundle identifier of your app +apple_id("arrosyidbh@gmail.com") # Your Apple Developer Portal username + +itc_team_id("120270185") # App Store Connect Team ID +team_id("Y4R2BD2VKJ") # Developer Portal Team ID + +# For more information about the Appfile, see: +# https://docs.fastlane.tools/advanced/#appfile diff --git a/app/ios/fastlane/Fastfile b/app/ios/fastlane/Fastfile new file mode 100644 index 00000000..a8b53d04 --- /dev/null +++ b/app/ios/fastlane/Fastfile @@ -0,0 +1,33 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:ios) + +platform :ios do + desc "Push a new beta build to TestFlight" + lane :beta do + get_certificates + get_provisioning_profile + # sync_code_signing + disable_automatic_code_signing(path: "Runner.xcodeproj") + increment_build_number + build_app( + # skip_build_archive: true, + archive_path: "../build/ios/archive/Runner.xcarchive", + ) + enable_automatic_code_signing(path: "Runner.xcodeproj") + upload_to_testflight + end +end diff --git a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart index 2b149604..48dd5adc 100644 --- a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart +++ b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart @@ -20,14 +20,13 @@ class QuizExerciseCubit extends Cubit { late WeeklyQuiz quiz; late WeeklyQuizParticipation participation; - late QuizExercise currentProblem; int currentProblemIndex = 0; late List problemIdList; + late List problemList; + late List answerList; late QuizExerciseAttempt attempt; String? quizParticipantId; - String selectedAnswer = ''; - String shortAnswer = ''; late int remainingDuration; Timer? timer; @@ -65,28 +64,29 @@ class QuizExerciseCubit extends Cubit { // Shuffle Problem List problemIdList.shuffle(); + // reset index every time this method is called + currentProblemIndex = 0; + + // Fetch all quiz data + problemList = await quizExerciseRepository.getListQuizExercise( + taskIds: problemIdList); + // TODO(someone): fix the check logic later // if (weeklyQuizParticipant.attempts.isEmpty) { + answerList = problemList + .map((e) => QuizExerciseAnswer( + taskId: e.id, + correctAnswer: e.answer.correctAnswer, + answer: '', + taskChallengeGroup: e.challengeGroup)) + .toList(); attempt = QuizExerciseAttempt( startAt: DateTime.now(), totalBlank: problemIdList.length, totalCorrect: 0, totalIncorrect: 0, - answers: [], ); - // reset index every time this method is called - currentProblemIndex = 0; - - // Fetch all quiz data for it to be available when offline - await quizExerciseRepository - .getListQuizExerciseByTaskIdList(problemIdList); - - currentProblem = - await quizExerciseRepository.getQuizExercise(problemIdList.first); - - currentProblem.question.options?.shuffle(); - final duration = quiz.duration_minute[participation.challenge_group]; if (duration == null) { throw Exception('Duration for selected Challenge Group not found'); @@ -96,10 +96,12 @@ class QuizExerciseCubit extends Cubit { emit( QuizExerciseShow( quiz: quiz, - quizExercise: currentProblem, + quizExercise: problemList[currentProblemIndex], remainingDuration: Duration(seconds: remainingDuration), - selectedAnswer: selectedAnswer, - shortAnswer: shortAnswer, + answer: answerList[currentProblemIndex], + attempt: attempt, + currentProblemIndex: currentProblemIndex, + totalProblem: problemIdList.length, ), ); } catch (e) { @@ -108,13 +110,31 @@ class QuizExerciseCubit extends Cubit { } void selectAnswer(String answerId) { - selectedAnswer = answerId; + answerList[currentProblemIndex].answer = answerId; + emit( + QuizExerciseShow( + quiz: quiz, + quizExercise: problemList[currentProblemIndex], + remainingDuration: Duration(seconds: remainingDuration), + answer: answerList[currentProblemIndex], + attempt: attempt, + currentProblemIndex: currentProblemIndex, + totalProblem: problemIdList.length, + ), + ); + } + + void fillAnswer(String answer) { + answerList[currentProblemIndex].answer = answer; emit( QuizExerciseShow( quiz: quiz, - quizExercise: currentProblem, + quizExercise: problemList[currentProblemIndex], remainingDuration: Duration(seconds: remainingDuration), - selectedAnswer: selectedAnswer, + attempt: attempt, + answer: answerList[currentProblemIndex], + currentProblemIndex: currentProblemIndex, + totalProblem: problemIdList.length, ), ); } @@ -131,104 +151,112 @@ class QuizExerciseCubit extends Cubit { } } - void fillAnswer(String answer) { - shortAnswer = answer; - - emit( - QuizExerciseShow( - quiz: quiz, - quizExercise: currentProblem, - remainingDuration: Duration(seconds: remainingDuration), - shortAnswer: shortAnswer, - ), - ); - } - Future finishExerciseTimeUp() async { await postQuizExerciseAttempt(); emit(QuizExerciseFinished(quizParticipantId!)); } Future submitAnswer() async { - if (((currentProblem.type == 'MULTIPLE_CHOICE' || - currentProblem.type == 'MULTIPLE_CHOICE_IMAGE') && - selectedAnswer == '') || - (currentProblem.type == 'SHORT_ANSWER') && shortAnswer == '') { + var currentProblem = problemList[currentProblemIndex]; + var answer = answerList[currentProblemIndex].answer; + if (answer == '') { emit( QuizExerciseShow( quiz: quiz, quizExercise: currentProblem, remainingDuration: Duration(seconds: remainingDuration), - selectedAnswer: selectedAnswer, - shortAnswer: shortAnswer, + attempt: attempt, + answer: answerList[currentProblemIndex], modalErrorMessage: currentProblem.type == 'SHORT_ANSWER' ? 'Isi jawaban anda' : 'Pilih salah satu jawaban', + currentProblemIndex: currentProblemIndex, + totalProblem: problemIdList.length, ), ); return; } - try { - var verdict = 'INCORRECT'; - - if (currentProblem.type == 'MULTIPLE_CHOICE' || - currentProblem.type == 'MULTIPLE_CHOICE_IMAGE' && - currentProblem.answer.correctAnswer.contains(selectedAnswer)) { - verdict = 'CORRECT'; - } - - if (currentProblem.type == 'SHORT_ANSWER' && - currentProblem.answer.correctAnswer - .map((answer) => answer.toLowerCase()) - .contains(shortAnswer.trim().toLowerCase())) { - verdict = 'CORRECT'; - } - attempt.answers?.add( - QuizExerciseAnswer( - answer: currentProblem.type == 'SHORT_ANSWER' - ? shortAnswer - : selectedAnswer, - correctAnswer: currentProblem.answer.correctAnswer, - taskChallengeGroup: currentProblem.challengeGroup, - taskId: currentProblem.id, - verdict: verdict, - ), - ); + var verdict = 'INCORRECT'; + if (currentProblem.type == 'MULTIPLE_CHOICE' || + currentProblem.type == 'MULTIPLE_CHOICE_IMAGE' && + currentProblem.answer.correctAnswer.contains(answer)) { + verdict = 'CORRECT'; + } + if (currentProblem.type == 'SHORT_ANSWER' && + currentProblem.answer.correctAnswer + .map((answer) => answer.toLowerCase()) + .contains(answer.trim().toLowerCase())) { + verdict = 'CORRECT'; + } + answerList[currentProblemIndex].verdict = verdict; - if (verdict == 'CORRECT') { - attempt.totalCorrect++; - attempt.totalBlank--; - } else { - attempt.totalIncorrect++; - attempt.totalBlank--; - } + attempt.totalBlank = answerList.where((e) => e.verdict == null).length; + attempt.totalCorrect = answerList + .where((e) => e.verdict != null && e.verdict == 'CORRECT') + .length; + attempt.totalIncorrect = answerList + .where((e) => e.verdict == null && e.verdict == 'INCORRECT') + .length; + if (currentProblemIndex < problemIdList.length) { currentProblemIndex++; + } - if (currentProblemIndex < problemIdList.length) { - currentProblem = await quizExerciseRepository - .getQuizExercise(problemIdList[currentProblemIndex]); - selectedAnswer = ''; - shortAnswer = ''; - emit( - QuizExerciseShow( - quiz: quiz, - quizExercise: currentProblem, - remainingDuration: Duration(seconds: remainingDuration), - selectedAnswer: selectedAnswer, - shortAnswer: shortAnswer, - ), - ); - } else { - await postQuizExerciseAttempt(); - emit(QuizExerciseFinished(quizParticipantId!)); - } + emit( + QuizExerciseShow( + quiz: quiz, + quizExercise: problemList[currentProblemIndex], + remainingDuration: Duration(seconds: remainingDuration), + attempt: attempt, + answer: answerList[currentProblemIndex], + currentProblemIndex: currentProblemIndex, + totalProblem: problemIdList.length, + ), + ); + } + + Future finishExercise() async { + try { + await postQuizExerciseAttempt(); + emit(QuizExerciseFinished(quizParticipantId!)); } catch (e) { emit(QuizExerciseFailed(e.toString())); } } + void toNextQuestion() { + if (currentProblemIndex == problemIdList.length - 1) return; + currentProblemIndex++; + emit( + QuizExerciseShow( + quiz: quiz, + quizExercise: problemList[currentProblemIndex], + remainingDuration: Duration(seconds: remainingDuration), + attempt: attempt, + answer: answerList[currentProblemIndex], + currentProblemIndex: currentProblemIndex, + totalProblem: problemIdList.length, + ), + ); + } + + void toPreviousQuestion() { + if (currentProblemIndex == 0) return; + currentProblemIndex--; + emit( + QuizExerciseShow( + quiz: quiz, + quizExercise: problemList[currentProblemIndex], + remainingDuration: Duration(seconds: remainingDuration), + attempt: attempt, + answer: answerList[currentProblemIndex], + currentProblemIndex: currentProblemIndex, + totalProblem: problemIdList.length, + ), + ); + } + Future postQuizExerciseAttempt() async { attempt.score = (attempt.totalCorrect / (attempt.totalCorrect + @@ -250,10 +278,12 @@ class QuizExerciseCubit extends Cubit { emit( QuizExerciseShow( quiz: quiz, - quizExercise: currentProblem, + quizExercise: problemList[currentProblemIndex], remainingDuration: Duration(seconds: remainingDuration), - selectedAnswer: selectedAnswer, - shortAnswer: shortAnswer, + attempt: attempt, + answer: answerList[currentProblemIndex], + currentProblemIndex: currentProblemIndex, + totalProblem: problemIdList.length, ), ); } else { @@ -272,10 +302,12 @@ class QuizExerciseCubit extends Cubit { emit( QuizExerciseShow( quiz: quiz, - quizExercise: currentProblem, + quizExercise: problemList[currentProblemIndex], remainingDuration: Duration(seconds: remainingDuration), - selectedAnswer: selectedAnswer, - shortAnswer: shortAnswer, + attempt: attempt, + answer: answerList[currentProblemIndex], + currentProblemIndex: currentProblemIndex, + totalProblem: problemIdList.length, ), ); } diff --git a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_state.dart b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_state.dart index 9d0ef3fd..4a2d409b 100644 --- a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_state.dart +++ b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_state.dart @@ -14,17 +14,21 @@ class QuizExerciseLoading extends QuizExerciseState {} class QuizExerciseShow extends QuizExerciseState { final QuizExercise quizExercise; final WeeklyQuiz quiz; + final QuizExerciseAnswer answer; + final QuizExerciseAttempt attempt; final Duration remainingDuration; - final String selectedAnswer; - final String shortAnswer; final String modalErrorMessage; + final int currentProblemIndex; + final int totalProblem; const QuizExerciseShow({ required this.quiz, required this.quizExercise, required this.remainingDuration, - this.shortAnswer = '', - this.selectedAnswer = '', + required this.currentProblemIndex, + required this.totalProblem, + required this.attempt, + required this.answer, this.modalErrorMessage = '', }); @@ -33,12 +37,14 @@ class QuizExerciseShow extends QuizExerciseState { quiz, quizExercise, remainingDuration, - selectedAnswer, - shortAnswer, + answer, + attempt, modalErrorMessage, ]; } +class QuizExerciseFinalization extends QuizExerciseState {} + class QuizExerciseFinished extends QuizExerciseState { final String quizParticipantId; const QuizExerciseFinished(this.quizParticipantId); diff --git a/app/lib/features/quiz_exercise/presentation/model/answer.dart b/app/lib/features/quiz_exercise/presentation/model/answer.dart index f5d5f623..0f494438 100644 --- a/app/lib/features/quiz_exercise/presentation/model/answer.dart +++ b/app/lib/features/quiz_exercise/presentation/model/answer.dart @@ -13,11 +13,12 @@ class Answer { }); factory Answer.fromJson(Map json) { + var correctAnswer = json['correct_answer']; return Answer( aspect: Aspect.fromJson(json['aspect'] as Map), - correctAnswer: (json['correct_answer'] as List) - .map((e) => e.toString()) - .toList(), + correctAnswer: correctAnswer == null + ? [] + : (correctAnswer as List).map((e) => e.toString()).toList(), explanation: Explanation.fromJson(json['explanation'] as Map), ); diff --git a/app/lib/features/quiz_exercise/presentation/model/questions.dart b/app/lib/features/quiz_exercise/presentation/model/questions.dart index 663890ba..6ab76684 100644 --- a/app/lib/features/quiz_exercise/presentation/model/questions.dart +++ b/app/lib/features/quiz_exercise/presentation/model/questions.dart @@ -2,15 +2,16 @@ import 'options.dart'; class Question { final String content; - final List? options; + final List options; - Question({required this.content, this.options}); + Question({required this.content, required this.options}); factory Question.fromJson(Map json) { + var options = json['options']; return Question( content: json['content'] as String, - options: json['options'] != null - ? (json['options'] as List) + options: options != null + ? (options as List) .map((e) => Options.fromJson(e as Map)) .toList() : [], diff --git a/app/lib/features/quiz_exercise/presentation/model/quiz_exercise_answer.dart b/app/lib/features/quiz_exercise/presentation/model/quiz_exercise_answer.dart index ceeece91..b2a51a92 100644 --- a/app/lib/features/quiz_exercise/presentation/model/quiz_exercise_answer.dart +++ b/app/lib/features/quiz_exercise/presentation/model/quiz_exercise_answer.dart @@ -3,14 +3,14 @@ class QuizExerciseAnswer { List correctAnswer; String taskChallengeGroup; String taskId; - String verdict; + String? verdict; QuizExerciseAnswer({ + required this.taskId, required this.answer, required this.correctAnswer, required this.taskChallengeGroup, - required this.taskId, - required this.verdict, + this.verdict, }); Map toJson() { diff --git a/app/lib/features/quiz_exercise/presentation/model/quiz_exercise_attempt.dart b/app/lib/features/quiz_exercise/presentation/model/quiz_exercise_attempt.dart index 1f23aeca..171e798e 100644 --- a/app/lib/features/quiz_exercise/presentation/model/quiz_exercise_attempt.dart +++ b/app/lib/features/quiz_exercise/presentation/model/quiz_exercise_attempt.dart @@ -36,7 +36,8 @@ class QuizExerciseAttempt { totalCorrect: json['n_answer_correct'] as int, totalIncorrect: json['n_answer_incorrect'] as int, score: json['score'] as int, - answers: json['answers'] as List, + answers: + json['answers'] == null ? [] : json['answers'] as List, ); Map toJson() { diff --git a/app/lib/features/quiz_exercise/presentation/pages/quiz_exercise_page.dart b/app/lib/features/quiz_exercise/presentation/pages/quiz_exercise_page.dart index a5f1ead0..0b3edc3c 100644 --- a/app/lib/features/quiz_exercise/presentation/pages/quiz_exercise_page.dart +++ b/app/lib/features/quiz_exercise/presentation/pages/quiz_exercise_page.dart @@ -81,9 +81,13 @@ class _QuizExercisePageState extends State { task: state.quizExercise, context: context, remainingDuration: state.remainingDuration, - onTap: () { - onTaskTap(); + attempt: state.attempt, + onTaskTap: () { + onAnswerTap(); }, + showPreviousButton: state.currentProblemIndex > 0, + showNextButton: state.currentProblemIndex < + (state.totalProblem - 1), ); } return Container(); @@ -97,7 +101,7 @@ class _QuizExercisePageState extends State { ); } - void onTaskTap() { + void onAnswerTap() { showDialog( context: context, builder: (context) { @@ -109,8 +113,7 @@ class _QuizExercisePageState extends State { return TaskDialog( task: state.quizExercise, preview: false, - shortAnswer: state.shortAnswer, - selectedAnswer: state.selectedAnswer, + answer: state.answer.answer, error: state.modalErrorMessage, ); } diff --git a/app/lib/features/quiz_exercise/presentation/pages/task_dialog.dart b/app/lib/features/quiz_exercise/presentation/pages/task_dialog.dart index 5e1f374a..6be7730b 100644 --- a/app/lib/features/quiz_exercise/presentation/pages/task_dialog.dart +++ b/app/lib/features/quiz_exercise/presentation/pages/task_dialog.dart @@ -10,8 +10,7 @@ import '../bloc/quiz_exercise_cubit.dart'; import '../model/quiz_exercise.dart'; class TaskDialog extends StatelessWidget { - final String? shortAnswer; - final String? selectedAnswer; + final String? answer; final String? error; final bool preview; final QuizExercise task; @@ -19,8 +18,7 @@ class TaskDialog extends StatelessWidget { required this.task, required this.preview, super.key, - this.shortAnswer, - this.selectedAnswer, + this.answer, this.error, }); @@ -48,52 +46,50 @@ class TaskDialog extends StatelessWidget { // ), // ), // if (!preview) - ...task.question.options!.asMap().entries.map((e) { - final current = String.fromCharCode(65 + e.key); + ...task.question.options!.asMap().entries.map((e) { + final current = String.fromCharCode(65 + e.key); - return RadioListTile( - title: SizedBox( - child: Transform.translate( - offset: const Offset( - -20, - 0, - ), // Set the desired offset - child: Row( - children: [ - Text('$current. '), - task.type == 'MULTIPLE_CHOICE_IMAGE' - ? - Image.network( - e.value.content.replaceAll(Assets.sourceImg, Assets.urlImg), - width: MediaQuery.of(context).size.width - 240, - ) - : Flexible( - child: HtmlWithCachedImages( - data:e.value.content - ), - ) - ], - ), + return RadioListTile( + title: SizedBox( + child: Transform.translate( + offset: const Offset( + -20, + 0, + ), // Set the desired offset + child: Row( + children: [ + Text('$current. '), + task.type == 'MULTIPLE_CHOICE_IMAGE' + ? Image.network( + e.value.content.replaceAll( + Assets.sourceImg, Assets.urlImg), + width: + MediaQuery.of(context).size.width - 240, + ) + : Flexible( + child: HtmlWithCachedImages( + data: e.value.content), + ) + ], ), ), - value: e.value.id, - groupValue: selectedAnswer, - onChanged: (value) { - context - .read() - .selectAnswer(e.value.id); - }, - ); - }), + ), + value: e.value.id, + groupValue: answer, + onChanged: (value) { + context.read().selectAnswer(e.value.id); + }, + ); + }), // if (!preview) - task.type == 'SHORT_ANSWER' - ? Container( - padding: const EdgeInsets.only(top: 20), - child: CustomTextField('Jawaban anda', (value) { - context.read().fillAnswer(value); - }, (p0) => null, ''), - ) - : Container(), + task.type == 'SHORT_ANSWER' + ? Container( + padding: const EdgeInsets.only(top: 20), + child: CustomTextField('Jawaban anda', (value) { + context.read().fillAnswer(value); + }, (p0) => null, ''), + ) + : Container(), if (!preview) if (error != null) Text(error!), ], @@ -111,7 +107,6 @@ class TaskDialog extends StatelessWidget { }, ), ), - if (!preview) SizedBox( width: 100, @@ -122,11 +117,11 @@ class TaskDialog extends StatelessWidget { onTap: () { var error = ''; if (task.type == 'SHORT_ANSWER') { - if (shortAnswer == '') { + if (answer == '') { error = 'Isi jawaban anda'; } } else { - if (selectedAnswer == '') { + if (answer == '') { error = 'Pilih salah satu jawaban'; } } @@ -136,12 +131,8 @@ class TaskDialog extends StatelessWidget { backgroundColor: Colors.red, duration: const Duration(seconds: 1), behavior: SnackBarBehavior.floating, - margin: - const EdgeInsets.only( - bottom: 50, - left: 10, - right: 10 - ), + margin: const EdgeInsets.only( + bottom: 50, left: 10, right: 10), content: Text(error), ); diff --git a/app/lib/features/quiz_exercise/presentation/pages/task_view.dart b/app/lib/features/quiz_exercise/presentation/pages/task_view.dart index 3e51f911..5316ca50 100644 --- a/app/lib/features/quiz_exercise/presentation/pages/task_view.dart +++ b/app/lib/features/quiz_exercise/presentation/pages/task_view.dart @@ -9,140 +9,213 @@ import '../../../../core/constants/assets.dart'; import '../../../../core/theme/font_theme.dart'; import '../bloc/quiz_exercise_cubit.dart'; import '../model/quiz_exercise.dart'; +import '../model/quiz_exercise_attempt.dart'; class TaskView extends StatelessWidget { final Duration? remainingDuration; final QuizExercise task; + final QuizExerciseAttempt attempt; final BuildContext context; - final Function onTap; + final Function onTaskTap; + final bool showPreviousButton; + final bool showNextButton; const TaskView({ required this.task, required this.context, - required this.onTap, + required this.onTaskTap, + required this.attempt, super.key, + this.showPreviousButton = false, + this.showNextButton = false, this.remainingDuration, }); Future _showExitConfirmationDialog(BuildContext context) async { return await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Anda akan keluar dari latihan?'), - content: const Text('Latihan akan dianggap selesai dengan hasil seadanya'), - actions: [ - SizedBox( - width: 100, - height: 50, - child: Button( - buttonType: ButtonType.secondary, - text: 'Cancel', - onTap: () { - Navigator.of(context).pop(false); - }, - ), + context: context, + builder: (context) => AlertDialog( + title: const Text('Anda akan keluar dari latihan?'), + content: const Text( + 'Latihan akan dianggap selesai dengan hasil seadanya'), + actions: [ + SizedBox( + width: 100, + height: 50, + child: Button( + buttonType: ButtonType.secondary, + text: 'Cancel', + onTap: () { + Navigator.of(context).pop(false); + }, + ), + ), + SizedBox( + width: 100, + height: 50, + child: Button( + buttonType: ButtonType.tertiary, + text: 'Ya', + onTap: () { + context + .read() + .finishExerciseTimeUp(); + Navigator.of(context).pop(true); + })) + ], ), - SizedBox( - width: 100, - height: 50, - child: Button( - buttonType: ButtonType.tertiary, - text: 'Ya', - onTap: () { - context.read().finishExerciseTimeUp(); - Navigator.of(context).pop(true); - } - ) - ) - ], - ), - ) ?? false; + ) ?? + false; } @override Widget build(BuildContext context) { return WillPopScope( - onWillPop: () async { - // Display the popup when the back button is pressed - final result = await _showExitConfirmationDialog(context); - return result; - }, - child: Column( - children: [ - if (remainingDuration != null) + onWillPop: () async { + // Display the popup when the back button is pressed + final result = await _showExitConfirmationDialog(context); + return result; + }, + child: Column( + children: [ + if (remainingDuration != null) + Container( + color: Colors.yellowAccent, + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Text( + '${remainingDuration!.inMinutes.toString().padLeft(2, '0')} : ${remainingDuration!.inSeconds.remainder(60).toString().padLeft(2, '0')}'), + ), + if (remainingDuration != null) + const SizedBox( + height: 10, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset( + '${Assets.flagDir}${task.country}.png', + width: 40, + height: 20, + ), + Flexible( + child: Text( + task.title, + textAlign: TextAlign.center, + style: FontTheme.blackSubtitleBold(), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + task.id, + style: const TextStyle(fontSize: 9), + ), + Text( + task.source, + style: const TextStyle(fontSize: 7), + ), + ], + ), + ], + ), Container( - color: Colors.yellowAccent, - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), - child: Text( - '${remainingDuration!.inMinutes.toString().padLeft(2, '0')} : ${remainingDuration!.inSeconds.remainder(60).toString().padLeft(2, '0')}'), + height: 2, + color: Colors.black, ), - if (remainingDuration != null) const SizedBox( - height: 10, + height: 3, ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Image.asset( - '${Assets.flagDir}${task.country}.png', - width: 40, - height: 20, - ), - Flexible( - child: Text( - task.title, - textAlign: TextAlign.center, - style: FontTheme.blackSubtitleBold(), + Container( + height: MediaQuery.of(context).size.height - 340, + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration(border: Border.all()), + child: SingleChildScrollView( + child: HtmlWithCachedImages( + // ignore: prefer_adjacent_string_concatenation + // data: '

Deskripsi

${task.description.content}' + + // '

Pertanyaan

${task.question.content}', + data: task.description.content + .replaceAll(Assets.sourceImg, Assets.urlImg), ), ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - task.id, - style: const TextStyle(fontSize: 9), - ), - Text( - task.source, - style: const TextStyle(fontSize: 7), - ), - ], - ), - ], - ), - Container( - height: 2, - color: Colors.black, - ), - const SizedBox( - height: 3, - ), - Container( - height: MediaQuery.of(context).size.height - 340, - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration(border: Border.all()), - child: SingleChildScrollView( - child: HtmlWithCachedImages( - // ignore: prefer_adjacent_string_concatenation - // data: '

Deskripsi

${task.description.content}' + - // '

Pertanyaan

${task.question.content}', - data: task.description.content.replaceAll(Assets.sourceImg, Assets.urlImg), - ), ), - ), - const SizedBox( - height: 14, - ), - SizedBox( - width: 150, - child: Button( - text: 'JAWAB', - buttonType: ButtonType.primary, - onTap: onTap, + const SizedBox( + height: 14, ), - ), - ], - ) - ); + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 50, + child: Column( + children: [ + if (showPreviousButton) + Button( + innerHorizontalPadding: 4, + innerVerticalPadding: 8, + text: '<', + buttonType: ButtonType.primary, + onTap: () { + context + .read() + .toPreviousQuestion(); + }, + ), + ], + )), + Column( + children: [ + SizedBox( + width: 150, + child: Button( + innerHorizontalPadding: 4, + innerVerticalPadding: 8, + text: 'JAWAB', + buttonType: ButtonType.primary, + onTap: onTaskTap, + ), + ), + if (attempt.totalBlank == 0) + SizedBox( + height: 12, + ), + if (attempt.totalBlank == 0) + SizedBox( + width: 150, + child: Button( + innerHorizontalPadding: 4, + innerVerticalPadding: 8, + text: 'FINISH', + buttonType: ButtonType.tertiary, + onTap: () { + context.read().toNextQuestion(); + }, + ), + ), + ], + ), + SizedBox( + width: 50, + child: Column( + children: [ + if (showNextButton) + Button( + innerHorizontalPadding: 4, + innerVerticalPadding: 8, + text: '>', + buttonType: ButtonType.primary, + onTap: () { + context + .read() + .toNextQuestion(); + }, + ), + ], + )), + ], + ), + ], + )); } } diff --git a/app/lib/features/quiz_exercise/presentation/repositories/quiz_exercise.dart b/app/lib/features/quiz_exercise/presentation/repositories/quiz_exercise.dart index 00a49152..1ad330cf 100644 --- a/app/lib/features/quiz_exercise/presentation/repositories/quiz_exercise.dart +++ b/app/lib/features/quiz_exercise/presentation/repositories/quiz_exercise.dart @@ -14,42 +14,19 @@ class QuizExerciseRepository { db.settings = FirebaseService.settings; } - Future> getListQuizExerciseByTaskIdList( - List taskIdList, - ) async { + Future> getListQuizExercise( + {List? taskIds}) async { final quizExerciseList = []; try { - final result = await db - .collection('task_set') - .where(FieldPath.documentId, whereIn: taskIdList) - .get(); - for (final element in result.docs) { - quizExerciseList.add(QuizExercise.fromJson(element.data())); - } - - return quizExerciseList; - } on FirebaseException catch (e) { - if (kDebugMode) { - print("Failed with error '${e.code}': '${e.message}'"); + if (taskIds == null) { + final globalResult = + await db.collection('configuration').doc('global_variables').get(); + final taskSet = globalResult.get('task_set_doc_index') as List; + taskIds = taskSet.map((e) => e['doc_id'] as String) as List; } - return quizExerciseList; - } catch (e) { - throw Exception(e.toString()); - } - } - Future> getListQuizExercise() async { - final quizExerciseList = []; - try { - final globalResult = - await db.collection('configuration').doc('global_variables').get(); - final taskSet = globalResult.get('task_set_doc_index') as List; - final taskIds = taskSet.map((e) => e['doc_id'] as String); - - final taskListResult = await db - .collection('task_set') - .where(FieldPath.documentId, whereIn: taskIds) - .get(); + final taskListResult = + await db.collection('task_set').where('id', whereIn: taskIds).get(); for (final element in taskListResult.docs) { quizExerciseList.add(QuizExercise.fromJson(element.data())); } @@ -73,7 +50,7 @@ class QuizExerciseRepository { final taskSet = globalResult.get('task_set_doc_$group') as List; for (final element in taskSet) { - quizExerciseBaseList + quizExerciseBaseList .add(QuizExerciseBase.fromJson(element as Map)); } @@ -88,16 +65,6 @@ class QuizExerciseRepository { } } - Future> getListQuizExerciseNew() async { - final quizExerciseBaseList = []; - try { - - return quizExerciseBaseList; - } catch (e) { - throw Exception(e.toString()); - } - } - Future getQuizExercise(String taskId) async { try { final result = @@ -131,8 +98,8 @@ class QuizExerciseRepository { } Future updateQuizExercise( - String taskId, - String? status, + String taskId, + String? status, ) async { try { // Update Status Task Set @@ -140,8 +107,8 @@ class QuizExerciseRepository { .collection('task_set') .doc(taskId) .update({ - 'status': status, - }); + 'status': status, + }); // Dapatkan referensi ke dokumen yang akan diperbarui final globalResult = FirebaseFirestore.instance @@ -172,10 +139,9 @@ class QuizExerciseRepository { // Update Status Configuration await globalResult.update({ 'task_set_doc_index': taskSet, - 'weeklyquiz_number' : weeklyQuizNumber + 'weeklyquiz_number': weeklyQuizNumber }); - - } catch (e) { + } catch (e) { throw Exception(e.toString()); } } diff --git a/app/lib/models/quiz_participation.dart b/app/lib/models/quiz_participation.dart index 25cc8fb1..511c8701 100644 --- a/app/lib/models/quiz_participation.dart +++ b/app/lib/models/quiz_participation.dart @@ -32,21 +32,26 @@ class WeeklyQuizParticipation extends Equatable { factory WeeklyQuizParticipation.fromJson( String id, Map json, - ) => - WeeklyQuizParticipation( - id: id, - quiz_start_at: json['quiz_start_at'] as String, - quiz_title: json['quiz_title'] as String, - user_name: json['user_name'] as String, - user_uid: json['user_uid'] as String, - challenge_group: json['challenge_group'] as String, - quiz_end_at: json['quiz_end_at'] as String, - quiz_id: json['quiz_id'] as String, - quiz_max_attempts: json['quiz_max_attempts'] as int, - attempts: (json['attempts'] as List) - .map((i) => QuizExerciseAttempt.fromJson(i as Map)) - .toList(), - ); + ) { + var attempts = json['attempts']; + return WeeklyQuizParticipation( + id: id, + quiz_start_at: json['quiz_start_at'] as String, + quiz_title: json['quiz_title'] as String, + user_name: json['user_name'] as String, + user_uid: json['user_uid'] as String, + challenge_group: json['challenge_group'] as String, + quiz_end_at: json['quiz_end_at'] as String, + quiz_id: json['quiz_id'] as String, + quiz_max_attempts: json['quiz_max_attempts'] as int, + attempts: attempts == null + ? [] + : (attempts as List) + .map((i) => + QuizExerciseAttempt.fromJson(i as Map)) + .toList(), + ); + } @override List get props => [ diff --git a/app/pubspec.lock b/app/pubspec.lock index 28904c97..a0c970ce 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -253,10 +253,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.2" convert: dependency: transitive description: @@ -924,10 +924,10 @@ packages: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" mime: dependency: transitive description: @@ -1313,18 +1313,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" stream_transform: dependency: transitive description: @@ -1361,10 +1361,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.6.0" timezone: dependency: transitive description: @@ -1513,10 +1513,10 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -1558,5 +1558,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" + dart: ">=3.1.0 <4.0.0" flutter: ">=3.13.0" From 3782b13e494af5cbd8dd2d25638a00303ad414b4 Mon Sep 17 00:00:00 2001 From: Muktazam Hasbi Ashidiqi Date: Tue, 26 Mar 2024 14:51:59 +0700 Subject: [PATCH 2/5] fix: quiz: filter problems only short answer, multiple choice and multiple choice image --- .../presentation/bloc/quiz_exercise_cubit.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart index 48dd5adc..51e95ab2 100644 --- a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart +++ b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart @@ -70,6 +70,12 @@ class QuizExerciseCubit extends Cubit { // Fetch all quiz data problemList = await quizExerciseRepository.getListQuizExercise( taskIds: problemIdList); + problemList = problemList + .where((quizExercise) => + quizExercise.type == 'MULTIPLE_CHOICE' || + quizExercise.type == 'MULTIPLE_CHOICE_IMAGE' || + quizExercise.type == 'SHORT_ANSWER') + .toList(); // TODO(someone): fix the check logic later // if (weeklyQuizParticipant.attempts.isEmpty) { @@ -157,8 +163,8 @@ class QuizExerciseCubit extends Cubit { } Future submitAnswer() async { - var currentProblem = problemList[currentProblemIndex]; - var answer = answerList[currentProblemIndex].answer; + final currentProblem = problemList[currentProblemIndex]; + final answer = answerList[currentProblemIndex].answer; if (answer == '') { emit( QuizExerciseShow( From 39e7068aa8b06597aa69113046047b7b212af0dc Mon Sep 17 00:00:00 2001 From: Muktazam Hasbi Ashidiqi Date: Tue, 26 Mar 2024 16:44:02 +0700 Subject: [PATCH 3/5] fix: quiz: show save answer for short andwer type --- .../presentation/bloc/quiz_exercise_cubit.dart | 4 ++++ .../presentation/pages/task_dialog.dart | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart index 51e95ab2..10442c80 100644 --- a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart +++ b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart @@ -77,6 +77,10 @@ class QuizExerciseCubit extends Cubit { quizExercise.type == 'SHORT_ANSWER') .toList(); + problemIdList = problemList + .map((quizExercise) => quizExercise.id) // Extracting ids + .toList(); + // TODO(someone): fix the check logic later // if (weeklyQuizParticipant.attempts.isEmpty) { answerList = problemList diff --git a/app/lib/features/quiz_exercise/presentation/pages/task_dialog.dart b/app/lib/features/quiz_exercise/presentation/pages/task_dialog.dart index 6be7730b..5cdc436d 100644 --- a/app/lib/features/quiz_exercise/presentation/pages/task_dialog.dart +++ b/app/lib/features/quiz_exercise/presentation/pages/task_dialog.dart @@ -46,7 +46,7 @@ class TaskDialog extends StatelessWidget { // ), // ), // if (!preview) - ...task.question.options!.asMap().entries.map((e) { + ...task.question.options.asMap().entries.map((e) { final current = String.fromCharCode(65 + e.key); return RadioListTile( @@ -85,9 +85,14 @@ class TaskDialog extends StatelessWidget { task.type == 'SHORT_ANSWER' ? Container( padding: const EdgeInsets.only(top: 20), - child: CustomTextField('Jawaban anda', (value) { - context.read().fillAnswer(value); - }, (p0) => null, ''), + child: CustomTextField( + 'Jawaban anda', + (value) { + context.read().fillAnswer(value); + }, + (p0) => null, + answer, + ), ) : Container(), if (!preview) From d0a3eb6fa1a6abd94d046188bfa28426aafb5c40 Mon Sep 17 00:00:00 2001 From: Muktazam Hasbi Ashidiqi Date: Mon, 1 Apr 2024 13:57:46 +0700 Subject: [PATCH 4/5] fix: quiz: show finish button --- .../presentation/bloc/quiz_exercise_cubit.dart | 16 +--------------- .../presentation/pages/task_view.dart | 4 ++-- .../Flutter/GeneratedPluginRegistrant.swift | 2 ++ 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart index 10442c80..0ceabed4 100644 --- a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart +++ b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart @@ -209,21 +209,7 @@ class QuizExerciseCubit extends Cubit { .where((e) => e.verdict == null && e.verdict == 'INCORRECT') .length; - if (currentProblemIndex < problemIdList.length) { - currentProblemIndex++; - } - - emit( - QuizExerciseShow( - quiz: quiz, - quizExercise: problemList[currentProblemIndex], - remainingDuration: Duration(seconds: remainingDuration), - attempt: attempt, - answer: answerList[currentProblemIndex], - currentProblemIndex: currentProblemIndex, - totalProblem: problemIdList.length, - ), - ); + toNextQuestion(); } Future finishExercise() async { diff --git a/app/lib/features/quiz_exercise/presentation/pages/task_view.dart b/app/lib/features/quiz_exercise/presentation/pages/task_view.dart index 5316ca50..397b7831 100644 --- a/app/lib/features/quiz_exercise/presentation/pages/task_view.dart +++ b/app/lib/features/quiz_exercise/presentation/pages/task_view.dart @@ -177,7 +177,7 @@ class TaskView extends StatelessWidget { ), ), if (attempt.totalBlank == 0) - SizedBox( + const SizedBox( height: 12, ), if (attempt.totalBlank == 0) @@ -189,7 +189,7 @@ class TaskView extends StatelessWidget { text: 'FINISH', buttonType: ButtonType.tertiary, onTap: () { - context.read().toNextQuestion(); + context.read().finishExercise(); }, ), ), diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift index 1ad35f2a..1e112358 100644 --- a/app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,6 +12,7 @@ import firebase_messaging import firebase_storage import flutter_local_notifications import geolocator_apple +import google_sign_in_ios import path_provider_foundation import printing import shared_preferences_foundation @@ -26,6 +27,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) + FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) From 243c358422e222f36c20dce900485d0bd6e8b25f Mon Sep 17 00:00:00 2001 From: Muktazam Hasbi Ashidiqi Date: Mon, 1 Apr 2024 15:14:05 +0700 Subject: [PATCH 5/5] fix: quiz: calculate attempt --- .../presentation/bloc/quiz_exercise_cubit.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart index 0ceabed4..eca50b64 100644 --- a/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart +++ b/app/lib/features/quiz_exercise/presentation/bloc/quiz_exercise_cubit.dart @@ -188,9 +188,9 @@ class QuizExerciseCubit extends Cubit { } var verdict = 'INCORRECT'; - if (currentProblem.type == 'MULTIPLE_CHOICE' || - currentProblem.type == 'MULTIPLE_CHOICE_IMAGE' && - currentProblem.answer.correctAnswer.contains(answer)) { + if ((currentProblem.type == 'MULTIPLE_CHOICE' || + currentProblem.type == 'MULTIPLE_CHOICE_IMAGE') && + currentProblem.answer.correctAnswer.contains(answer)) { verdict = 'CORRECT'; } if (currentProblem.type == 'SHORT_ANSWER' && @@ -206,7 +206,7 @@ class QuizExerciseCubit extends Cubit { .where((e) => e.verdict != null && e.verdict == 'CORRECT') .length; attempt.totalIncorrect = answerList - .where((e) => e.verdict == null && e.verdict == 'INCORRECT') + .where((e) => e.verdict != null && e.verdict == 'INCORRECT') .length; toNextQuestion();