feat: adding leave page in the waiting room

This commit is contained in:
akhdanre 2025-05-12 22:41:14 +07:00
parent da6597f42b
commit 5f54ca6c8c
9 changed files with 60 additions and 16 deletions

View File

@ -11,7 +11,7 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Quiz App',
locale: Get.locale ?? context.locale, // 🔁 This ensures GetX reacts to locale changes
locale: Get.locale ?? context.locale,
fallbackLocale: const Locale('en', 'US'),
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,

View File

@ -6,11 +6,13 @@ class GlobalButton extends StatelessWidget {
final VoidCallback? onPressed;
final String text;
final ButtonType type;
final Color baseColor;
const GlobalButton({
super.key,
required this.text,
required this.onPressed,
this.baseColor = const Color(0xFF0052CC),
this.type = ButtonType.primary,
});
@ -24,12 +26,12 @@ class GlobalButton extends StatelessWidget {
switch (type) {
case ButtonType.primary:
backgroundColor = const Color(0xFF0052CC);
backgroundColor = baseColor;
foregroundColor = Colors.white;
break;
case ButtonType.secondary:
backgroundColor = Colors.white;
foregroundColor = const Color(0xFF0052CC);
foregroundColor = baseColor;
borderColor = const Color(0xFF0052CC);
break;
case ButtonType.disabled:

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class GlobalTextField extends StatelessWidget {
final TextEditingController controller;
@ -10,6 +9,7 @@ class GlobalTextField extends StatelessWidget {
final bool obscureText;
final VoidCallback? onToggleVisibility;
final TextInputType textInputType;
final bool forceUpperCase;
const GlobalTextField(
{super.key,
@ -20,6 +20,7 @@ class GlobalTextField extends StatelessWidget {
this.isPassword = false,
this.obscureText = false,
this.onToggleVisibility,
this.forceUpperCase = false,
this.textInputType = TextInputType.text});
@override
@ -28,7 +29,8 @@ class GlobalTextField extends StatelessWidget {
controller: controller,
keyboardType: textInputType,
obscureText: isPassword ? obscureText : false,
maxLines: limitTextLine, // <-- ini tambahan dari limitTextLine
maxLines: limitTextLine,
textCapitalization: forceUpperCase ? TextCapitalization.characters : TextCapitalization.none,
decoration: InputDecoration(
labelText: labelText,
labelStyle: const TextStyle(

View File

@ -58,15 +58,21 @@ class SocketService {
}
void joinRoom({required String sessionCode, required String userId}) {
socket.emit('join_room', {
var data = {
'session_code': sessionCode,
'user_id': userId,
});
};
print(data);
socket.emit(
'join_room',
data,
);
}
void leaveRoom({required String sessionId, String username = "anonymous"}) {
void leaveRoom({required String sessionId, required String userId, String username = "anonymous"}) {
socket.emit('leave_room', {
'session_id': sessionId,
'user_id': userId,
'username': username,
});
}

View File

@ -27,6 +27,7 @@ class JoinRoomController extends GetxController {
return;
}
_socketService.initSocketConnection();
_socketService.joinRoom(sessionCode: code, userId: _userController.userData!.id);
Get.toNamed(AppRoutes.waitRoomPage, arguments: WaitingRoomDTO(false, SessionResponseModel(sessionId: "", sessionCode: code)));
}

View File

@ -54,6 +54,7 @@ class JoinRoomView extends GetView<JoinRoomController> {
controller: controller.codeController,
hintText: context.tr("room_code_hint"),
textInputType: TextInputType.text,
forceUpperCase: true,
),
const SizedBox(height: 30),
GlobalButton(

View File

@ -1,4 +1,5 @@
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';
@ -6,6 +7,9 @@ class WaitingRoomBinding extends Bindings {
@override
void dependencies() {
if (!Get.isRegistered<SocketService>()) Get.put(SocketService());
Get.lazyPut<WaitingRoomController>(() => WaitingRoomController(Get.find<SocketService>()));
Get.lazyPut<WaitingRoomController>(() => WaitingRoomController(
Get.find<SocketService>(),
Get.find<UserController>(),
));
}
}

View File

@ -2,6 +2,8 @@ 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/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/session/session_response_model.dart';
@ -10,7 +12,8 @@ import 'package:quiz_app/data/services/socket_service.dart';
class WaitingRoomController extends GetxController {
final SocketService _socketService;
WaitingRoomController(this._socketService);
final UserController _userController;
WaitingRoomController(this._socketService, this._userController);
final sessionCode = ''.obs;
final quizMeta = Rx<QuizListingModel?>(null);
@ -20,6 +23,7 @@ class WaitingRoomController extends GetxController {
final quizQuestions = <Map<String, dynamic>>[].obs;
final isQuizStarted = false.obs;
SessionResponseModel? roomData;
@override
void onInit() {
super.onInit();
@ -30,10 +34,10 @@ class WaitingRoomController extends GetxController {
void _loadInitialData() {
final data = Get.arguments as WaitingRoomDTO;
SessionResponseModel? roomData = data.data;
roomData = data.data;
isAdmin.value = data.isAdmin;
sessionCode.value = roomData.sessionCode;
sessionCode.value = roomData!.sessionCode;
quizMeta.value = QuizListingModel(
quizId: "q123",
@ -49,9 +53,18 @@ class WaitingRoomController extends GetxController {
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"] == "leave") {
final userId = data["data"];
CustomNotification.warning(title: "Participan Leave", message: "participan leave the room");
joinedUsers.removeWhere((e) => e.id == userId);
}
});
@ -86,4 +99,12 @@ class WaitingRoomController extends GetxController {
void startQuiz() {
_socketService.startQuiz(sessionCode: sessionCode.value);
}
void leaveRoom() async {
_socketService.leaveRoom(sessionId: roomData!.sessionId, userId: _userController.userData!.id);
Get.offAllNamed(AppRoutes.mainPage);
await Future.delayed(Duration(seconds: 2));
_socketService.dispose();
}
}

View File

@ -33,7 +33,13 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
GlobalButton(
text: "Mulai Kuis",
onPressed: controller.startQuiz,
),
)
else
GlobalButton(
text: "Tinggalkan Ruangan",
onPressed: controller.leaveRoom,
baseColor: const Color.fromARGB(255, 204, 14, 0),
)
],
);
}),
@ -68,6 +74,7 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
if (quiz == null) return const SizedBox.shrink();
return Container(
padding: const EdgeInsets.all(16),
width: double.infinity,
decoration: BoxDecoration(
color: AppColors.background,
border: Border.all(color: AppColors.borderLight),