fix: join system

This commit is contained in:
akhdanre 2025-05-17 16:20:18 +07:00
parent 1cce1aba2c
commit 81d900878f
9 changed files with 213 additions and 36 deletions

View File

@ -1,8 +1,17 @@
import 'package:quiz_app/data/models/quiz/quiz_info_model.dart';
import 'package:quiz_app/data/models/session/session_info_model.dart';
import 'package:quiz_app/data/models/session/session_response_model.dart';
class WaitingRoomDTO {
final bool isAdmin;
final SessionResponseModel data;
final SessionInfo sessionInfo;
final QuizInfo quizInfo;
WaitingRoomDTO(this.isAdmin, this.data);
WaitingRoomDTO({
required this.isAdmin,
required this.data,
required this.sessionInfo,
required this.quizInfo,
});
}

View File

@ -0,0 +1,31 @@
class QuizInfo {
final String title;
final String description;
final int totalQuiz;
final int limitDuration;
QuizInfo({
required this.title,
required this.description,
required this.totalQuiz,
required this.limitDuration,
});
factory QuizInfo.fromJson(Map<String, dynamic> json) {
return QuizInfo(
title: json['title'],
description: json['description'],
totalQuiz: json['total_quiz'],
limitDuration: json['limit_duration'],
);
}
Map<String, dynamic> toJson() {
return {
'title': title,
'description': description,
'question_count': totalQuiz,
'limit_duration': limitDuration,
};
}
}

View File

@ -0,0 +1,61 @@
import 'package:quiz_app/data/models/user/user_model.dart';
class SessionInfo {
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<UserModel> participants;
final int currentQuestionIndex;
SessionInfo({
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 SessionInfo.fromJson(Map<String, dynamic> json) {
return SessionInfo(
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'],
participants: (json['participants'] as List<dynamic>?)?.map((e) => UserModel.fromJson(e as Map<String, dynamic>)).toList() ?? [],
currentQuestionIndex: json['current_question_index'],
);
}
Map<String, dynamic> 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,
'participant_limit': participantLimit,
'participants': participants,
'current_question_index': currentQuestionIndex,
};
}
}

View File

@ -1,11 +1,22 @@
class UserModel {
final String id;
final String name;
final String username;
final String userPic;
final DateTime joinedAt;
UserModel({
required this.id,
required this.name,
required this.username,
required this.userPic,
required this.joinedAt,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'],
username: json['username'],
userPic: json['user_pic'] ?? "",
joinedAt: DateTime.parse(json['joined_at']),
);
}
}

View File

@ -1,8 +1,11 @@
import 'package:flutter/material.dart';
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/controllers/user_controller.dart';
import 'package:quiz_app/data/dto/waiting_room_dto.dart';
import 'package:quiz_app/data/models/quiz/quiz_info_model.dart';
import 'package:quiz_app/data/models/session/session_info_model.dart';
import 'package:quiz_app/data/models/session/session_response_model.dart';
import 'package:quiz_app/data/services/socket_service.dart';
@ -29,7 +32,30 @@ class JoinRoomController extends GetxController {
_socketService.initSocketConnection();
_socketService.joinRoom(sessionCode: code, userId: _userController.userData!.id);
Get.toNamed(AppRoutes.waitRoomPage, arguments: WaitingRoomDTO(false, SessionResponseModel(sessionId: "", sessionCode: code)));
_socketService.errors.listen((error) {
logC.i(error);
});
_socketService.roomMessages.listen((data) {
if (data["type"] == "join") {
final Map<String, dynamic> dataPayload = data["data"];
final Map<String, dynamic> sessionInfoJson = dataPayload["session_info"];
final Map<String, dynamic> quizInfoJson = dataPayload["quiz_info"];
Get.toNamed(
AppRoutes.waitRoomPage,
arguments: WaitingRoomDTO(
isAdmin: false,
data: SessionResponseModel(
sessionId: sessionInfoJson["id"],
sessionCode: sessionInfoJson["session_code"],
),
sessionInfo: SessionInfo.fromJson(sessionInfoJson),
quizInfo: QuizInfo.fromJson(quizInfoJson),
),
);
}
});
}
@override

View File

@ -31,7 +31,7 @@ class MonitorQuizController extends GetxController {
userList.map(
(user) => ParticipantAnswerPoint(
id: user.id,
name: user.name,
name: user.username,
),
),
);

View File

@ -4,8 +4,11 @@ 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/base/base_model.dart';
import 'package:quiz_app/data/models/quiz/quiz_info_model.dart';
import 'package:quiz_app/data/models/quiz/quiz_listing_model.dart';
import 'package:quiz_app/data/models/session/session_info_model.dart';
import 'package:quiz_app/data/models/session/session_request_model.dart';
import 'package:quiz_app/data/models/session/session_response_model.dart';
import 'package:quiz_app/data/services/quiz_service.dart';
import 'package:quiz_app/data/services/session_service.dart';
import 'package:quiz_app/data/services/socket_service.dart';
@ -66,7 +69,26 @@ class RoomMakerController extends GetxController {
_socketService.initSocketConnection();
_socketService.joinRoom(sessionCode: response.data!.sessionCode, userId: _userController.userData!.id);
Get.toNamed(AppRoutes.waitRoomPage, arguments: WaitingRoomDTO(true, response.data!));
_socketService.roomMessages.listen((data) {
if (data["type"] == "join") {
final Map<String, dynamic> dataPayload = data["data"];
final Map<String, dynamic> sessionInfoJson = dataPayload["session_info"];
final Map<String, dynamic> quizInfoJson = dataPayload["quiz_info"];
Get.toNamed(
AppRoutes.waitRoomPage,
arguments: WaitingRoomDTO(
isAdmin: true,
data: SessionResponseModel(
sessionId: sessionInfoJson["id"],
sessionCode: sessionInfoJson["session_code"],
),
sessionInfo: SessionInfo.fromJson(sessionInfoJson),
quizInfo: QuizInfo.fromJson(quizInfoJson),
),
);
}
});
}
}

