From 871ec13c31f6c1df5e2345ed232c7d28e77fc390 Mon Sep 17 00:00:00 2001 From: akhdanre Date: Mon, 19 May 2025 00:44:42 +0700 Subject: [PATCH] feat: done implement admin result page --- lib/app/routes/app_pages.dart | 11 +- lib/core/endpoint/api_endpoint.dart | 2 + lib/data/models/history/session_history.dart | 87 ++++ lib/data/services/history_service.dart | 17 + .../bindings/admin_result_binding.dart | 11 + .../controller/admin_result_controller.dart | 32 ++ .../view/admin_result_page.dart | 487 +++++++++--------- .../controller/monitor_quiz_controller.dart | 5 + .../controller/room_maker_controller.dart | 71 ++- .../room_maker/view/room_maker_view.dart | 26 +- 10 files changed, 468 insertions(+), 281 deletions(-) create mode 100644 lib/data/models/history/session_history.dart create mode 100644 lib/feature/admin_result_page/bindings/admin_result_binding.dart create mode 100644 lib/feature/admin_result_page/controller/admin_result_controller.dart diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 773afc9..71c0700 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -1,5 +1,7 @@ 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/view/admin_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'; @@ -142,9 +144,10 @@ class AppPages { page: () => UpdateProfilePage(), binding: UpdateProfileBinding(), ), - // GetPage( - // name: AppRoutes.monitorResultMPLPage, - // page: () => AdminResultPage(), - // ) + GetPage( + name: AppRoutes.monitorResultMPLPage, + page: () => AdminResultPage(), + binding: AdminResultBinding(), + ) ]; } diff --git a/lib/core/endpoint/api_endpoint.dart b/lib/core/endpoint/api_endpoint.dart index 6da5d84..d33c324 100644 --- a/lib/core/endpoint/api_endpoint.dart +++ b/lib/core/endpoint/api_endpoint.dart @@ -23,6 +23,8 @@ class APIEndpoint { static const String session = "/session"; + static const String sessionHistory = "/history/session"; + static const String userData = "/user"; static const String userUpdate = "/user/update"; } diff --git a/lib/data/models/history/session_history.dart b/lib/data/models/history/session_history.dart new file mode 100644 index 0000000..4391410 --- /dev/null +++ b/lib/data/models/history/session_history.dart @@ -0,0 +1,87 @@ +class Participant { + final String id; + final String name; + final int score; + + Participant({ + required this.id, + required this.name, + required this.score, + }); + + factory Participant.fromJson(Map json) { + return Participant( + id: json['id'], + name: json['name'], + score: json['score'], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'score': score, + }; + } +} + +class SessionHistory { + final String id; + final String sessionCode; + final String quizId; + final String hostId; + final DateTime createdAt; + final DateTime? startedAt; + final DateTime? endedAt; + final bool isActive; + final int participantLimit; + final List participants; + final int currentQuestionIndex; + + SessionHistory({ + required this.id, + required this.sessionCode, + required this.quizId, + required this.hostId, + required this.createdAt, + this.startedAt, + this.endedAt, + required this.isActive, + required this.participantLimit, + required this.participants, + required this.currentQuestionIndex, + }); + + factory SessionHistory.fromJson(Map json) { + return SessionHistory( + id: json['id'], + sessionCode: json['session_code'], + quizId: json['quiz_id'], + hostId: json['host_id'], + createdAt: DateTime.parse(json['created_at']), + startedAt: json['started_at'] != null ? DateTime.parse(json['started_at']) : null, + endedAt: json['ended_at'] != null ? DateTime.parse(json['ended_at']) : null, + isActive: json['is_active'], + participantLimit: json['participan_limit'], // Typo di JSON, harusnya "participant_limit" + participants: (json['participants'] as List).map((p) => Participant.fromJson(p)).toList(), + currentQuestionIndex: json['current_question_index'], + ); + } + + Map toJson() { + return { + 'id': id, + 'session_code': sessionCode, + 'quiz_id': quizId, + 'host_id': hostId, + 'created_at': createdAt.toIso8601String(), + 'started_at': startedAt?.toIso8601String(), + 'ended_at': endedAt?.toIso8601String(), + 'is_active': isActive, + 'participan_limit': participantLimit, // Tetap gunakan sesuai field JSON yang ada + 'participants': participants.map((p) => p.toJson()).toList(), + 'current_question_index': currentQuestionIndex, + }; + } +} diff --git a/lib/data/services/history_service.dart b/lib/data/services/history_service.dart index ef91028..b3816d2 100644 --- a/lib/data/services/history_service.dart +++ b/lib/data/services/history_service.dart @@ -5,6 +5,7 @@ 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/detail_quiz_history.dart'; import 'package:quiz_app/data/models/history/quiz_history.dart'; +import 'package:quiz_app/data/models/history/session_history.dart'; import 'package:quiz_app/data/providers/dio_client.dart'; class HistoryService extends GetxService { @@ -45,4 +46,20 @@ class HistoryService extends GetxService { return null; } } + + Future?> getSessionHistory(String sessionId) async { + try { + final result = await _dio.get("${APIEndpoint.sessionHistory}/$sessionId"); + + final parsedResponse = BaseResponseModel.fromJson( + result.data, + (data) => SessionHistory.fromJson(data), + ); + + return parsedResponse; + } catch (e, stacktrace) { + logC.e(e, stackTrace: stacktrace); + return null; + } + } } diff --git a/lib/feature/admin_result_page/bindings/admin_result_binding.dart b/lib/feature/admin_result_page/bindings/admin_result_binding.dart new file mode 100644 index 0000000..73d8731 --- /dev/null +++ b/lib/feature/admin_result_page/bindings/admin_result_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:quiz_app/data/services/history_service.dart'; +import 'package:quiz_app/feature/admin_result_page/controller/admin_result_controller.dart'; + +class AdminResultBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => HistoryService()); + Get.lazyPut(() => AdminResultController(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 new file mode 100644 index 0000000..58b70da --- /dev/null +++ b/lib/feature/admin_result_page/controller/admin_result_controller.dart @@ -0,0 +1,32 @@ +import 'package:get/get.dart'; +import 'package:quiz_app/data/models/history/session_history.dart'; +import 'package:quiz_app/data/services/history_service.dart'; + +class AdminResultController extends GetxController { + final HistoryService _historyService; + + AdminResultController(this._historyService); + + SessionHistory? sessionHistory; + RxBool isLoading = false.obs; + + @override + void onInit() { + loadData(); + super.onInit(); + } + + void loadData() async { + isLoading.value = true; + + final sessionId = Get.arguments as String; + final result = await _historyService.getSessionHistory(sessionId); + + if (result != null) { + sessionHistory = result.data!; + print(sessionHistory!.toJson()); + } + + isLoading.value = false; + } +} 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 c09a5f9..523cd26 100644 --- a/lib/feature/admin_result_page/view/admin_result_page.dart +++ b/lib/feature/admin_result_page/view/admin_result_page.dart @@ -1,263 +1,246 @@ -// import 'package:flutter/material.dart'; -// import 'package:lucide_icons/lucide_icons.dart'; -// import 'dart:math' as math; +import 'package:flutter/material.dart'; +import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; +import 'package:get/get_state_manager/src/simple/get_view.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/data/models/history/session_history.dart'; +import 'package:quiz_app/feature/admin_result_page/controller/admin_result_controller.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/detail_participant_result_page.dart'; +class AdminResultPage extends GetView { + const AdminResultPage({Key? key}) : super(key: key); -// class AdminResultPage extends StatelessWidget { -// const AdminResultPage({Key? key}) : super(key: key); + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.background, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// backgroundColor: AppColors.background, -// body: SafeArea( -// child: Padding( -// padding: const EdgeInsets.all(16.0), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// _buildSectionHeader("Hasil Akhir Kuis"), -// _buildSummaryCard(), -// const SizedBox(height: 20), -// _buildSectionHeader('Peringkat Peserta'), -// const SizedBox(height: 14), -// Expanded( -// child: ListView.separated( -// itemCount: dummyParticipants.length, -// separatorBuilder: (context, index) => const SizedBox(height: 10), -// itemBuilder: (context, index) { -// final participant = dummyParticipants[index]; -// return _buildParticipantResultCard( -// context, -// participant, -// position: index + 1, -// ); -// }, -// ), -// ), -// ], -// ), -// ), -// ), -// ); -// } + if (controller.sessionHistory == null) { + return const Center(child: Text("Data tidak ditemukan.")); + } -// Widget _buildSectionHeader(String title) { -// return Container( -// padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), -// decoration: BoxDecoration( -// borderRadius: BorderRadius.circular(8), -// ), -// child: Text(title, style: AppTextStyles.title), -// ); -// } + final participants = controller.sessionHistory!.participants; -// Widget _buildSummaryCard() { -// // Hitung nilai rata-rata -// final avgScore = dummyParticipants.map((p) => p.scorePercent).reduce((a, b) => a + b) / dummyParticipants.length; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionHeader("Hasil Akhir Kuis"), + const SizedBox(height: 20), + _buildSummaryCard(participants), + const SizedBox(height: 20), + _buildSectionHeader('Peringkat Peserta'), + const SizedBox(height: 14), + Expanded( + child: ListView.separated( + itemCount: participants.length, + separatorBuilder: (context, index) => const SizedBox(height: 10), + itemBuilder: (context, index) { + final participant = participants[index]; + return _buildParticipantResultCard( + participant, + position: index + 1, + ); + }, + ), + ), + ], + ); + }), + ), + ), + ); + } -// // Hitung jumlah peserta yang lulus (>= 60%) -// final passCount = dummyParticipants.where((p) => p.scorePercent >= 60).length; + Widget _buildSectionHeader(String title) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: Text(title, style: AppTextStyles.title), + ); + } -// return Card( -// elevation: 2, -// color: Colors.white, -// shadowColor: AppColors.shadowPrimary.withValues(alpha:0.2), -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.circular(16), -// side: BorderSide(color: AppColors.accentBlue.withValues(alpha:0.2)), -// ), -// child: Padding( -// padding: const EdgeInsets.all(20), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Row( -// children: [ -// Icon( -// LucideIcons.clipboardCheck, -// color: AppColors.primaryBlue, -// size: 20, -// ), -// const SizedBox(width: 8), -// Text( -// "RINGKASAN KUIS", -// style: TextStyle( -// fontSize: 13, -// fontWeight: FontWeight.bold, -// color: AppColors.primaryBlue, -// letterSpacing: 0.8, -// ), -// ), -// ], -// ), -// Divider(color: AppColors.borderLight, height: 20), -// Row( -// mainAxisAlignment: MainAxisAlignment.spaceAround, -// children: [ -// _buildSummaryItem( -// icon: LucideIcons.users, -// value: "${dummyParticipants.length}", -// label: "Total Peserta", -// ), -// _buildSummaryItem( -// icon: LucideIcons.percent, -// value: "${avgScore.toStringAsFixed(1)}%", -// label: "Rata-Rata Nilai", -// valueColor: _getScoreColor(avgScore), -// ), -// _buildSummaryItem( -// icon: LucideIcons.award, -// value: "$passCount/${dummyParticipants.length}", -// label: "Peserta Lulus", -// valueColor: AppColors.scoreGood, -// ), -// ], -// ), -// ], -// ), -// ), -// ); -// } + Widget _buildSummaryCard(List participants) { + final avgScore = participants.isNotEmpty ? participants.map((p) => p.score).reduce((a, b) => a + b) / participants.length : 0.0; -// Widget _buildSummaryItem({ -// required IconData icon, -// required String value, -// required String label, -// Color? valueColor, -// }) { -// return Column( -// children: [ -// Icon(icon, color: AppColors.softGrayText, size: 22), -// const SizedBox(height: 8), -// Text( -// value, -// style: TextStyle( -// fontSize: 18, -// fontWeight: FontWeight.bold, -// color: valueColor ?? AppColors.darkText, -// ), -// ), -// const SizedBox(height: 4), -// Text( -// label, -// style: AppTextStyles.caption, -// ), -// ], -// ); -// } + final passCount = participants.where((p) => p.score >= 60).length; -// Widget _buildParticipantResultCard(BuildContext context, ParticipantResult participant, {required int position}) { -// return InkWell( -// onTap: () { -// // Navigasi ke halaman detail saat kartu ditekan -// Navigator.push( -// context, -// MaterialPageRoute( -// builder: (context) => ParticipantDetailPage(participant: participant), -// ), -// ); -// }, -// child: Card( -// elevation: 2, -// color: Colors.white, -// shadowColor: AppColors.shadowPrimary.withValues(alpha:0.2), -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.circular(16), -// side: BorderSide(color: AppColors.accentBlue.withValues(alpha:0.2)), -// ), -// child: Padding( -// padding: const EdgeInsets.all(20), -// child: Row( -// children: [ -// // Position indicator -// 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), -// // Participant info -// Expanded( -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Text( -// participant.name, -// style: AppTextStyles.subtitle, -// ), -// const SizedBox(height: 4), -// Text( -// "Benar: ${participant.correctAnswers}/${participant.totalQuestions}", -// style: AppTextStyles.caption, -// ), -// ], -// ), -// ), -// // Score -// Container( -// width: 60, -// height: 60, -// decoration: BoxDecoration( -// shape: BoxShape.circle, -// color: _getScoreColor(participant.scorePercent).withValues(alpha:0.1), -// border: Border.all( -// color: _getScoreColor(participant.scorePercent), -// width: 2, -// ), -// ), -// child: Center( -// child: Text( -// "${participant.scorePercent.toInt()}%", -// style: TextStyle( -// fontSize: 16, -// fontWeight: FontWeight.bold, -// color: _getScoreColor(participant.scorePercent), -// ), -// ), -// ), -// ), -// const SizedBox(width: 12), -// // Arrow indicator -// Icon( -// LucideIcons.chevronRight, -// color: AppColors.softGrayText, -// size: 20, -// ), -// ], -// ), -// ), -// ), -// ); -// } + return Card( + elevation: 2, + color: Colors.white, + shadowColor: AppColors.shadowPrimary.withOpacity(0.2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide(color: AppColors.accentBlue.withOpacity(0.2)), + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.assignment_turned_in, color: AppColors.primaryBlue, size: 20), + const SizedBox(width: 8), + Text( + "RINGKASAN KUIS", + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: AppColors.primaryBlue, + letterSpacing: 0.8, + ), + ), + ], + ), + const Divider(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildSummaryItem( + icon: Icons.group, + value: "${participants.length}", + label: "Total Peserta", + ), + _buildSummaryItem( + icon: Icons.percent, + value: "${avgScore.toStringAsFixed(1)}%", + label: "Rata-Rata Nilai", + valueColor: _getScoreColor(avgScore), + ), + _buildSummaryItem( + icon: Icons.emoji_events, + value: "$passCount/${participants.length}", + label: "Peserta Lulus", + valueColor: AppColors.scoreGood, + ), + ], + ), + ], + ), + ), + ); + } -// Color _getScoreColor(double score) { -// if (score >= 80) return AppColors.scoreExcellent; -// if (score >= 70) return AppColors.scoreGood; -// if (score >= 60) return AppColors.scoreAverage; -// return AppColors.scorePoor; -// } + Widget _buildSummaryItem({ + required IconData icon, + required String value, + required String label, + Color? valueColor, + }) { + return Column( + children: [ + Icon(icon, color: AppColors.softGrayText, size: 22), + const SizedBox(height: 8), + Text( + value, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: valueColor ?? AppColors.darkText, + ), + ), + const SizedBox(height: 4), + Text( + label, + style: AppTextStyles.caption, + ), + ], + ); + } -// Color _getPositionColor(int position) { -// if (position == 1) return const Color(0xFFFFD700); // Gold -// if (position == 2) return const Color(0xFFC0C0C0); // Silver -// if (position == 3) return const Color(0xFFCD7F32); // Bronze -// return AppColors.softGrayText; -// } -// } + Widget _buildParticipantResultCard(Participant participant, {required int position}) { + final scorePercent = participant.score.toDouble(); + + return Card( + elevation: 2, + color: Colors.white, + shadowColor: AppColors.shadowPrimary.withOpacity(0.2), + shape: RoundedRectangleBorder( + 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, + ), + ), + ), + ), + 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, + ), + ), + 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), + ], + ), + ), + ); + } + + Color _getScoreColor(double score) { + if (score >= 80) return AppColors.scoreExcellent; + if (score >= 70) return AppColors.scoreGood; + if (score >= 60) return AppColors.scoreAverage; + return AppColors.scorePoor; + } + + Color _getPositionColor(int position) { + if (position == 1) return const Color(0xFFFFD700); // Gold + if (position == 2) return const Color(0xFFC0C0C0); // Silver + if (position == 3) return const Color(0xFFCD7F32); // Bronze + return AppColors.softGrayText; + } +} diff --git a/lib/feature/monitor_quiz/controller/monitor_quiz_controller.dart b/lib/feature/monitor_quiz/controller/monitor_quiz_controller.dart index 62f1c1e..b0a8cab 100644 --- a/lib/feature/monitor_quiz/controller/monitor_quiz_controller.dart +++ b/lib/feature/monitor_quiz/controller/monitor_quiz_controller.dart @@ -1,4 +1,5 @@ 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/user/user_model.dart'; import 'package:quiz_app/data/services/socket_service.dart'; @@ -100,6 +101,10 @@ class MonitorQuizController extends GetxController { // Notify observers participan.refresh(); }); + + _socketService.quizDone.listen((_) { + Get.offAllNamed(AppRoutes.monitorResultMPLPage, arguments: sessionId); + }); } } diff --git a/lib/feature/room_maker/controller/room_maker_controller.dart b/lib/feature/room_maker/controller/room_maker_controller.dart index 904027f..a271f36 100644 --- a/lib/feature/room_maker/controller/room_maker_controller.dart +++ b/lib/feature/room_maker/controller/room_maker_controller.dart @@ -26,26 +26,68 @@ class RoomMakerController extends GetxController { this._quizService, ); - // final roomName = ''.obs; final selectedQuiz = Rxn(); - RxBool isOnwQuiz = true.obs; final TextEditingController nameTC = TextEditingController(); final TextEditingController maxPlayerTC = TextEditingController(); + final ScrollController scrollController = ScrollController(); final availableQuizzes = [].obs; + int currentPage = 1; + bool isLoading = false; + bool hasMoreData = true; + @override void onInit() { - loadQuiz(); + loadQuiz(reset: true); + scrollController.addListener(_scrollListener); super.onInit(); } - loadQuiz() async { - BaseResponseModel>? response = await _quizService.userQuiz(_userController.userData!.id, 1); + Future loadQuiz({bool reset = false}) async { + if (isLoading) return; + + isLoading = true; + + if (reset) { + currentPage = 1; + hasMoreData = true; + } + + BaseResponseModel>? response; + + if (isOnwQuiz.value) { + response = await _quizService.userQuiz(_userController.userData!.id, currentPage); + } else { + response = await _quizService.recomendationQuiz(page: currentPage, amount: 5); + } + if (response != null) { - availableQuizzes.assignAll(response.data!); + if (reset) { + availableQuizzes.assignAll(response.data!); + } else { + availableQuizzes.addAll(response.data!); + } + + if (response.data == null || response.data!.isEmpty) { + hasMoreData = false; + } else { + currentPage++; + } + } else { + hasMoreData = false; + } + + isLoading = false; + } + + void _scrollListener() { + if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 100) { + if (hasMoreData && !isLoading) { + loadQuiz(); + } } } @@ -67,7 +109,10 @@ class RoomMakerController extends GetxController { if (response != null) { _socketService.initSocketConnection(); - _socketService.joinRoom(sessionCode: response.data!.sessionCode, userId: _userController.userData!.id); + _socketService.joinRoom( + sessionCode: response.data!.sessionCode, + userId: _userController.userData!.id, + ); _socketService.roomMessages.listen((data) { if (data["type"] == "join") { @@ -94,17 +139,7 @@ class RoomMakerController extends GetxController { void onQuizSourceChange(bool base) async { isOnwQuiz.value = base; - if (base) { - BaseResponseModel>? response = await _quizService.userQuiz(_userController.userData!.id, 1); - if (response != null) { - availableQuizzes.assignAll(response.data!); - } - return; - } - BaseResponseModel>? response = await _quizService.recomendationQuiz(page: 1, amount: 4); - if (response != null) { - availableQuizzes.assignAll(response.data!); - } + await loadQuiz(reset: true); } void onQuizChoosen(String quizId) { diff --git a/lib/feature/room_maker/view/room_maker_view.dart b/lib/feature/room_maker/view/room_maker_view.dart index bf79d92..2dd72b1 100644 --- a/lib/feature/room_maker/view/room_maker_view.dart +++ b/lib/feature/room_maker/view/room_maker_view.dart @@ -44,13 +44,25 @@ class RoomMakerView extends GetView { Expanded( child: Container( child: Obx(() => ListView.builder( - itemCount: controller.availableQuizzes.length, - itemBuilder: (context, index) { - return QuizContainerComponent( - data: controller.availableQuizzes[index], - onTap: controller.onQuizChoosen, - ); - })), + controller: controller.scrollController, + itemCount: controller.availableQuizzes.length + (controller.isLoading ? 1 : 0), + itemBuilder: (context, index) { + if (index < controller.availableQuizzes.length) { + return QuizContainerComponent( + data: controller.availableQuizzes[index], + onTap: controller.onQuizChoosen, + ); + } else { + // Loading Indicator di Bawah + return Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + }, + )), ), ), SizedBox(