diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index c791205..c9f278f 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -17,6 +17,8 @@ import 'package:quiz_app/feature/quiz_play/binding/quiz_play_binding.dart'; import 'package:quiz_app/feature/quiz_play/view/quiz_play_view.dart'; import 'package:quiz_app/feature/quiz_preview/binding/quiz_preview_binding.dart'; import 'package:quiz_app/feature/quiz_preview/view/quiz_preview.dart'; +import 'package:quiz_app/feature/quiz_result/binding/quiz_result_binding.dart'; +import 'package:quiz_app/feature/quiz_result/view/quiz_result_view.dart'; import 'package:quiz_app/feature/register/binding/register_binding.dart'; import 'package:quiz_app/feature/register/view/register_page.dart'; import 'package:quiz_app/feature/search/binding/search_binding.dart'; @@ -78,6 +80,11 @@ class AppPages { name: AppRoutes.playQuizPage, page: () => QuizPlayView(), binding: QuizPlayBinding(), + ), + GetPage( + name: AppRoutes.resultQuizPage, + page: () => QuizResultView(), + binding: QuizResultBinding(), ) ]; } diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index 6f23a36..8d4553a 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -14,4 +14,5 @@ abstract class AppRoutes { static const detailQuizPage = "/quiz/detail"; static const playQuizPage = "/quiz/play"; + static const resultQuizPage = "/quiz/result"; } diff --git a/lib/feature/quiz_creation/controller/quiz_creation_controller.dart b/lib/feature/quiz_creation/controller/quiz_creation_controller.dart index dc5462a..234ac02 100644 --- a/lib/feature/quiz_creation/controller/quiz_creation_controller.dart +++ b/lib/feature/quiz_creation/controller/quiz_creation_controller.dart @@ -90,8 +90,15 @@ class QuizCreationController extends GetxController { questionTC.text = data.question ?? ""; answerTC.text = data.answer ?? ""; - currentQuestionType.value = data.type ?? QuestionType.fillTheBlank; + currentQuestionType.value = data.type ?? QuestionType.fillTheBlank; + if (currentQuestionType.value == QuestionType.option) { + for (int i = 0; i < optionTCList.length; i++) { + optionTCList[i].text = data.options?[i].text ?? ""; + } + } else { + cleanInput(); + } if (data.options != null && data.options!.isNotEmpty) { for (int i = 0; i < optionTCList.length; i++) { optionTCList[i].text = data.options!.length > i ? data.options![i].text : ''; @@ -105,6 +112,12 @@ class QuizCreationController extends GetxController { } } + void cleanInput() { + for (final controller in optionTCList) { + controller.clear(); + } + } + void _updateCurrentQuestion({ String? question, String? answer, @@ -116,16 +129,18 @@ class QuizCreationController extends GetxController { quizData[selectedQuizIndex.value] = current.copyWith( question: question, answer: answer, - options: options ?? - (currentQuestionType.value == QuestionType.option - ? List.generate( - optionTCList.length, - (index) => OptionData(index: index, text: optionTCList[index].text), - ) - : null), + options: options, correctAnswerIndex: correctAnswerIndex, type: type, ); + + // ?? + // (currentQuestionType.value == QuestionType.option + // ? List.generate( + // optionTCList.length, + // (index) => OptionData(index: index, text: optionTCList[index].text), + // ) + // : null), } void updateTOFAnswer(bool answer) { diff --git a/lib/feature/quiz_play/controller/quiz_play_controller.dart b/lib/feature/quiz_play/controller/quiz_play_controller.dart index 4736b29..4e23248 100644 --- a/lib/feature/quiz_play/controller/quiz_play_controller.dart +++ b/lib/feature/quiz_play/controller/quiz_play_controller.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:quiz_app/app/routes/app_pages.dart'; import 'package:quiz_app/core/utils/logger.dart'; import 'package:quiz_app/data/models/quiz/library_quiz_model.dart'; import 'package:quiz_app/data/models/quiz/question_listings_model.dart'; @@ -14,6 +15,7 @@ class QuizPlayController extends GetxController { final isStarting = true.obs; final isAnswerSelected = false.obs; final selectedAnswer = ''.obs; + final idxOptionSelected = (-1).obs; final List answeredQuestions = []; final answerTextController = TextEditingController(); @@ -65,14 +67,20 @@ class QuizPlayController extends GetxController { isAnswerSelected.value = false; } - void selectAnswer(String answer) { - selectedAnswer.value = answer; + void selectAnswerOption(int selectedIndex) { + selectedAnswer.value = selectedIndex.toString(); isAnswerSelected.value = true; + idxOptionSelected.value = selectedIndex; } + // void selectAnswerFTB(String answer) { + // selectedAnswer.value = answer; + // isAnswerSelected.value = true; + // } + void onChooseTOF(bool value) { choosenAnswerTOF.value = value ? 1 : 2; - selectedAnswer.value = value ? "Ya" : "Tidak"; + selectedAnswer.value = value.toString(); } void _submitAnswerIfNeeded() { @@ -87,7 +95,6 @@ class QuizPlayController extends GetxController { userAnswer = selectedAnswer.value.trim(); } - // Masukkan ke answeredQuestions answeredQuestions.add(AnsweredQuestion( index: currentIndex.value, question: question.question, @@ -117,15 +124,19 @@ class QuizPlayController extends GetxController { void _finishQuiz() { _timer?.cancel(); - logC.i(answeredQuestions.map((e) => e.toJson()).toList()); - Get.defaultDialog( - title: "Selesai", - middleText: "Kamu telah menyelesaikan semua soal!", - onConfirm: () { - Get.back(); - Get.back(); - }, - textConfirm: "OK", + // logC.i(answeredQuestions.map((e) => e.toJson()).toList()); + // Get.defaultDialog( + // title: "Selesai", + // middleText: "Kamu telah menyelesaikan semua soal!", + // onConfirm: () { + // Get.back(); + // Get.back(); + // }, + // textConfirm: "OK", + // ); + Get.toNamed( + AppRoutes.resultQuizPage, + arguments: [quizData.questionListings, answeredQuestions], ); } diff --git a/lib/feature/quiz_play/view/quiz_play_view.dart b/lib/feature/quiz_play/view/quiz_play_view.dart index 0fe513d..46981e6 100644 --- a/lib/feature/quiz_play/view/quiz_play_view.dart +++ b/lib/feature/quiz_play/view/quiz_play_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:quiz_app/app/const/colors/app_colors.dart'; import 'package:quiz_app/component/global_text_field.dart'; import 'package:quiz_app/feature/quiz_play/controller/quiz_play_controller.dart'; @@ -64,13 +65,13 @@ class QuizPlayView extends GetView { width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Colors.black, + backgroundColor: controller.idxOptionSelected.value == index ? AppColors.primaryBlue : Colors.white, + foregroundColor: controller.idxOptionSelected.value == index ? Colors.white : Colors.black, side: const BorderSide(color: Colors.grey), padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), - onPressed: () => controller.selectAnswer(option), + onPressed: () => controller.selectAnswerOption(index), child: Text(option), ), ); diff --git a/lib/feature/quiz_preview/controller/quiz_preview_controller.dart b/lib/feature/quiz_preview/controller/quiz_preview_controller.dart index a357183..d6b13d1 100644 --- a/lib/feature/quiz_preview/controller/quiz_preview_controller.dart +++ b/lib/feature/quiz_preview/controller/quiz_preview_controller.dart @@ -76,6 +76,7 @@ class QuizPreviewController extends GetxController { return questions.map((q) { String typeString; String answer = ""; + List? option; switch (q.type) { case QuestionType.fillTheBlank: typeString = 'fill_the_blank'; @@ -84,6 +85,7 @@ class QuizPreviewController extends GetxController { case QuestionType.option: typeString = 'option'; answer = q.correctAnswerIndex.toString(); + option = q.options?.map((o) => o.text).toList(); break; case QuestionType.trueOrFalse: typeString = 'true_false'; @@ -99,7 +101,7 @@ class QuizPreviewController extends GetxController { targetAnswer: answer, duration: 30, type: typeString, - options: q.options?.map((o) => o.text).toList(), + options: option, ); }).toList(); } diff --git a/lib/feature/quiz_result/binding/quiz_result_binding.dart b/lib/feature/quiz_result/binding/quiz_result_binding.dart new file mode 100644 index 0000000..6a1be4c --- /dev/null +++ b/lib/feature/quiz_result/binding/quiz_result_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:quiz_app/feature/quiz_result/controller/quiz_result_controller.dart'; + +class QuizResultBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => QuizResultController()); + } +} diff --git a/lib/feature/quiz_result/controller/quiz_result_controller.dart b/lib/feature/quiz_result/controller/quiz_result_controller.dart new file mode 100644 index 0000000..7bce6ca --- /dev/null +++ b/lib/feature/quiz_result/controller/quiz_result_controller.dart @@ -0,0 +1,43 @@ +import 'package:get/get.dart'; +import 'package:quiz_app/data/models/quiz/question_listings_model.dart'; +import 'package:quiz_app/feature/quiz_play/controller/quiz_play_controller.dart'; + +class QuizResultController extends GetxController { + late final List questions; + late final List answers; + + RxInt correctAnswers = 0.obs; + RxInt totalQuestions = 0.obs; + RxDouble scorePercentage = 0.0.obs; + + @override + void onInit() { + super.onInit(); + loadData(); + calculateResult(); + } + + void loadData() { + final args = Get.arguments as List; + + questions = args[0] as List; + answers = args[1] as List; + totalQuestions.value = questions.length; + } + + void calculateResult() { + int correct = answers.where((a) => a.isCorrect).length; + correctAnswers.value = correct; + if (totalQuestions.value > 0) { + scorePercentage.value = (correctAnswers.value / totalQuestions.value) * 100; + } + } + + String getResultMessage() { + if (scorePercentage.value >= 80) { + return "Lulus 🎉"; + } else { + return "Belum Lulus 😔"; + } + } +} diff --git a/lib/feature/quiz_result/view/quiz_result_view.dart b/lib/feature/quiz_result/view/quiz_result_view.dart new file mode 100644 index 0000000..9e226cb --- /dev/null +++ b/lib/feature/quiz_result/view/quiz_result_view.dart @@ -0,0 +1,146 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:quiz_app/app/const/colors/app_colors.dart'; +import 'package:quiz_app/feature/quiz_result/controller/quiz_result_controller.dart'; + +class QuizResultView extends GetView { + const QuizResultView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFF9FAFB), + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + automaticallyImplyLeading: false, + title: const Text( + 'Hasil Kuis', + style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold), + ), + iconTheme: const IconThemeData(color: Colors.black), + centerTitle: true, + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Obx(() => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildScoreSummary(), + const SizedBox(height: 16), + Expanded( + child: ListView.builder( + itemCount: controller.questions.length, + itemBuilder: (context, index) { + return _buildQuestionResult(index); + }, + ), + ), + ], + )), + ), + ), + ); + } + + Widget _buildScoreSummary() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Skor Kamu: ${controller.correctAnswers}/${controller.totalQuestions}", + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + LinearProgressIndicator( + value: controller.scorePercentage.value / 100, + minHeight: 10, + backgroundColor: AppColors.borderLight, + valueColor: const AlwaysStoppedAnimation(AppColors.primaryBlue), + ), + const SizedBox(height: 8), + Text( + controller.getResultMessage(), + style: TextStyle( + fontSize: 16, + color: controller.scorePercentage.value >= 80 ? Colors.green : Colors.red, + ), + ), + ], + ); + } + + Widget _buildQuestionResult(int index) { + final answered = controller.answers[index]; + final isCorrect = answered.isCorrect; + + return Container( + width: double.infinity, + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColors.borderLight), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 6, + offset: const Offset(2, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Soal ${answered.index + 1}', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: AppColors.darkText)), + const SizedBox(height: 6), + Text( + // Tidak ada tipe soal di AnsweredQuestion, jadi kalau mau kasih tipe harus pakai question[index].type + // kalau tidak mau ribet, ini bisa dihapus saja + // controller.mapQuestionTypeToText(controller.questions[answered.index].type), + '', // kosongkan dulu + style: const TextStyle(fontSize: 12, color: AppColors.softGrayText, fontStyle: FontStyle.italic), + ), + const SizedBox(height: 12), + Text( + answered.question, + style: const TextStyle(fontSize: 16, color: AppColors.darkText), + ), + const SizedBox(height: 12), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon(Icons.person, size: 18, color: AppColors.primaryBlue), + const SizedBox(width: 6), + Expanded( + child: Text( + "Jawaban Kamu: ${answered.selectedAnswer}", + style: TextStyle( + color: isCorrect ? Colors.green : Colors.red, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon(Icons.check, size: 18, color: AppColors.softGrayText), + const SizedBox(width: 6), + Expanded( + child: Text( + "Jawaban Benar: ${answered.correctAnswer}", + style: const TextStyle(color: AppColors.darkText), + ), + ), + ], + ), + ], + ), + ); + } +}