feat: preparaiion on socket connection
This commit is contained in:
parent
edb7ab0fdf
commit
dda268d2d5
|
@ -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";
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -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>(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue