feat: done final result on the multiplayer quiz
This commit is contained in:
parent
871ec13c31
commit
abe21031ec
|
@ -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(),
|
||||
)
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
class QuestionAnswer {
|
||||
final int index;
|
||||
final String question;
|
||||
final dynamic targetAnswer;
|
||||
final int duration;
|
||||
final String type;
|
||||
final List<String>? 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<String, dynamic> json) {
|
||||
return QuestionAnswer(
|
||||
index: json['index'],
|
||||
question: json['question'],
|
||||
targetAnswer: json['target_answer'],
|
||||
duration: json['duration'],
|
||||
type: json['type'],
|
||||
options: json['options'] != null ? List<String>.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<QuestionAnswer> 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<String, dynamic> 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";
|
||||
}
|
|
@ -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<BaseResponseModel<ParticipantResult>?> getAnswerSession(String sessionId, String userId) async {
|
||||
try {
|
||||
final response = await _dio.post(APIEndpoint.quizAnswerSession, data: {
|
||||
"session_id": sessionId,
|
||||
"user_id": userId,
|
||||
});
|
||||
final parsedResponse = BaseResponseModel<ParticipantResult>.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<AnswerService>()));
|
||||
}
|
||||
}
|
|
@ -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},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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?> participantResult = Rx<ParticipantResult?>(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;
|
||||
}
|
||||
}
|
|
@ -167,64 +167,67 @@ class AdminResultPage extends GetView<AdminResultController> {
|
|||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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<ParticipantResultController> {
|
||||
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<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue