feat: adding monitor quiz logic
This commit is contained in:
parent
5f54ca6c8c
commit
e060f32593
|
@ -14,6 +14,7 @@ import 'package:quiz_app/feature/listing_quiz/binding/listing_quiz_binding.dart'
|
||||||
import 'package:quiz_app/feature/listing_quiz/view/listing_quiz_view.dart';
|
import 'package:quiz_app/feature/listing_quiz/view/listing_quiz_view.dart';
|
||||||
import 'package:quiz_app/feature/login/bindings/login_binding.dart';
|
import 'package:quiz_app/feature/login/bindings/login_binding.dart';
|
||||||
import 'package:quiz_app/feature/login/view/login_page.dart';
|
import 'package:quiz_app/feature/login/view/login_page.dart';
|
||||||
|
import 'package:quiz_app/feature/monitor_quiz/binding/monitor_quiz_binding.dart';
|
||||||
import 'package:quiz_app/feature/monitor_quiz/view/monitor_quiz_view.dart';
|
import 'package:quiz_app/feature/monitor_quiz/view/monitor_quiz_view.dart';
|
||||||
import 'package:quiz_app/feature/navigation/bindings/navigation_binding.dart';
|
import 'package:quiz_app/feature/navigation/bindings/navigation_binding.dart';
|
||||||
import 'package:quiz_app/feature/navigation/views/navbar_view.dart';
|
import 'package:quiz_app/feature/navigation/views/navbar_view.dart';
|
||||||
|
@ -127,7 +128,7 @@ class AppPages {
|
||||||
GetPage(
|
GetPage(
|
||||||
name: AppRoutes.monitorQuizMPLPage,
|
name: AppRoutes.monitorQuizMPLPage,
|
||||||
page: () => MonitorQuizView(),
|
page: () => MonitorQuizView(),
|
||||||
// binding: JoinRoomBinding(),
|
binding: MonitorQuizBinding(),
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: AppRoutes.playQuizMPLPage,
|
name: AppRoutes.playQuizMPLPage,
|
||||||
|
|
|
@ -7,3 +7,5 @@ class UserModel {
|
||||||
required this.name,
|
required this.name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,23 @@ class SocketService {
|
||||||
|
|
||||||
final _roomMessageController = StreamController<Map<String, dynamic>>.broadcast();
|
final _roomMessageController = StreamController<Map<String, dynamic>>.broadcast();
|
||||||
final _chatMessageController = StreamController<Map<String, dynamic>>.broadcast();
|
final _chatMessageController = StreamController<Map<String, dynamic>>.broadcast();
|
||||||
|
final _questionUpdateController = StreamController<Map<String, dynamic>>.broadcast();
|
||||||
final _quizStartedController = StreamController<void>.broadcast();
|
final _quizStartedController = StreamController<void>.broadcast();
|
||||||
|
final _answerSubmittedController = StreamController<Map<String, dynamic>>.broadcast();
|
||||||
|
final _scoreUpdateController = StreamController<Map<String, dynamic>>.broadcast();
|
||||||
|
final _quizDoneController = StreamController<void>.broadcast();
|
||||||
|
final _roomClosedController = StreamController<String>.broadcast();
|
||||||
final _errorController = StreamController<String>.broadcast();
|
final _errorController = StreamController<String>.broadcast();
|
||||||
|
|
||||||
|
// Public streams
|
||||||
Stream<Map<String, dynamic>> get roomMessages => _roomMessageController.stream;
|
Stream<Map<String, dynamic>> get roomMessages => _roomMessageController.stream;
|
||||||
|
Stream<Map<String, dynamic>> get questionUpdate => _questionUpdateController.stream;
|
||||||
Stream<Map<String, dynamic>> get chatMessages => _chatMessageController.stream;
|
Stream<Map<String, dynamic>> get chatMessages => _chatMessageController.stream;
|
||||||
Stream<void> get quizStarted => _quizStartedController.stream;
|
Stream<void> get quizStarted => _quizStartedController.stream;
|
||||||
|
Stream<Map<String, dynamic>> get answerSubmitted => _answerSubmittedController.stream;
|
||||||
|
Stream<Map<String, dynamic>> get scoreUpdates => _scoreUpdateController.stream;
|
||||||
|
Stream<void> get quizDone => _quizDoneController.stream;
|
||||||
|
Stream<String> get roomClosed => _roomClosedController.stream;
|
||||||
Stream<String> get errors => _errorController.stream;
|
Stream<String> get errors => _errorController.stream;
|
||||||
|
|
||||||
void initSocketConnection() {
|
void initSocketConnection() {
|
||||||
|
@ -25,48 +36,67 @@ class SocketService {
|
||||||
socket.connect();
|
socket.connect();
|
||||||
|
|
||||||
socket.onConnect((_) {
|
socket.onConnect((_) {
|
||||||
logC.i('Connected: ${socket.id}');
|
logC.i('✅ Connected: ${socket.id}');
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.onDisconnect((_) {
|
socket.onDisconnect((_) {
|
||||||
logC.i('Disconnected');
|
logC.i('❌ Disconnected');
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('connection_response', (data) {
|
socket.on('connection_response', (data) {
|
||||||
logC.i('Connection response: $data');
|
logC.i('🟢 Connection response: $data');
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('room_message', (data) {
|
socket.on('room_message', (data) {
|
||||||
logC.i('Room Message: $data');
|
logC.i('📥 Room Message: $data');
|
||||||
_roomMessageController.add(Map<String, dynamic>.from(data));
|
_roomMessageController.add(Map<String, dynamic>.from(data));
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('receive_message', (data) {
|
socket.on('receive_message', (data) {
|
||||||
logC.i('Message from ${data['from']}: ${data['message']}');
|
logC.i('💬 Chat from ${data['from']}: ${data['message']}');
|
||||||
_chatMessageController.add(Map<String, dynamic>.from(data));
|
_chatMessageController.add(Map<String, dynamic>.from(data));
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('quiz_started', (_) {
|
socket.on('quiz_started', (_) {
|
||||||
logC.i('Quiz has started!');
|
logC.i('🚀 Quiz Started!');
|
||||||
_quizStartedController.add(null);
|
_quizStartedController.add(null);
|
||||||
});
|
});
|
||||||
|
socket.on('quiz_question', (data) {
|
||||||
|
logC.i('🚀 question getted!');
|
||||||
|
_questionUpdateController.add(Map<String, dynamic>.from(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('answer_submitted', (data) {
|
||||||
|
logC.i('✅ Answer Submitted: $data');
|
||||||
|
_answerSubmittedController.add(Map<String, dynamic>.from(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('score_update', (data) {
|
||||||
|
logC.i('📊 Score Update: $data');
|
||||||
|
_scoreUpdateController.add(Map<String, dynamic>.from(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('quiz_done', (_) {
|
||||||
|
logC.i('🏁 Quiz Finished!');
|
||||||
|
_quizDoneController.add(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('room_closed', (data) {
|
||||||
|
logC.i('🔒 Room Closed: $data');
|
||||||
|
_roomClosedController.add(data['room'].toString());
|
||||||
|
});
|
||||||
|
|
||||||
socket.on('error', (data) {
|
socket.on('error', (data) {
|
||||||
logC.i('Socket error: $data');
|
logC.e('⚠️ Socket Error: $data');
|
||||||
_errorController.add(data.toString());
|
_errorController.add(data.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void joinRoom({required String sessionCode, required String userId}) {
|
void joinRoom({required String sessionCode, required String userId}) {
|
||||||
var data = {
|
socket.emit('join_room', {
|
||||||
'session_code': sessionCode,
|
'session_code': sessionCode,
|
||||||
'user_id': userId,
|
'user_id': userId,
|
||||||
};
|
});
|
||||||
print(data);
|
|
||||||
socket.emit(
|
|
||||||
'join_room',
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void leaveRoom({required String sessionId, required String userId, String username = "anonymous"}) {
|
void leaveRoom({required String sessionId, required String userId, String username = "anonymous"}) {
|
||||||
|
@ -89,14 +119,12 @@ class SocketService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit when admin starts the quiz
|
|
||||||
void startQuiz({required String sessionCode}) {
|
void startQuiz({required String sessionCode}) {
|
||||||
socket.emit('start_quiz', {
|
socket.emit('start_quiz', {
|
||||||
'session_code': sessionCode,
|
'session_code': sessionCode,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit user's answer during quiz
|
|
||||||
void sendAnswer({
|
void sendAnswer({
|
||||||
required String sessionId,
|
required String sessionId,
|
||||||
required String userId,
|
required String userId,
|
||||||
|
@ -111,12 +139,8 @@ class SocketService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit when user finishes the quiz
|
void endSession({required String sessionId, required String userId}) {
|
||||||
void doneQuiz({
|
socket.emit('end_session', {
|
||||||
required String sessionId,
|
|
||||||
required String userId,
|
|
||||||
}) {
|
|
||||||
socket.emit('quiz_done', {
|
|
||||||
'session_id': sessionId,
|
'session_id': sessionId,
|
||||||
'user_id': userId,
|
'user_id': userId,
|
||||||
});
|
});
|
||||||
|
@ -127,6 +151,10 @@ class SocketService {
|
||||||
_roomMessageController.close();
|
_roomMessageController.close();
|
||||||
_chatMessageController.close();
|
_chatMessageController.close();
|
||||||
_quizStartedController.close();
|
_quizStartedController.close();
|
||||||
|
_answerSubmittedController.close();
|
||||||
|
_scoreUpdateController.close();
|
||||||
|
_quizDoneController.close();
|
||||||
|
_roomClosedController.close();
|
||||||
_errorController.close();
|
_errorController.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:quiz_app/data/services/socket_service.dart';
|
||||||
|
import 'package:quiz_app/feature/monitor_quiz/controller/monitor_quiz_controller.dart';
|
||||||
|
|
||||||
|
class MonitorQuizBinding extends Bindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
Get.lazyPut<MonitorQuizController>(() => MonitorQuizController(Get.find<SocketService>()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
import 'package:get/get.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';
|
||||||
|
|
||||||
|
class MonitorQuizController extends GetxController {
|
||||||
|
final SocketService _socketService;
|
||||||
|
|
||||||
|
MonitorQuizController(this._socketService);
|
||||||
|
|
||||||
|
String sessionCode = "";
|
||||||
|
String sessionId = "";
|
||||||
|
|
||||||
|
RxString currentQuestion = "".obs;
|
||||||
|
RxList<ParticipantAnswerPoint> participan = <ParticipantAnswerPoint>[].obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
loadData();
|
||||||
|
registerListener();
|
||||||
|
super.onInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadData() {
|
||||||
|
final args = Get.arguments;
|
||||||
|
sessionCode = args["session_code"] ?? "";
|
||||||
|
sessionId = args["session_id"] ?? "";
|
||||||
|
|
||||||
|
final List<UserModel> userList = (args["list_participan"] as List<dynamic>).map((e) => e as UserModel).toList();
|
||||||
|
participan.assignAll(
|
||||||
|
userList.map(
|
||||||
|
(user) => ParticipantAnswerPoint(
|
||||||
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerListener() {
|
||||||
|
_socketService.questionUpdate.listen((data) {
|
||||||
|
logC.i(data);
|
||||||
|
currentQuestion.value = data["question"];
|
||||||
|
});
|
||||||
|
|
||||||
|
_socketService.scoreUpdates.listen((data) {
|
||||||
|
logC.i("📊 Score Update Received: $data");
|
||||||
|
|
||||||
|
final Map<String, dynamic> scoreMap = Map<String, dynamic>.from(data);
|
||||||
|
|
||||||
|
scoreMap.forEach((userId, scoreData) {
|
||||||
|
final index = participan.indexWhere((p) => p.id == userId);
|
||||||
|
|
||||||
|
if (index != -1) {
|
||||||
|
// Participant found, update their scores
|
||||||
|
final participant = participan[index];
|
||||||
|
final correct = scoreData["correct"] ?? 0;
|
||||||
|
final incorrect = scoreData["incorrect"] ?? 0;
|
||||||
|
|
||||||
|
participant.correct.value = correct;
|
||||||
|
participant.wrong.value = incorrect;
|
||||||
|
} else {
|
||||||
|
// Participant not found, optionally add new participant
|
||||||
|
participan.add(
|
||||||
|
ParticipantAnswerPoint(
|
||||||
|
id: userId,
|
||||||
|
name: "Unknown", // Or fetch proper name if available
|
||||||
|
correct: (scoreData["correct"] ?? 0).obs,
|
||||||
|
wrong: (scoreData["incorrect"] ?? 0).obs,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notify observers if needed (optional)
|
||||||
|
participan.refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParticipantAnswerPoint {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final RxInt correct;
|
||||||
|
final RxInt wrong;
|
||||||
|
|
||||||
|
ParticipantAnswerPoint({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
RxInt? correct,
|
||||||
|
RxInt? wrong,
|
||||||
|
}) : correct = correct ?? 0.obs,
|
||||||
|
wrong = wrong ?? 0.obs;
|
||||||
|
}
|
|
@ -1,10 +1,164 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
import 'package:quiz_app/feature/monitor_quiz/controller/monitor_quiz_controller.dart';
|
||||||
|
|
||||||
|
class MonitorQuizView extends GetView<MonitorQuizController> {
|
||||||
|
const MonitorQuizView({super.key});
|
||||||
|
|
||||||
class MonitorQuizView extends StatelessWidget {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Text("monitor quiz admin"),
|
appBar: AppBar(title: const Text('Monitor Quiz')),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Obx(
|
||||||
|
() => _buildCurrentQuestion(questionText: controller.currentQuestion.value),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const Text(
|
||||||
|
'Daftar Peserta:',
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Obx(
|
||||||
|
() => ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: controller.participan.length,
|
||||||
|
itemBuilder: (context, index) => _buildUserRow(
|
||||||
|
name: controller.participan[index].name,
|
||||||
|
totalBenar: controller.participan[index].correct.value,
|
||||||
|
totalSalah: controller.participan[index].wrong.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCurrentQuestion({required String questionText}) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade50,
|
||||||
|
border: Border.all(color: Colors.blue),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"pertanyaan sekarang",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
LucideIcons.helpCircle,
|
||||||
|
color: Colors.blue,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
questionText,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUserRow({
|
||||||
|
required String name,
|
||||||
|
required int totalBenar,
|
||||||
|
required int totalSalah,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.person,
|
||||||
|
size: 30,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
LucideIcons.checkCircle,
|
||||||
|
color: Colors.green,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
'Benar: $totalBenar',
|
||||||
|
style: const TextStyle(color: Colors.green),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
const Icon(
|
||||||
|
LucideIcons.xCircle,
|
||||||
|
color: Colors.red,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
'Salah: $totalSalah',
|
||||||
|
style: const TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:quiz_app/component/global_button.dart';
|
||||||
import 'package:quiz_app/data/controllers/user_controller.dart';
|
import 'package:quiz_app/data/controllers/user_controller.dart';
|
||||||
import 'package:quiz_app/data/services/socket_service.dart';
|
import 'package:quiz_app/data/services/socket_service.dart';
|
||||||
|
|
||||||
|
@ -9,59 +10,71 @@ class PlayQuizMultiplayerController extends GetxController {
|
||||||
|
|
||||||
PlayQuizMultiplayerController(this._socketService, this._userController);
|
PlayQuizMultiplayerController(this._socketService, this._userController);
|
||||||
|
|
||||||
final questions = <MultiplayerQuestionModel>[].obs;
|
// final questions = <MultiplayerQuestionModel>[].obs;
|
||||||
final Rxn<MultiplayerQuestionModel> currentQuestion = Rxn<MultiplayerQuestionModel>();
|
final Rxn<MultiplayerQuestionModel> currentQuestion = Rxn<MultiplayerQuestionModel>();
|
||||||
final currentQuestionIndex = 0.obs;
|
final currentQuestionIndex = 0.obs;
|
||||||
final selectedAnswer = Rxn<String>();
|
final selectedAnswer = Rxn<String>();
|
||||||
final isDone = false.obs;
|
final isDone = false.obs;
|
||||||
|
|
||||||
|
final Rx<ButtonType> buttonType = ButtonType.disabled.obs;
|
||||||
|
|
||||||
final fillInAnswerController = TextEditingController();
|
final fillInAnswerController = TextEditingController();
|
||||||
|
bool? selectedTOFAns;
|
||||||
|
|
||||||
late final String sessionCode;
|
late final String sessionCode;
|
||||||
late final bool isAdmin;
|
late final bool isAdmin;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
|
_loadData();
|
||||||
|
_registerListener();
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadData() {
|
||||||
final args = Get.arguments as Map<String, dynamic>;
|
final args = Get.arguments as Map<String, dynamic>;
|
||||||
sessionCode = args["session_code"];
|
sessionCode = args["session_code"];
|
||||||
isAdmin = args["is_admin"];
|
isAdmin = args["is_admin"];
|
||||||
|
|
||||||
_socketService.socket.on("quiz_question", (data) {
|
|
||||||
final model = MultiplayerQuestionModel.fromJson(Map<String, dynamic>.from(data));
|
|
||||||
currentQuestion.value = model;
|
|
||||||
questions.add(model);
|
|
||||||
fillInAnswerController.clear(); // reset tiap soal baru
|
|
||||||
});
|
|
||||||
|
|
||||||
_socketService.socket.on("quiz_done", (_) {
|
|
||||||
isDone.value = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isAnswerSelected() {
|
_registerListener() {
|
||||||
final type = currentQuestion.value?.type;
|
fillInAnswerController.addListener(() {
|
||||||
if (type == 'fill_in_the_blank') {
|
final text = fillInAnswerController.text;
|
||||||
return fillInAnswerController.text.trim().isNotEmpty;
|
|
||||||
}
|
if (text.isNotEmpty) {
|
||||||
return selectedAnswer.value != null;
|
buttonType.value = ButtonType.primary;
|
||||||
|
} else {
|
||||||
|
buttonType.value = ButtonType.disabled;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_socketService.questionUpdate.listen((data) {
|
||||||
|
buttonType.value = ButtonType.disabled;
|
||||||
|
fillInAnswerController.clear();
|
||||||
|
|
||||||
|
final model = MultiplayerQuestionModel.fromJson(Map<String, dynamic>.from(data));
|
||||||
|
currentQuestion.value = model;
|
||||||
|
// questions.add(model);
|
||||||
|
fillInAnswerController.clear(); // reset tiap soal baru
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectOptionAnswer(String option) {
|
void selectOptionAnswer(String option) {
|
||||||
selectedAnswer.value = option;
|
selectedAnswer.value = option;
|
||||||
|
buttonType.value = ButtonType.primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectTrueFalseAnswer(bool value) {
|
void selectTrueFalseAnswer(bool value) {
|
||||||
selectedAnswer.value = value.toString();
|
selectedAnswer.value = value.toString();
|
||||||
|
buttonType.value = ButtonType.primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
void submitAnswer() {
|
void submitAnswer() {
|
||||||
final question = questions[currentQuestionIndex.value];
|
final question = currentQuestion.value!;
|
||||||
final type = question.type;
|
final type = question.type;
|
||||||
|
|
||||||
String? answer;
|
String? answer;
|
||||||
if (type == 'fill_in_the_blank') {
|
if (type == 'fill_the_blank') {
|
||||||
answer = fillInAnswerController.text.trim();
|
answer = fillInAnswerController.text.trim();
|
||||||
} else {
|
} else {
|
||||||
answer = selectedAnswer.value;
|
answer = selectedAnswer.value;
|
||||||
|
@ -75,18 +88,6 @@ class PlayQuizMultiplayerController extends GetxController {
|
||||||
answer: answer,
|
answer: answer,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentQuestionIndex.value < questions.length - 1) {
|
|
||||||
currentQuestionIndex.value++;
|
|
||||||
selectedAnswer.value = null;
|
|
||||||
fillInAnswerController.clear();
|
|
||||||
} else {
|
|
||||||
isDone.value = true;
|
|
||||||
_socketService.doneQuiz(
|
|
||||||
sessionId: sessionCode,
|
|
||||||
userId: _userController.userData!.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -100,21 +101,24 @@ class MultiplayerQuestionModel {
|
||||||
final int questionIndex;
|
final int questionIndex;
|
||||||
final String question;
|
final String question;
|
||||||
final String type; // 'option', 'true_false', 'fill_in_the_blank'
|
final String type; // 'option', 'true_false', 'fill_in_the_blank'
|
||||||
final List<String> options;
|
final int duration;
|
||||||
|
final List<String>? options;
|
||||||
|
|
||||||
MultiplayerQuestionModel({
|
MultiplayerQuestionModel({
|
||||||
required this.questionIndex,
|
required this.questionIndex,
|
||||||
required this.question,
|
required this.question,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.options,
|
required this.duration,
|
||||||
|
this.options,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory MultiplayerQuestionModel.fromJson(Map<String, dynamic> json) {
|
factory MultiplayerQuestionModel.fromJson(Map<String, dynamic> json) {
|
||||||
return MultiplayerQuestionModel(
|
return MultiplayerQuestionModel(
|
||||||
questionIndex: json['question_index'],
|
questionIndex: json['index'],
|
||||||
question: json['question'],
|
question: json['question'],
|
||||||
type: json['type'],
|
type: json['type'],
|
||||||
options: List<String>.from(json['options']),
|
duration: json['duration'],
|
||||||
|
options: json['options'] != null ? List<String>.from(json['options']) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:quiz_app/component/global_button.dart';
|
||||||
import 'package:quiz_app/component/global_text_field.dart';
|
import 'package:quiz_app/component/global_text_field.dart';
|
||||||
import 'package:quiz_app/feature/play_quiz_multiplayer/controller/play_quiz_controller.dart';
|
import 'package:quiz_app/feature/play_quiz_multiplayer/controller/play_quiz_controller.dart';
|
||||||
|
|
||||||
|
@ -12,25 +13,17 @@ class PlayQuizMultiplayerView extends GetView<PlayQuizMultiplayerController> {
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: Obx(() {
|
title: Text(
|
||||||
if (controller.questions.isEmpty) {
|
"Soal ${(controller.currentQuestion.value?.questionIndex ?? 0) + 1}/10",
|
||||||
return const Text(
|
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
|
||||||
"Loading...",
|
),
|
||||||
style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Text(
|
|
||||||
"Soal ${controller.currentQuestionIndex.value + 1}/${controller.questions.length}",
|
|
||||||
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
body: Obx(() {
|
body: Obx(() {
|
||||||
if (controller.isDone.value) {
|
if (controller.isDone.value) {
|
||||||
return _buildDoneView();
|
return _buildDoneView();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controller.questions.isEmpty) {
|
if (controller.currentQuestion.value == null) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,25 +45,16 @@ class PlayQuizMultiplayerView extends GetView<PlayQuizMultiplayerController> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (question.type == 'option') _buildOptionQuestion(),
|
if (question.type == 'option') _buildOptionQuestion(),
|
||||||
if (question.type == 'fill_in_the_blank') _buildFillInBlankQuestion(),
|
if (question.type == 'fill_the_blank') _buildFillInBlankQuestion(),
|
||||||
if (question.type == 'true_false') _buildTrueFalseQuestion(),
|
if (question.type == 'true_false') _buildTrueFalseQuestion(),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
SizedBox(
|
Obx(
|
||||||
width: double.infinity,
|
() => GlobalButton(
|
||||||
child: ElevatedButton(
|
text: "Kirim jawaban",
|
||||||
onPressed: controller.isAnswerSelected() ? controller.submitAnswer : null,
|
onPressed: controller.submitAnswer,
|
||||||
style: ElevatedButton.styleFrom(
|
type: controller.buttonType.value,
|
||||||
backgroundColor: controller.isAnswerSelected() ? const Color(0xFF2563EB) : Colors.grey,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
minimumSize: const Size(double.infinity, 50),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
"Kirim Jawaban",
|
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -79,7 +63,7 @@ class PlayQuizMultiplayerView extends GetView<PlayQuizMultiplayerController> {
|
||||||
Widget _buildOptionQuestion() {
|
Widget _buildOptionQuestion() {
|
||||||
final options = controller.currentQuestion.value!.options;
|
final options = controller.currentQuestion.value!.options;
|
||||||
return Column(
|
return Column(
|
||||||
children: List.generate(options.length, (index) {
|
children: List.generate(options!.length, (index) {
|
||||||
final option = options[index];
|
final option = options[index];
|
||||||
final isSelected = controller.selectedAnswer.value == option;
|
final isSelected = controller.selectedAnswer.value == option;
|
||||||
|
|
||||||
|
@ -121,7 +105,7 @@ class PlayQuizMultiplayerView extends GetView<PlayQuizMultiplayerController> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTrueFalseButton(String label, bool value) {
|
Widget _buildTrueFalseButton(String label, bool value) {
|
||||||
final isSelected = controller.selectedAnswer.value == value.toString();
|
final isSelected = controller.selectedTOFAns = value;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
|
|
@ -71,12 +71,7 @@ class WaitingRoomController extends GetxController {
|
||||||
_socketService.quizStarted.listen((_) {
|
_socketService.quizStarted.listen((_) {
|
||||||
isQuizStarted.value = true;
|
isQuizStarted.value = true;
|
||||||
Get.snackbar("Info", "Kuis telah dimulai");
|
Get.snackbar("Info", "Kuis telah dimulai");
|
||||||
if (isAdmin.value) {
|
if (!isAdmin.value) {
|
||||||
Get.offAllNamed(AppRoutes.monitorQuizMPLPage, arguments: {
|
|
||||||
"session_code": sessionCode.value,
|
|
||||||
"is_admin": isAdmin.value,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Get.offAllNamed(AppRoutes.playQuizMPLPage, arguments: {
|
Get.offAllNamed(AppRoutes.playQuizMPLPage, arguments: {
|
||||||
"session_code": sessionCode.value,
|
"session_code": sessionCode.value,
|
||||||
"is_admin": isAdmin.value,
|
"is_admin": isAdmin.value,
|
||||||
|
@ -98,6 +93,11 @@ class WaitingRoomController extends GetxController {
|
||||||
|
|
||||||
void startQuiz() {
|
void startQuiz() {
|
||||||
_socketService.startQuiz(sessionCode: sessionCode.value);
|
_socketService.startQuiz(sessionCode: sessionCode.value);
|
||||||
|
Get.offAllNamed(AppRoutes.monitorQuizMPLPage, arguments: {
|
||||||
|
"session_code": sessionCode.value,
|
||||||
|
"is_admin": isAdmin.value,
|
||||||
|
"list_participan": joinedUsers.toList(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void leaveRoom() async {
|
void leaveRoom() async {
|
||||||
|
|
Loading…
Reference in New Issue