feat: preparaiion on socket connection

This commit is contained in:
akhdanre 2025-05-07 18:07:24 +07:00
parent edb7ab0fdf
commit dda268d2d5
5 changed files with 152 additions and 26 deletions

View File

@ -1,5 +1,6 @@
class APIEndpoint { class APIEndpoint {
static const String baseUrl = "http://192.168.1.9:5000"; // static const String baseUrl = "http://192.168.1.9:5000";
static const String baseUrl = "http://172.16.106.133:5000";
static const String api = "$baseUrl/api"; static const String api = "$baseUrl/api";
static const String login = "/login"; static const String login = "/login";

View File

@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.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';
@ -14,6 +15,8 @@ class PlayQuizMultiplayerController extends GetxController {
final selectedAnswer = Rxn<String>(); final selectedAnswer = Rxn<String>();
final isDone = false.obs; final isDone = false.obs;
final fillInAnswerController = TextEditingController();
late final String sessionCode; late final String sessionCode;
late final bool isAdmin; late final bool isAdmin;
@ -29,6 +32,7 @@ class PlayQuizMultiplayerController extends GetxController {
final model = MultiplayerQuestionModel.fromJson(Map<String, dynamic>.from(data)); final model = MultiplayerQuestionModel.fromJson(Map<String, dynamic>.from(data));
currentQuestion.value = model; currentQuestion.value = model;
questions.add(model); questions.add(model);
fillInAnswerController.clear(); // reset tiap soal baru
}); });
_socketService.socket.on("quiz_done", (_) { _socketService.socket.on("quiz_done", (_) {
@ -36,11 +40,34 @@ class PlayQuizMultiplayerController extends GetxController {
}); });
} }
bool isAnswerSelected() {
final type = currentQuestion.value?.type;
if (type == 'fill_in_the_blank') {
return fillInAnswerController.text.trim().isNotEmpty;
}
return selectedAnswer.value != null;
}
void selectOptionAnswer(String option) {
selectedAnswer.value = option;
}
void selectTrueFalseAnswer(bool value) {
selectedAnswer.value = value.toString();
}
void submitAnswer() { void submitAnswer() {
final question = questions[currentQuestionIndex.value]; final question = questions[currentQuestionIndex.value];
final answer = selectedAnswer.value; final type = question.type;
if (answer != null) { String? answer;
if (type == 'fill_in_the_blank') {
answer = fillInAnswerController.text.trim();
} else {
answer = selectedAnswer.value;
}
if (answer != null && answer.isNotEmpty) {
_socketService.sendAnswer( _socketService.sendAnswer(
sessionId: sessionCode, sessionId: sessionCode,
userId: _userController.userData!.id, userId: _userController.userData!.id,
@ -52,6 +79,7 @@ class PlayQuizMultiplayerController extends GetxController {
if (currentQuestionIndex.value < questions.length - 1) { if (currentQuestionIndex.value < questions.length - 1) {
currentQuestionIndex.value++; currentQuestionIndex.value++;
selectedAnswer.value = null; selectedAnswer.value = null;
fillInAnswerController.clear();
} else { } else {
isDone.value = true; isDone.value = true;
_socketService.doneQuiz( _socketService.doneQuiz(
@ -60,16 +88,24 @@ class PlayQuizMultiplayerController extends GetxController {
); );
} }
} }
@override
void onClose() {
fillInAnswerController.dispose();
super.onClose();
}
} }
class MultiplayerQuestionModel { 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 List<String> options; final List<String> options;
MultiplayerQuestionModel({ MultiplayerQuestionModel({
required this.questionIndex, required this.questionIndex,
required this.question, required this.question,
required this.type,
required this.options, required this.options,
}); });
@ -77,6 +113,7 @@ class MultiplayerQuestionModel {
return MultiplayerQuestionModel( return MultiplayerQuestionModel(
questionIndex: json['question_index'], questionIndex: json['question_index'],
question: json['question'], question: json['question'],
type: json['type'],
options: List<String>.from(json['options']), options: List<String>.from(json['options']),
); );
} }
@ -85,6 +122,7 @@ class MultiplayerQuestionModel {
return { return {
'question_index': questionIndex, 'question_index': questionIndex,
'question': question, 'question': question,
'type': type,
'options': options, 'options': options,
}; };
} }

View File

@ -1,17 +1,28 @@
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_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';
class PlayQuizMultiplayerView extends GetView<PlayQuizMultiplayerController> { class PlayQuizMultiplayerView extends GetView<PlayQuizMultiplayerController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF9FAFB),
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
title: Obx(() { title: Obx(() {
if (controller.questions.isEmpty) { if (controller.questions.isEmpty) {
return const Text("Loading..."); return const Text(
"Loading...",
style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
);
} }
return Text("Soal ${controller.currentQuestionIndex.value + 1}/${controller.questions.length}"); return Text(
"Soal ${controller.currentQuestionIndex.value + 1}/${controller.questions.length}",
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
);
}), }),
), ),
body: Obx(() { body: Obx(() {
@ -29,28 +40,36 @@ class PlayQuizMultiplayerView extends GetView<PlayQuizMultiplayerController> {
} }
Widget _buildQuestionView() { Widget _buildQuestionView() {
final question = controller.currentQuestion.value!;
return Padding( return Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
controller.currentQuestion.value!.question, question.question,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
...controller.currentQuestion.value!.options.map((option) => RadioListTile<String>( if (question.type == 'option') _buildOptionQuestion(),
title: Text(option), if (question.type == 'fill_in_the_blank') _buildFillInBlankQuestion(),
value: option, if (question.type == 'true_false') _buildTrueFalseQuestion(),
groupValue: controller.selectedAnswer.value,
onChanged: (value) => controller.selectedAnswer.value = value,
)),
const Spacer(), const Spacer(),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: controller.selectedAnswer.value == null ? null : controller.submitAnswer, onPressed: controller.isAnswerSelected() ? controller.submitAnswer : null,
child: const Text("Kirim Jawaban"), style: ElevatedButton.styleFrom(
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),
),
), ),
), ),
], ],
@ -58,8 +77,74 @@ class PlayQuizMultiplayerView extends GetView<PlayQuizMultiplayerController> {
); );
} }
Widget _buildOptionQuestion() {
final options = controller.currentQuestion.value!.options;
return Column(
children: List.generate(options.length, (index) {
final option = options[index];
final isSelected = controller.selectedAnswer.value == option;
return Container(
margin: const EdgeInsets.only(bottom: 12),
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: isSelected ? const Color(0xFF2563EB) : Colors.white,
foregroundColor: isSelected ? Colors.white : Colors.black,
side: const BorderSide(color: Colors.grey),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () => controller.selectOptionAnswer(option),
child: Text(option),
),
);
}),
);
}
Widget _buildFillInBlankQuestion() {
return Column(
children: [
GlobalTextField(controller: controller.fillInAnswerController),
const SizedBox(height: 20),
],
);
}
Widget _buildTrueFalseQuestion() {
return Column(
children: [
_buildTrueFalseButton('Ya', true),
_buildTrueFalseButton('Tidak', false),
],
);
}
Widget _buildTrueFalseButton(String label, bool value) {
final isSelected = controller.selectedAnswer.value == value.toString();
return Container(
margin: const EdgeInsets.only(bottom: 12),
width: double.infinity,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: isSelected ? (value ? Colors.green : Colors.red) : Colors.white,
foregroundColor: isSelected ? Colors.white : Colors.black,
side: const BorderSide(color: Colors.grey),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () => controller.selectTrueFalseAnswer(value),
icon: Icon(value ? Icons.check_circle_outline : Icons.cancel_outlined),
label: Text(label),
),
);
}
Widget _buildDoneView() { Widget _buildDoneView() {
return Center( return Padding(
padding: const EdgeInsets.all(20),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -69,8 +154,16 @@ class PlayQuizMultiplayerView extends GetView<PlayQuizMultiplayerController> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
ElevatedButton( ElevatedButton(
onPressed: () {}, onPressed: () {
child: const Text("Lihat Hasil"), // Arahkan ke halaman hasil atau leaderboard
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2563EB),
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 50),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: const Text("Lihat Hasil", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
), ),
], ],
), ),

