develop #1

Merged
akhdanre merged 104 commits from develop into main 2025-07-10 12:38:53 +07:00
11 changed files with 125 additions and 59 deletions
Showing only changes of commit 0283806cf3 - Show all commits

View File

@ -138,6 +138,18 @@
},
"get_ready": "Get Ready",
"quiz_starting_soon" : "Quiz Starting Soon"
"quiz_starting_soon": "Quiz Starting Soon",
"waiting_room": {
"title": "Waiting Room",
"participants_joined": "Participants Joined:",
"leave_room": "Leave Room",
"session_code": "Session Code:",
"copy_code": "Copy Code",
"quiz_info": "Quiz Information:",
"quiz_title": "Title",
"quiz_description": "Description",
"quiz_total_question": "Total Questions",
"quiz_duration": "Duration"
}
}

View File

@ -122,5 +122,17 @@
},
"get_ready": "Bersiaplah",
"quiz_starting_soon": "Kuis akan segera dimulai"
"quiz_starting_soon": "Kuis akan segera dimulai",
"waiting_room": {
"title": "Ruang Tunggu",
"participants_joined": "Peserta Bergabung:",
"leave_room": "Keluar dari Ruangan",
"session_code": "Kode Sesi:",
"copy_code": "Salin Kode",
"quiz_info": "Informasi Kuis:",
"quiz_title": "Judul",
"quiz_description": "Deskripsi",
"quiz_total_question": "Total Pertanyaan",
"quiz_duration": "Durasi"
}
}

View File

@ -124,5 +124,17 @@
},
"get_ready": "Bersedia",
"quiz_starting_soon": "Kuiz akan bermula sebentar lagi"
"quiz_starting_soon": "Kuiz akan bermula sebentar lagi",
"waiting_room": {
"title": "Bilik Menunggu",
"participants_joined": "Peserta Telah Sertai:",
"leave_room": "Tinggalkan Bilik",
"session_code": "Kod Sesi:",
"copy_code": "Salin Kod",
"quiz_info": "Maklumat Kuiz:",
"quiz_title": "Tajuk",
"quiz_description": "Penerangan",
"quiz_total_question": "Jumlah Soalan",
"quiz_duration": "Tempoh"
}
}

View File

@ -0,0 +1,5 @@
extension StringCasingExtension on String {
String toTitleCase() {
return split(' ').map((word) => word.isNotEmpty ? '${word[0].toUpperCase()}${word.substring(1).toLowerCase()}' : '').join(' ');
}
}

View File

