diff --git a/lib/core/endpoint/api_endpoint.dart b/lib/core/endpoint/api_endpoint.dart index 4611a3d..d518cfb 100644 --- a/lib/core/endpoint/api_endpoint.dart +++ b/lib/core/endpoint/api_endpoint.dart @@ -1,5 +1,6 @@ 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 login = "/login"; diff --git a/lib/feature/play_quiz_multiplayer/controller/play_quiz_controller.dart b/lib/feature/play_quiz_multiplayer/controller/play_quiz_controller.dart index a8356d9..204ab4b 100644 --- a/lib/feature/play_quiz_multiplayer/controller/play_quiz_controller.dart +++ b/lib/feature/play_quiz_multiplayer/controller/play_quiz_controller.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:quiz_app/data/controllers/user_controller.dart'; import 'package:quiz_app/data/services/socket_service.dart'; @@ -14,6 +15,8 @@ class PlayQuizMultiplayerController extends GetxController { final selectedAnswer = Rxn(); final isDone = false.obs; + final fillInAnswerController = TextEditingController(); + late final String sessionCode; late final bool isAdmin; @@ -29,6 +32,7 @@ class PlayQuizMultiplayerController extends GetxController { final model = MultiplayerQuestionModel.fromJson(Map.from(data)); currentQuestion.value = model; questions.add(model); + fillInAnswerController.clear(); // reset tiap soal baru }); _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() { 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( sessionId: sessionCode, userId: _userController.userData!.id, @@ -52,6 +79,7 @@ class PlayQuizMultiplayerController extends GetxController { if (currentQuestionIndex.value < questions.length - 1) { currentQuestionIndex.value++; selectedAnswer.value = null; + fillInAnswerController.clear(); } else { isDone.value = true; _socketService.doneQuiz( @@ -60,16 +88,24 @@ class PlayQuizMultiplayerController extends GetxController { ); } } + + @override + void onClose() { + fillInAnswerController.dispose(); + super.onClose(); + } } class MultiplayerQuestionModel { final int questionIndex; final String question; + final String type; // 'option', 'true_false', 'fill_in_the_blank' final List options; MultiplayerQuestionModel({ required this.questionIndex, required this.question, + required this.type, required this.options, }); @@ -77,6 +113,7 @@ class MultiplayerQuestionModel { return MultiplayerQuestionModel( questionIndex: json['question_index'], question: json['question'], + type: json['type'], options: List.from(json['options']), ); } @@ -85,6 +122,7 @@ class MultiplayerQuestionModel { return { 'question_index': questionIndex, 'question': question, + 'type': type, 'options': options, }; } diff --git a/lib/feature/play_quiz_multiplayer/view/play_quiz_multiplayer.dart b/lib/feature/play_quiz_multiplayer/view/play_quiz_multiplayer.dart index 8ee2ad7..9055503 100644 --- a/lib/feature/play_quiz_multiplayer/view/play_quiz_multiplayer.dart +++ b/lib/feature/play_quiz_multiplayer/view/play_quiz_multiplayer.dart @@ -1,17 +1,28 @@ import 'package:flutter/material.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'; class PlayQuizMultiplayerView extends GetView { @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: const Color(0xFFF9FAFB), appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + centerTitle: true, title: Obx(() { 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(() { @@ -29,28 +40,36 @@ class PlayQuizMultiplayerView extends GetView { } Widget _buildQuestionView() { + final question = controller.currentQuestion.value!; return Padding( padding: const EdgeInsets.all(20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - controller.currentQuestion.value!.question, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + question.question, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black), ), const SizedBox(height: 20), - ...controller.currentQuestion.value!.options.map((option) => RadioListTile( - title: Text(option), - value: option, - groupValue: controller.selectedAnswer.value, - onChanged: (value) => controller.selectedAnswer.value = value, - )), + if (question.type == 'option') _buildOptionQuestion(), + if (question.type == 'fill_in_the_blank') _buildFillInBlankQuestion(), + if (question.type == 'true_false') _buildTrueFalseQuestion(), + const Spacer(), SizedBox( width: double.infinity, child: ElevatedButton( - onPressed: controller.selectedAnswer.value == null ? null : controller.submitAnswer, - child: const Text("Kirim Jawaban"), + onPressed: controller.isAnswerSelected() ? controller.submitAnswer : null, + 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 { ); } + 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() { - return Center( + return Padding( + padding: const EdgeInsets.all(20), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -69,8 +154,16 @@ class PlayQuizMultiplayerView extends GetView { ), const SizedBox(height: 16), ElevatedButton( - onPressed: () {}, - child: const Text("Lihat Hasil"), + onPressed: () { + // 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)), ), ], ), diff --git a/lib/feature/waiting_room/binding/waiting_room_binding.dart b/lib/feature/waiting_room/binding/waiting_room_binding.dart index 1fb5fbc..ccd1b1c 100644 --- a/lib/feature/waiting_room/binding/waiting_room_binding.dart +++ b/lib/feature/waiting_room/binding/waiting_room_binding.dart @@ -1,5 +1,4 @@ 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/feature/waiting_room/controller/waiting_room_controller.dart'; @@ -7,9 +6,6 @@ class WaitingRoomBinding extends Bindings { @override void dependencies() { if (!Get.isRegistered()) Get.put(SocketService()); - Get.lazyPut(() => WaitingRoomController( - Get.find(), - Get.find(), - )); + Get.lazyPut(() => WaitingRoomController(Get.find())); } } diff --git a/lib/feature/waiting_room/controller/waiting_room_controller.dart b/lib/feature/waiting_room/controller/waiting_room_controller.dart index e177e4f..856231c 100644 --- a/lib/feature/waiting_room/controller/waiting_room_controller.dart +++ b/lib/feature/waiting_room/controller/waiting_room_controller.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.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/models/quiz/quiz_listing_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 { final SocketService _socketService; - final UserController _userController; - WaitingRoomController(this._socketService, this._userController); + WaitingRoomController(this._socketService); final sessionCode = ''.obs; final quizMeta = Rx(null);