View File

@ -1,5 +1,4 @@
import 'package:get/get.dart'; import 'package:get/get.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';
import 'package:quiz_app/feature/waiting_room/controller/waiting_room_controller.dart'; import 'package:quiz_app/feature/waiting_room/controller/waiting_room_controller.dart';
@ -7,9 +6,6 @@ class WaitingRoomBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {
if (!Get.isRegistered<SocketService>()) Get.put(SocketService()); if (!Get.isRegistered<SocketService>()) Get.put(SocketService());
Get.lazyPut<WaitingRoomController>(() => WaitingRoomController( Get.lazyPut<WaitingRoomController>(() => WaitingRoomController(Get.find<SocketService>()));
Get.find<SocketService>(),
Get.find<UserController>(),
));
} }
} }

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:quiz_app/app/routes/app_pages.dart'; import 'package:quiz_app/app/routes/app_pages.dart';
import 'package:quiz_app/data/controllers/user_controller.dart';
import 'package:quiz_app/data/dto/waiting_room_dto.dart'; import 'package:quiz_app/data/dto/waiting_room_dto.dart';
import 'package:quiz_app/data/models/quiz/quiz_listing_model.dart'; import 'package:quiz_app/data/models/quiz/quiz_listing_model.dart';
import 'package:quiz_app/data/models/session/session_response_model.dart'; import 'package:quiz_app/data/models/session/session_response_model.dart';
@ -11,8 +10,7 @@ import 'package:quiz_app/data/services/socket_service.dart';
class WaitingRoomController extends GetxController { class WaitingRoomController extends GetxController {
final SocketService _socketService; final SocketService _socketService;
final UserController _userController; WaitingRoomController(this._socketService);
WaitingRoomController(this._socketService, this._userController);
final sessionCode = ''.obs; final sessionCode = ''.obs;
final quizMeta = Rx<QuizListingModel?>(null); final quizMeta = Rx<QuizListingModel?>(null);