From abe21031ecba44b34eecffd5fb09119c978a6d85 Mon Sep 17 00:00:00 2001 From: akhdanre Date: Mon, 19 May 2025 01:42:15 +0700 Subject: [PATCH] feat: done final result on the multiplayer quiz --- lib/app/routes/app_pages.dart | 7 + lib/app/routes/app_routes.dart | 2 + lib/core/endpoint/api_endpoint.dart | 1 + .../history/participant_history_result.dart | 78 +++ lib/data/services/answer_service.dart | 19 + .../detail_participant_result_binding.dart | 11 + .../controller/admin_result_controller.dart | 10 +- .../detail_participant_result_controller.dart | 47 ++ .../view/admin_result_page.dart | 101 ++-- .../view/detail_participant_result_page.dart | 526 ++++++++++-------- 10 files changed, 521 insertions(+), 281 deletions(-) create mode 100644 lib/data/models/history/participant_history_result.dart create mode 100644 lib/feature/admin_result_page/bindings/detail_participant_result_binding.dart create mode 100644 lib/feature/admin_result_page/controller/detail_participant_result_controller.dart diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 71c0700..81fcddc 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -1,7 +1,9 @@ import 'package:get/get_navigation/src/routes/get_route.dart'; import 'package:quiz_app/app/middleware/auth_middleware.dart'; import 'package:quiz_app/feature/admin_result_page/bindings/admin_result_binding.dart'; +import 'package:quiz_app/feature/admin_result_page/bindings/detail_participant_result_binding.dart'; import 'package:quiz_app/feature/admin_result_page/view/admin_result_page.dart'; +import 'package:quiz_app/feature/admin_result_page/view/detail_participant_result_page.dart'; import 'package:quiz_app/feature/history/binding/detail_history_binding.dart'; import 'package:quiz_app/feature/history/binding/history_binding.dart'; import 'package:quiz_app/feature/history/view/detail_history_view.dart'; @@ -148,6 +150,11 @@ class AppPages { name: AppRoutes.monitorResultMPLPage, page: () => AdminResultPage(), binding: AdminResultBinding(), + ), + GetPage( + name: AppRoutes.quizMPLResultPage, + page: () => ParticipantDetailPage(), + binding: DetailParticipantResultBinding(), ) ]; } diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index 49ebba5..c20d3af 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -27,4 +27,6 @@ abstract class AppRoutes { static const monitorResultMPLPage = "/room/quiz/monitor/result"; static const updateProfilePage = "/profile/update"; + + static const quizMPLResultPage = "/room/quiz/result"; } diff --git a/lib/core/endpoint/api_endpoint.dart b/lib/core/endpoint/api_endpoint.dart index d33c324..b62104f 100644 --- a/lib/core/endpoint/api_endpoint.dart +++ b/lib/core/endpoint/api_endpoint.dart @@ -11,6 +11,7 @@ class APIEndpoint { static const String quiz = "/quiz"; static const String quizGenerate = "/quiz/ai"; static const String quizAnswer = "/quiz/answer"; + static const String quizAnswerSession = "/quiz/answer/session"; static const String userQuiz = "/quiz/user"; static const String quizRecomendation = "/quiz/recomendation"; diff --git a/lib/data/models/history/participant_history_result.dart b/lib/data/models/history/participant_history_result.dart new file mode 100644 index 0000000..dad6a43 --- /dev/null +++ b/lib/data/models/history/participant_history_result.dart @@ -0,0 +1,78 @@ +class QuestionAnswer { + final int index; + final String question; + final dynamic targetAnswer; + final int duration; + final String type; + final List? options; + final String answer; + final bool isCorrect; + final double timeSpent; + + QuestionAnswer({ + required this.index, + required this.question, + required this.targetAnswer, + required this.duration, + required this.type, + required this.options, + required this.answer, + required this.isCorrect, + required this.timeSpent, + }); + + factory QuestionAnswer.fromJson(Map json) { + return QuestionAnswer( + index: json['index'], + question: json['question'], + targetAnswer: json['target_answer'], + duration: json['duration'], + type: json['type'], + options: json['options'] != null ? List.from(json['options']) : null, + answer: json['answer'], + isCorrect: json['is_correct'], + timeSpent: (json['time_spent'] as num).toDouble(), + ); + } +} + +class ParticipantResult { + final String id; + final String sessionId; + final String quizId; + final String userId; + final String answeredAt; + final List answers; + final int totalScore; + final int totalCorrect; + + ParticipantResult({ + required this.id, + required this.sessionId, + required this.quizId, + required this.userId, + required this.answeredAt, + required this.answers, + required this.totalScore, + required this.totalCorrect, + }); + + factory ParticipantResult.fromJson(Map json) { + return ParticipantResult( + id: json['id'], + sessionId: json['session_id'], + quizId: json['quiz_id'], + userId: json['user_id'], + answeredAt: json['answered_at'], + answers: (json['answers'] as List).map((e) => QuestionAnswer.fromJson(e)).toList(), + totalScore: json['total_score'], + totalCorrect: json['total_correct'], + ); + } + + double get scorePercent => (totalCorrect / answers.length) * 100; + + int get totalQuestions => answers.length; + + String get name => "User $userId"; +} diff --git a/lib/data/services/answer_service.dart b/lib/data/services/answer_service.dart index cc377cd..7810f17 100644 --- a/lib/data/services/answer_service.dart +++ b/lib/data/services/answer_service.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:quiz_app/core/endpoint/api_endpoint.dart'; import 'package:quiz_app/core/utils/logger.dart'; import 'package:quiz_app/data/models/base/base_model.dart'; +import 'package:quiz_app/data/models/history/participant_history_result.dart'; import 'package:quiz_app/data/providers/dio_client.dart'; class AnswerService extends GetxService { @@ -26,4 +27,22 @@ class AnswerService extends GetxService { return null; } } + + Future?> getAnswerSession(String sessionId, String userId) async { + try { + final response = await _dio.post(APIEndpoint.quizAnswerSession, data: { + "session_id": sessionId, + "user_id": userId, + }); + final parsedResponse = BaseResponseModel.fromJson( + response.data, + (data) => ParticipantResult.fromJson(data), + ); + + return parsedResponse; + } on DioException catch (e) { + logC.e('Gagal mengirim jawaban: ${e.response?.data['message'] ?? e.message}'); + return null; + } + } } diff --git a/lib/feature/admin_result_page/bindings/detail_participant_result_binding.dart b/lib/feature/admin_result_page/bindings/detail_participant_result_binding.dart new file mode 100644 index 0000000..2aa6220 --- /dev/null +++ b/lib/feature/admin_result_page/bindings/detail_participant_result_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:quiz_app/data/services/answer_service.dart'; +import 'package:quiz_app/feature/admin_result_page/controller/detail_participant_result_controller.dart'; + +class DetailParticipantResultBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => AnswerService()); + Get.lazyPut(() => ParticipantResultController(Get.find())); + } +} diff --git a/lib/feature/admin_result_page/controller/admin_result_controller.dart b/lib/feature/admin_result_page/controller/admin_result_controller.dart index 58b70da..3b24dcc 100644 --- a/lib/feature/admin_result_page/controller/admin_result_controller.dart +++ b/lib/feature/admin_result_page/controller/admin_result_controller.dart @@ -1,4 +1,5 @@ import 'package:get/get.dart'; +import 'package:quiz_app/app/routes/app_pages.dart'; import 'package:quiz_app/data/models/history/session_history.dart'; import 'package:quiz_app/data/services/history_service.dart'; @@ -10,6 +11,8 @@ class AdminResultController extends GetxController { SessionHistory? sessionHistory; RxBool isLoading = false.obs; + String sessionId = ""; + @override void onInit() { loadData(); @@ -19,7 +22,7 @@ class AdminResultController extends GetxController { void loadData() async { isLoading.value = true; - final sessionId = Get.arguments as String; + sessionId = Get.arguments as String; final result = await _historyService.getSessionHistory(sessionId); if (result != null) { @@ -29,4 +32,9 @@ class AdminResultController extends GetxController { isLoading.value = false; } + + void goToDetailParticipants(String userId, String username) => Get.toNamed( + AppRoutes.quizMPLResultPage, + arguments: {"user_id": userId, "session_id": sessionId, "username": username}, + ); } diff --git a/lib/feature/admin_result_page/controller/detail_participant_result_controller.dart b/lib/feature/admin_result_page/controller/detail_participant_result_controller.dart new file mode 100644 index 0000000..a159013 --- /dev/null +++ b/lib/feature/admin_result_page/controller/detail_participant_result_controller.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; +import 'package:get/get.dart'; +import 'package:quiz_app/data/models/history/participant_history_result.dart'; +import 'package:quiz_app/data/services/answer_service.dart'; + +class ParticipantResultController extends GetxController { + final AnswerService _answerService; + + ParticipantResultController(this._answerService); + + final Rx participantResult = Rx(null); + final RxBool isLoading = false.obs; + + RxString participantName = "".obs; + + @override + void onInit() { + loadData(); + super.onInit(); + } + + void loadData() async { + isLoading.value = true; + + final args = Get.arguments; + participantName.value = args["username"]; + final response = await _answerService.getAnswerSession(args["session_id"], args["user_id"]); + + if (response != null) { + participantResult.value = response.data; + } + isLoading.value = false; + } + + double calculateScorePercent() { + if (participantResult.value == null) return 0; + return participantResult.value!.scorePercent; + } + + int getTotalCorrect() { + return participantResult.value?.totalCorrect ?? 0; + } + + int getTotalQuestions() { + return participantResult.value?.totalQuestions ?? 0; + } +} diff --git a/lib/feature/admin_result_page/view/admin_result_page.dart b/lib/feature/admin_result_page/view/admin_result_page.dart index 523cd26..9ded9a5 100644 --- a/lib/feature/admin_result_page/view/admin_result_page.dart +++ b/lib/feature/admin_result_page/view/admin_result_page.dart @@ -167,64 +167,67 @@ class AdminResultPage extends GetView { borderRadius: BorderRadius.circular(16), side: BorderSide(color: AppColors.accentBlue.withOpacity(0.2)), ), - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _getPositionColor(position), - ), - child: Center( - child: Text( - position.toString(), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white, + child: InkWell( + onTap: () => controller.goToDetailParticipants(participant.id, participant.name), + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _getPositionColor(position), + ), + child: Center( + child: Text( + position.toString(), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), ), ), ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(participant.name, style: AppTextStyles.subtitle), - const SizedBox(height: 4), - Text("Skor: ${participant.score}", style: AppTextStyles.caption), - ], - ), - ), - Container( - width: 60, - height: 60, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _getScoreColor(scorePercent).withOpacity(0.1), - border: Border.all( - color: _getScoreColor(scorePercent), - width: 2, + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(participant.name, style: AppTextStyles.subtitle), + const SizedBox(height: 4), + Text("Skor: ${participant.score}", style: AppTextStyles.caption), + ], ), ), - child: Center( - child: Text( - "${participant.score}%", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _getScoreColor(scorePercent).withOpacity(0.1), + border: Border.all( color: _getScoreColor(scorePercent), + width: 2, + ), + ), + child: Center( + child: Text( + "${participant.score}%", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: _getScoreColor(scorePercent), + ), ), ), ), - ), - const SizedBox(width: 12), - const Icon(Icons.chevron_right, color: AppColors.softGrayText, size: 20), - ], + const SizedBox(width: 12), + const Icon(Icons.chevron_right, color: AppColors.softGrayText, size: 20), + ], + ), ), ), ); diff --git a/lib/feature/admin_result_page/view/detail_participant_result_page.dart b/lib/feature/admin_result_page/view/detail_participant_result_page.dart index f121b27..4ba5b66 100644 --- a/lib/feature/admin_result_page/view/detail_participant_result_page.dart +++ b/lib/feature/admin_result_page/view/detail_participant_result_page.dart @@ -1,238 +1,302 @@ -// // Halaman detail untuk peserta -// import 'package:flutter/material.dart'; -// import 'package:lucide_icons/lucide_icons.dart'; -// import 'package:quiz_app/app/const/colors/app_colors.dart'; -// import 'package:quiz_app/app/const/text/text_style.dart'; -// import 'package:quiz_app/feature/admin_result_page/view/admin_result_page.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import 'package:quiz_app/app/const/colors/app_colors.dart'; +import 'package:quiz_app/data/models/history/participant_history_result.dart'; +import 'package:quiz_app/feature/admin_result_page/controller/detail_participant_result_controller.dart'; -// class ParticipantDetailPage extends StatelessWidget { -// final ParticipantResult participant; +class ParticipantDetailPage extends GetView { + const ParticipantDetailPage({ + Key? key, + }) : super(key: key); -// const ParticipantDetailPage({ -// Key? key, -// required this.participant, -// }) : super(key: key); + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar( + title: const Text('Detail Peserta'), + backgroundColor: Colors.white, + foregroundColor: AppColors.darkText, + elevation: 0, + leading: IconButton( + icon: const Icon(LucideIcons.arrowLeft), + onPressed: () => Get.back(), + ), + ), + body: Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// backgroundColor: AppColors.background, -// appBar: AppBar( -// title: Text('Detail ${participant.name}'), -// backgroundColor: Colors.white, -// foregroundColor: AppColors.darkText, -// elevation: 0, -// leading: IconButton( -// icon: const Icon(LucideIcons.arrowLeft), -// onPressed: () => Navigator.pop(context), -// ), -// ), -// body: Column( -// children: [ -// _buildParticipantHeader(), -// Expanded( -// child: ListView.builder( -// padding: const EdgeInsets.all(16), -// itemCount: participant.answers.length, -// itemBuilder: (context, index) { -// return _buildAnswerCard(participant.answers[index], index + 1); -// }, -// ), -// ), -// ], -// ), -// ); -// } + final participant = controller.participantResult.value; + if (participant == null) { + return const Center(child: Text('Data peserta tidak tersedia.')); + } -// Widget _buildParticipantHeader() { -// return Container( -// width: double.infinity, -// padding: const EdgeInsets.all(16), -// decoration: BoxDecoration( -// color: Colors.white, -// border: Border( -// bottom: BorderSide( -// color: AppColors.borderLight, -// width: 1, -// ), -// ), -// ), -// child: Row( -// children: [ -// CircleAvatar( -// radius: 26, -// backgroundColor: AppColors.accentBlue, -// child: Text( -// participant.name.isNotEmpty ? participant.name[0].toUpperCase() : "?", -// style: TextStyle( -// fontSize: 22, -// fontWeight: FontWeight.bold, -// color: AppColors.primaryBlue, -// ), -// ), -// ), -// const SizedBox(width: 16), -// Expanded( -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Text( -// participant.name, -// style: TextStyle( -// fontSize: 16, -// fontWeight: FontWeight.bold, -// color: AppColors.darkText, -// ), -// ), -// const SizedBox(height: 4), -// Text( -// "Jumlah Soal: ${participant.totalQuestions}", -// style: TextStyle( -// fontSize: 14, -// color: AppColors.softGrayText, -// ), -// ), -// ], -// ), -// ), -// Container( -// padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), -// decoration: BoxDecoration( -// color: _getScoreColor(participant.scorePercent).withValues(alpha:0.1), -// borderRadius: BorderRadius.circular(16), -// border: Border.all( -// color: _getScoreColor(participant.scorePercent), -// ), -// ), -// child: Row( -// children: [ -// Icon( -// LucideIcons.percent, -// size: 16, -// color: _getScoreColor(participant.scorePercent), -// ), -// const SizedBox(width: 6), -// Text( -// "${participant.scorePercent.toInt()}%", -// style: TextStyle( -// fontSize: 16, -// fontWeight: FontWeight.bold, -// color: _getScoreColor(participant.scorePercent), -// ), -// ), -// ], -// ), -// ), -// ], -// ), -// ); -// } + return Column( + children: [ + _buildParticipantHeader(participant), + Expanded( + child: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: participant.answers.length, + itemBuilder: (context, index) { + return _buildAnswerCard(participant.answers[index], index + 1); + }, + ), + ), + ], + ); + }), + ); + } -// Widget _buildAnswerCard(QuestionAnswer answer, int number) { -// return Container( -// width: double.infinity, -// margin: const EdgeInsets.only(bottom: 20), -// padding: const EdgeInsets.all(16), -// // decoration: _containerDecoration, -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Row( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Container( -// width: 28, -// height: 28, -// decoration: BoxDecoration( -// shape: BoxShape.circle, -// color: answer.isCorrect ? AppColors.primaryBlue.withValues(alpha:0.1) : AppColors.scorePoor.withValues(alpha:0.1), -// ), -// child: Center( -// child: Text( -// number.toString(), -// style: TextStyle( -// fontSize: 13, -// fontWeight: FontWeight.bold, -// color: answer.isCorrect ? AppColors.primaryBlue : AppColors.scorePoor, -// ), -// ), -// ), -// ), -// const SizedBox(width: 12), -// Expanded( -// child: Text( -// 'Soal $number: ${answer.question}', -// style: const TextStyle( -// fontSize: 16, -// fontWeight: FontWeight.bold, -// color: AppColors.darkText, -// ), -// ), -// ), -// Icon( -// answer.isCorrect ? LucideIcons.checkCircle : LucideIcons.xCircle, -// color: answer.isCorrect ? AppColors.primaryBlue : AppColors.scorePoor, -// size: 20, -// ), -// ], -// ), -// const SizedBox(height: 12), -// Divider(color: AppColors.borderLight), -// const SizedBox(height: 12), -// _buildAnswerRow( -// label: "Jawaban Siswa:", -// answer: answer.userAnswer, -// isCorrect: answer.isCorrect, -// ), -// if (!answer.isCorrect) ...[ -// const SizedBox(height: 10), -// _buildAnswerRow( -// label: "Jawaban Benar:", -// answer: answer.correctAnswer, -// isCorrect: true, -// ), -// ], -// ], -// ), -// ); -// } + Widget _buildParticipantHeader(ParticipantResult participant) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + bottom: BorderSide( + color: AppColors.borderLight, + width: 1, + ), + ), + ), + child: Row( + children: [ + CircleAvatar( + radius: 26, + backgroundColor: AppColors.accentBlue, + child: Text( + controller.participantName.value[0].toUpperCase(), + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: AppColors.primaryBlue, + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + controller.participantName.value, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.darkText, + ), + ), + const SizedBox(height: 4), + Text( + "Jumlah Soal: ${participant.totalQuestions}", + style: const TextStyle( + fontSize: 14, + color: AppColors.softGrayText, + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: _getScoreColor(participant.scorePercent).withOpacity(0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: _getScoreColor(participant.scorePercent), + ), + ), + child: Row( + children: [ + Icon( + LucideIcons.percent, + size: 16, + color: _getScoreColor(participant.scorePercent), + ), + const SizedBox(width: 6), + Text( + "${participant.scorePercent.toInt()}%", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: _getScoreColor(participant.scorePercent), + ), + ), + ], + ), + ), + ], + ), + ); + } -// Widget _buildAnswerRow({ -// required String label, -// required String answer, -// required bool isCorrect, -// }) { -// return Row( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// SizedBox( -// width: 110, -// child: Text( -// label, -// style: TextStyle( -// fontSize: 14, -// fontWeight: FontWeight.w500, -// color: AppColors.softGrayText, -// ), -// ), -// ), -// Expanded( -// child: Text( -// answer, -// style: TextStyle( -// fontSize: 15, -// fontWeight: FontWeight.w600, -// color: isCorrect ? AppColors.primaryBlue : AppColors.scorePoor, -// ), -// ), -// ), -// ], -// ); -// } + Widget _buildAnswerCard(QuestionAnswer answer, int number) { + return Container( + width: double.infinity, + margin: const EdgeInsets.only(bottom: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + blurRadius: 6, + offset: const Offset(0, 3), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: answer.isCorrect ? AppColors.primaryBlue.withOpacity(0.1) : AppColors.scorePoor.withOpacity(0.1), + ), + child: Center( + child: Text( + number.toString(), + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: answer.isCorrect ? AppColors.primaryBlue : AppColors.scorePoor, + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Soal $number: ${answer.question}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.darkText, + ), + ), + ), + Icon( + answer.isCorrect ? LucideIcons.checkCircle : LucideIcons.xCircle, + color: answer.isCorrect ? AppColors.primaryBlue : AppColors.scorePoor, + size: 20, + ), + ], + ), + const SizedBox(height: 12), + Divider(color: AppColors.borderLight), + const SizedBox(height: 12), + _buildAnswerRow( + label: "Tipe Soal:", + answer: answer.type, + isCorrect: true, + ), + _buildAnswerRow( + label: "Waktu Diberikan:", + answer: "${answer.duration} detik", + isCorrect: true, + ), + _buildAnswerRow( + label: "Waktu Dihabiskan:", + answer: "${answer.timeSpent} detik", + isCorrect: true, + ), + _buildAnswerRow( + label: "Jawaban Siswa:", + answer: answer.answer, + isCorrect: answer.isCorrect, + ), + if (!answer.isCorrect) ...[ + const SizedBox(height: 10), + _buildAnswerRow( + label: "Jawaban Benar:", + answer: answer.targetAnswer.toString(), + isCorrect: true, + ), + ], + if (answer.options != null) ...[ + const SizedBox(height: 10), + _buildOptions(answer.options!), + ], + ], + ), + ); + } -// Color _getScoreColor(double score) { -// if (score >= 70) return AppColors.scoreGood; -// if (score >= 60) return AppColors.scoreAverage; -// return AppColors.scorePoor; -// } -// } + Widget _buildOptions(List options) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Pilihan Jawaban:", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.softGrayText, + ), + ), + const SizedBox(height: 6), + ...options.map((opt) => Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Text( + "- $opt", + style: const TextStyle( + fontSize: 15, + color: AppColors.darkText, + ), + ), + )), + ], + ); + } + + Widget _buildAnswerRow({ + required String label, + required String answer, + required bool isCorrect, + }) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 110, + child: Text( + label, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.softGrayText, + ), + ), + ), + Expanded( + child: Text( + answer, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: isCorrect ? AppColors.primaryBlue : AppColors.scorePoor, + ), + ), + ), + ], + ); + } + + Color _getScoreColor(double score) { + if (score >= 70) return AppColors.scoreGood; + if (score >= 60) return AppColors.scoreAverage; + return AppColors.scorePoor; + } +}