View File

@ -5,7 +5,7 @@ import 'package:quiz_app/app/routes/app_pages.dart';
import 'package:quiz_app/core/utils/custom_notification.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/quiz/quiz_info_model.dart';
import 'package:quiz_app/data/models/session/session_response_model.dart';
import 'package:quiz_app/data/models/user/user_model.dart';
import 'package:quiz_app/data/services/socket_service.dart';
@ -16,7 +16,7 @@ class WaitingRoomController extends GetxController {
WaitingRoomController(this._socketService, this._userController);
final sessionCode = ''.obs;
final quizMeta = Rx<QuizListingModel?>(null);
final quizMeta = Rx<QuizInfo?>(null);
final joinedUsers = <UserModel>[].obs;
final isAdmin = true.obs;
@ -39,32 +39,44 @@ class WaitingRoomController extends GetxController {
sessionCode.value = roomData!.sessionCode;
quizMeta.value = QuizListingModel(
quizId: "q123",
authorId: "a123",
authorName: "Admin",
title: "Uji Coba Kuis",
description: "Kuis untuk testing",
date: DateTime.now().toIso8601String(),
totalQuiz: 5,
duration: 900,
);
quizMeta.value = data.quizInfo;
joinedUsers.assignAll(data.sessionInfo.participants);
}
void _registerSocketListeners() {
_socketService.roomMessages.listen((data) {
if (data["type"] == "join") {
final user = data["data"];
if (user != null) {
joinedUsers.assign(UserModel(id: user['user_id'], name: user['username']));
CustomNotification.success(title: "Participan Joined", message: "${user['username']} has joined to room");
if (data["type"] == "participan_join" || data["type"] == "participan_leave") {
joinedUsers.clear();
final dataPayload = data["data"];
final participants = dataPayload["participants"] as List<dynamic>?;
if (participants != null && participants.isNotEmpty) {
final users = participants.map((e) => UserModel.fromJson(e as Map<String, dynamic>)).toList();
joinedUsers.addAll(users);
if (data["type"] == "participan_join") {
CustomNotification.success(
title: "Participant Joined",
message: data["message"] ?? "A participant has joined the room.",
);
} else if (data["type"] == "participan_leave") {
CustomNotification.warning(
title: "Participant Left",
message: data["message"] ?? "A participant has left the room.",
);
}
}
}
if (data["type"] == "leave") {
final userId = data["data"];
CustomNotification.warning(title: "Participan Leave", message: "participan leave the room");
joinedUsers.removeWhere((e) => e.id == userId);
CustomNotification.warning(
title: "Participant Leave",
message: "Participant left the room",
);
// joinedUsers.removeWhere((e) => e.id == userId);
}
});
@ -101,7 +113,11 @@ class WaitingRoomController extends GetxController {
}
void leaveRoom() async {
_socketService.leaveRoom(sessionId: roomData!.sessionId, userId: _userController.userData!.id);
_socketService.leaveRoom(
sessionId: roomData!.sessionId,
userId: _userController.userData!.id,
username: _userController.userName.value,
);
Get.offAllNamed(AppRoutes.mainPage);
await Future.delayed(Duration(seconds: 2));

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app/component/global_button.dart';
import 'package:quiz_app/data/models/quiz/quiz_info_model.dart';
import 'package:quiz_app/data/models/user/user_model.dart';
import 'package:quiz_app/feature/waiting_room/controller/waiting_room_controller.dart';
import 'package:quiz_app/app/const/colors/app_colors.dart';
@ -21,7 +22,7 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildQuizMeta(quiz),
_buildQuizMeta(quiz!),
const SizedBox(height: 20),
_buildSessionCode(context, session),
const SizedBox(height: 20),
@ -70,8 +71,8 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
);
}
Widget _buildQuizMeta(dynamic quiz) {
if (quiz == null) return const SizedBox.shrink();
Widget _buildQuizMeta(QuizInfo quiz) {
// if (quiz == null) return const SizedBox.shrink();
return Container(
padding: const EdgeInsets.all(16),
width: double.infinity,
@ -88,7 +89,7 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
Text("Judul: ${quiz.title}"),
Text("Deskripsi: ${quiz.description}"),
Text("Jumlah Soal: ${quiz.totalQuiz}"),
Text("Durasi: ${quiz.duration ~/ 60} menit"),
Text("Durasi: ${quiz.limitDuration ~/ 60} menit"),
],
),
);
@ -109,9 +110,9 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
),
child: Row(
children: [
CircleAvatar(child: Text(user.name[0])),
CircleAvatar(child: Text(user.username[0])),
const SizedBox(width: 12),
Text(user.name, style: const TextStyle(fontSize: 16)),
Text(user.username, style: const TextStyle(fontSize: 16)),
],
),
);