@ -3,6 +3,7 @@ import 'package:quiz_app/data/models/user/user_model.dart';
class SessionInfo {
final String id;
final String sessionCode;
final String roomName;
final String quizId;
final String hostId;
final DateTime createdAt;
@ -16,6 +17,7 @@ class SessionInfo {
SessionInfo({
required this.id,
required this.sessionCode,
required this.roomName,
required this.quizId,
required this.hostId,
required this.createdAt,
@ -31,6 +33,7 @@ class SessionInfo {
return SessionInfo(
id: json['id'],
sessionCode: json['session_code'],
roomName: json["room_name"],
quizId: json['quiz_id'],
hostId: json['host_id'],
createdAt: DateTime.parse(json['created_at']),

View File

@ -1,11 +1,13 @@
class SessionRequestModel {
final String quizId;
final String hostId;
final String roomName;
final int limitParticipan;
SessionRequestModel({
required this.quizId,
required this.hostId,
required this.roomName,
required this.limitParticipan,
});
@ -13,6 +15,7 @@ class SessionRequestModel {
return SessionRequestModel(
quizId: json['quiz_id'],
hostId: json['host_id'],
roomName: json['room_name'],
limitParticipan: json['limit_participan'],
);
}
@ -21,6 +24,7 @@ class SessionRequestModel {
return {
'quiz_id': quizId,
'host_id': hostId,
'room_name': roomName,
'limit_participan': limitParticipan,
};
}

View File

@ -17,11 +17,7 @@ class SessionService extends GetxService {
Future<BaseResponseModel<SessionResponseModel>?> createSession(SessionRequestModel data) async {
try {
final response = await _dio.post(APIEndpoint.session, data: {
'quiz_id': data.quizId,
'host_id': data.hostId,
'limit_participan': data.limitParticipan,
});
final response = await _dio.post(APIEndpoint.session, data: data.toJson());
if (response.statusCode != 201) {
return null;
}

View File

@ -33,32 +33,32 @@ class JoinRoomView extends GetView<JoinRoomController> {
children: [
const SizedBox(height: 20),
TweenAnimationBuilder<double>(
duration: const Duration(seconds: 1),
tween: Tween(begin: 0.0, end: 1.0),
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: child,
);
},
child: Container(
padding: EdgeInsets.all(22),
decoration: BoxDecoration(
color: AppColors.primaryBlue.withValues(alpha: 0.05),
shape: BoxShape.circle,
border: Border.all(
color: AppColors.primaryBlue.withValues(alpha: 0.15),
width: 2,
),
),
child: Icon(
LucideIcons.trophy,
size: 70,
color: AppColors.primaryBlue,
),
),
),
// TweenAnimationBuilder<double>(
// duration: const Duration(seconds: 1),
// tween: Tween(begin: 0.0, end: 1.0),
// builder: (context, value, child) {
// return Transform.scale(
// scale: value,
// child: child,
// );
// },
// child: Container(
// padding: EdgeInsets.all(22),
// decoration: BoxDecoration(
// color: AppColors.primaryBlue.withValues(alpha: 0.05),
// shape: BoxShape.circle,
// border: Border.all(
// color: AppColors.primaryBlue.withValues(alpha: 0.15),
// width: 2,
// ),
// ),
// child: Icon(
// LucideIcons.trophy,
// size: 70,
// color: AppColors.primaryBlue,
// ),
// ),
// ),
const SizedBox(height: 30),

View File

@ -103,6 +103,7 @@ class RoomMakerController extends GetxController {
SessionRequestModel(
quizId: quiz.quizId,
hostId: _userController.userData!.id,
roomName: nameTC.text,
limitParticipan: int.parse(maxPlayerTC.text),
),
);

View File

@ -16,6 +16,7 @@ class WaitingRoomController extends GetxController {
WaitingRoomController(this._socketService, this._userController);
final sessionCode = ''.obs;
final roomName = "".obs;
String sessionId = '';
final quizMeta = Rx<QuizInfo?>(null);
final joinedUsers = <UserModel>[].obs;
@ -42,6 +43,7 @@ class WaitingRoomController extends GetxController {
sessionId = roomData!.sessionId;
quizMeta.value = data.quizInfo;
roomName.value = data.sessionInfo.roomName;
joinedUsers.assignAll(data.sessionInfo.participants);
}

View File

@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:quiz_app/app/const/text/string_extension.dart';
import 'package:quiz_app/app/const/text/text_style.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';
@ -11,7 +14,9 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
appBar: AppBar(title: const Text("Waiting Room")),
appBar: AppBar(
title: Text(tr("waiting_room.title"), style: AppTextStyles.title),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Obx(() {
@ -22,25 +27,39 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
Center(
child: Obx(() => Text(
controller.roomName.value.toTitleCase(),
style: AppTextStyles.title,
)),
),
const SizedBox(height: 20),
_buildQuizMeta(quiz!),
const SizedBox(height: 20),
_buildSessionCode(context, session),
const SizedBox(height: 20),
const Text("Peserta yang Bergabung:", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
Text(
tr("waiting_room.participants_joined"),
style: AppTextStyles.subtitle.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColors.darkText,
),
),
const SizedBox(height: 10),
Expanded(child: Obx(() => _buildUserList(users.toList()))),
const SizedBox(height: 16),
if (controller.isAdmin.value)
GlobalButton(
text: "Mulai Kuis",
onPressed: controller.startQuiz,
)
else
GlobalButton(
text: "Tinggalkan Ruangan",
onPressed: controller.leaveRoom,
baseColor: const Color.fromARGB(255, 204, 14, 0),
)
controller.isAdmin.value
? GlobalButton(
text: tr("start_quiz"),
onPressed: controller.startQuiz,
)
: GlobalButton(
text: tr("waiting_room.leave_room"),
onPressed: controller.leaveRoom,
baseColor: const Color.fromARGB(255, 204, 14, 0),
)
],
);
}),
@ -52,18 +71,19 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.primaryBlue.withValues(alpha: 0.05),
color: AppColors.accentBlue.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColors.primaryBlue),
),
child: Row(
children: [
const Text("Session Code: ", style: TextStyle(fontWeight: FontWeight.bold)),
SelectableText(code, style: const TextStyle(fontSize: 16)),
Text(tr("waiting_room.session_code"), style: AppTextStyles.statValue),
const SizedBox(width: 4),
SelectableText(code, style: AppTextStyles.body.copyWith(fontSize: 16)),
const Spacer(),
IconButton(
icon: const Icon(Icons.copy),
tooltip: 'Salin Kode',
tooltip: tr("waiting_room.copy_code"),
onPressed: () => controller.copySessionCode(context),
),
],
@ -72,7 +92,6 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
}
Widget _buildQuizMeta(QuizInfo quiz) {
// if (quiz == null) return const SizedBox.shrink();
return Container(
padding: const EdgeInsets.all(16),
width: double.infinity,
@ -84,12 +103,12 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Informasi Kuis:", style: TextStyle(fontWeight: FontWeight.bold)),
Text(tr("waiting_room.quiz_info"), style: AppTextStyles.subtitle.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text("Judul: ${quiz.title}"),
Text("Deskripsi: ${quiz.description}"),
Text("Jumlah Soal: ${quiz.totalQuiz}"),
Text("Durasi: ${quiz.limitDuration ~/ 60} menit"),
Text("${tr("waiting_room.quiz_title")}: ${quiz.title}", style: AppTextStyles.body),
Text("${tr("waiting_room.quiz_description")}: ${quiz.description}", style: AppTextStyles.body),
Text("${tr("waiting_room.quiz_total_question")}: ${quiz.totalQuiz}", style: AppTextStyles.body),
Text("${tr("waiting_room.quiz_duration")}: ${quiz.limitDuration ~/ 60} min", style: AppTextStyles.body),
],
),
);
@ -110,9 +129,9 @@ class WaitingRoomView extends GetView<WaitingRoomController> {
),
child: Row(
children: [
CircleAvatar(child: Text(user.username[0])),
CircleAvatar(child: Text(user.username[0].toUpperCase())),
const SizedBox(width: 12),
Text(user.username, style: const TextStyle(fontSize: 16)),
Text(user.username, style: AppTextStyles.body.copyWith(fontSize: 16)),
],
),
);