feat: adding create automatic quiz

This commit is contained in:
akhdanre 2025-05-18 15:00:55 +07:00
parent 737f0f775a
commit d925a22bb0
12 changed files with 152 additions and 52 deletions

View File

@ -82,5 +82,7 @@
"save_quiz": "Save Quiz", "save_quiz": "Save Quiz",
"select_language": "Select Language", "select_language": "Select Language",
"change_language": "Change Language" "change_language": "Change Language",
"auto_generate_quiz": "Auto Generate Quiz"
} }

View File

@ -82,5 +82,6 @@
"save_quiz": "Simpan Kuis", "save_quiz": "Simpan Kuis",
"select_language": "Pilih Bahasa", "select_language": "Pilih Bahasa",
"change_language": "Ganti Bahasa" "change_language": "Ganti Bahasa",
"auto_generate_quiz": "Buat Kuis Otomatis"
} }

View File

@ -79,5 +79,7 @@
"quiz_description_label": "Deskripsi Ringkas", "quiz_description_label": "Deskripsi Ringkas",
"quiz_subject_label": "Subjek", "quiz_subject_label": "Subjek",
"make_quiz_public": "Jadikan Kuiz Umum", "make_quiz_public": "Jadikan Kuiz Umum",
"save_quiz": "Simpan Kuiz" "save_quiz": "Simpan Kuiz",
"auto_generate_quiz": "Jana Kuiz Automatik"
} }

View File

@ -16,7 +16,7 @@ class MyApp extends StatelessWidget {
localizationsDelegates: context.localizationDelegates, localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales, supportedLocales: context.supportedLocales,
initialBinding: InitialBindings(), initialBinding: InitialBindings(),
initialRoute: AppRoutes.monitorResultMPLPage, initialRoute: AppRoutes.splashScreen,
getPages: AppPages.routes, getPages: AppPages.routes,
); );
} }

View File

@ -37,7 +37,6 @@ import 'package:quiz_app/feature/search/binding/search_binding.dart';
import 'package:quiz_app/feature/splash_screen/presentation/splash_screen_page.dart'; import 'package:quiz_app/feature/splash_screen/presentation/splash_screen_page.dart';
import 'package:quiz_app/feature/waiting_room/binding/waiting_room_binding.dart'; import 'package:quiz_app/feature/waiting_room/binding/waiting_room_binding.dart';
import 'package:quiz_app/feature/waiting_room/view/waiting_room_view.dart'; import 'package:quiz_app/feature/waiting_room/view/waiting_room_view.dart';
import 'package:quiz_app/feature/admin_result_page/view/admin_result_page.dart';
part 'app_routes.dart'; part 'app_routes.dart';
@ -136,9 +135,9 @@ class AppPages {
page: () => PlayQuizMultiplayerView(), page: () => PlayQuizMultiplayerView(),
binding: PlayQuizMultiplayerBinding(), binding: PlayQuizMultiplayerBinding(),
), ),
GetPage( // GetPage(
name: AppRoutes.monitorResultMPLPage, // name: AppRoutes.monitorResultMPLPage,
page: () => AdminResultPage(), // page: () => AdminResultPage(),
) // )
]; ];
} }

View File

@ -9,6 +9,7 @@ class APIEndpoint {
static const String register = "/register"; static const String register = "/register";
static const String quiz = "/quiz"; static const String quiz = "/quiz";
static const String quizGenerate = "/quiz/ai";
static const String quizAnswer = "/quiz/answer"; static const String quizAnswer = "/quiz/answer";
static const String userQuiz = "/quiz/user"; static const String userQuiz = "/quiz/user";

View File

@ -35,6 +35,32 @@ class QuizService extends GetxService {
} }
} }
Future<BaseResponseModel<List<RawQuizModel>>> createQuizAuto(String sentence) async {
try {
final response = await _dio.post(
APIEndpoint.quizGenerate,
data: {"sentence": sentence},
);
if (response.statusCode == 200) {
print(response.data);
// Parsing response using BaseResponseModel
final parseResponse = BaseResponseModel<List<RawQuizModel>>.fromJson(
response.data,
(data) => (data as List).map((item) => RawQuizModel.fromJson(item as Map<String, dynamic>)).toList(),
);
return parseResponse;
} else {
throw Exception("Quiz creation failed with status: ${response.statusCode}");
}
} catch (e) {
logC.e("Quiz creation error: $e");
throw Exception("Quiz creation error: $e");
}
}
Future<BaseResponseModel<List<QuizListingModel>>?> userQuiz(String userId, int page) async { Future<BaseResponseModel<List<QuizListingModel>>?> userQuiz(String userId, int page) async {
try { try {
final response = await _dio.get("${APIEndpoint.userQuiz}/$userId?page=$page"); final response = await _dio.get("${APIEndpoint.userQuiz}/$userId?page=$page");
@ -122,3 +148,27 @@ class QuizService extends GetxService {
} }
} }
} }
class RawQuizModel {
final String qustion;
final dynamic answer;
RawQuizModel({
required this.qustion,
required this.answer,
});
factory RawQuizModel.fromJson(Map<String, dynamic> json) {
return RawQuizModel(
qustion: json['qustion'] as String,
answer: json['answer'],
);
}
Map<String, dynamic> toJson() {
return {
'qustion': qustion,
'answer': answer,
};
}
}

View File

@ -1,9 +1,15 @@
import "package:get/get.dart"; import "package:get/get.dart";
import "package:quiz_app/data/services/quiz_service.dart";
import "package:quiz_app/feature/quiz_creation/controller/quiz_creation_controller.dart"; import "package:quiz_app/feature/quiz_creation/controller/quiz_creation_controller.dart";
class QuizCreationBinding extends Bindings { class QuizCreationBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {
Get.lazyPut<QuizCreationController>(() => QuizCreationController()); Get.lazyPut(() => QuizService());
Get.lazyPut<QuizCreationController>(
() => QuizCreationController(
Get.find<QuizService>(),
),
);
} }
} }

View File

@ -5,9 +5,18 @@ import 'package:quiz_app/app/const/enums/question_type.dart';
import 'package:quiz_app/app/routes/app_pages.dart'; import 'package:quiz_app/app/routes/app_pages.dart';
import 'package:quiz_app/component/notification/delete_confirmation.dart'; import 'package:quiz_app/component/notification/delete_confirmation.dart';
import 'package:quiz_app/component/notification/pop_up_confirmation.dart'; import 'package:quiz_app/component/notification/pop_up_confirmation.dart';
import 'package:quiz_app/core/utils/custom_floating_loading.dart';
import 'package:quiz_app/core/utils/logger.dart';
import 'package:quiz_app/data/models/base/base_model.dart';
import 'package:quiz_app/data/models/quiz/quiestion_data_model.dart'; import 'package:quiz_app/data/models/quiz/quiestion_data_model.dart';
import 'package:quiz_app/data/services/quiz_service.dart';
class QuizCreationController extends GetxController { class QuizCreationController extends GetxController {
final QuizService _quizService;
QuizCreationController(this._quizService);
final TextEditingController inputSentenceTC = TextEditingController();
final TextEditingController questionTC = TextEditingController(); final TextEditingController questionTC = TextEditingController();
final TextEditingController answerTC = TextEditingController(); final TextEditingController answerTC = TextEditingController();
final List<TextEditingController> optionTCList = List.generate(4, (_) => TextEditingController()); final List<TextEditingController> optionTCList = List.generate(4, (_) => TextEditingController());
@ -213,4 +222,56 @@ class QuizCreationController extends GetxController {
selectedQuizIndex.value -= 1; selectedQuizIndex.value -= 1;
} }
} }
void generateQuiz() async {
CustomFloatingLoading.showLoadingDialog(Get.context!);
try {
BaseResponseModel<List<RawQuizModel>> response = await _quizService.createQuizAuto(inputSentenceTC.text);
if (response.data != null) {
final previousLength = quizData.length;
if (previousLength == 1) quizData.removeAt(0);
for (final i in response.data!) {
QuestionType type = QuestionType.fillTheBlank;
if (i.answer.toString().toLowerCase() == 'true' || i.answer.toString().toLowerCase() == 'false') {
type = QuestionType.trueOrFalse;
}
quizData.add(QuestionData(
index: quizData.length + 1,
question: i.qustion,
answer: i.answer,
type: type,
));
}
if (response.data!.isNotEmpty) {
selectedQuizIndex.value = previousLength;
final data = quizData[selectedQuizIndex.value];
questionTC.text = data.question ?? "";
answerTC.text = data.answer ?? "";
currentDuration.value = data.duration;
currentQuestionType.value = data.type ?? QuestionType.fillTheBlank;
return;
}
}
} catch (e) {
logC.e("Error while generating quiz: $e");
} finally {
CustomFloatingLoading.hideLoadingDialog(Get.context!);
isGenerate.value = false;
if (quizData.isNotEmpty && selectedQuizIndex.value == 0) {
final data = quizData[0];
questionTC.text = data.question ?? "";
answerTC.text = data.answer ?? "";
currentDuration.value = data.duration;
currentQuestionType.value = data.type ?? QuestionType.fillTheBlank;
}
}
}
} }

View File

@ -1,7 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:quiz_app/app/const/colors/app_colors.dart'; import 'package:quiz_app/app/const/colors/app_colors.dart';
import 'package:quiz_app/app/const/enums/question_type.dart'; import 'package:quiz_app/app/const/enums/question_type.dart';
import 'package:quiz_app/component/global_button.dart';
import 'package:quiz_app/feature/quiz_creation/controller/quiz_creation_controller.dart'; import 'package:quiz_app/feature/quiz_creation/controller/quiz_creation_controller.dart';
import 'package:quiz_app/feature/quiz_creation/view/component/fill_the_blank_component.dart'; import 'package:quiz_app/feature/quiz_creation/view/component/fill_the_blank_component.dart';
import 'package:quiz_app/feature/quiz_creation/view/component/option_question_component.dart'; import 'package:quiz_app/feature/quiz_creation/view/component/option_question_component.dart';
@ -30,6 +32,11 @@ class CustomQuestionComponent extends GetView<QuizCreationController> {
_questionTypeValue(), _questionTypeValue(),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildDurationDropdown(), _buildDurationDropdown(),
const SizedBox(height: 30),
GlobalButton(
text: context.tr('save_all'),
onPressed: controller.onDone,
)
], ],
); );
} }

View File

@ -1,17 +1,19 @@
import 'package:easy_localization/easy_localization.dart';
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_button.dart';
import 'package:quiz_app/component/global_text_field.dart';
import 'package:quiz_app/feature/quiz_creation/controller/quiz_creation_controller.dart'; import 'package:quiz_app/feature/quiz_creation/controller/quiz_creation_controller.dart';
class GenerateComponent extends GetView<QuizCreationController> { class GenerateComponent extends GetView<QuizCreationController> {
const GenerateComponent({super.key}); const GenerateComponent({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( const Text(
"Unggah file materi kamu (PDF atau Word) untuk membuat soal otomatis.", "Masukkan paragraf untuk dijadikan soal",
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Color(0xFF6B778C), color: Color(0xFF6B778C),
@ -19,41 +21,16 @@ class GenerateComponent extends GetView<QuizCreationController> {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
GestureDetector( GlobalTextField(
onTap: () {}, hintText: "Tulis kalimat atau paragraf panjang, dan kami akan mengubahnya menjadi soal secara otomatis",
child: Container( controller: controller.inputSentenceTC,
width: double.infinity, limitTextLine: 15,
padding: const EdgeInsets.symmetric(vertical: 30),
decoration: BoxDecoration(
color: const Color(0xFFF0F2F5),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade300),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.insert_drive_file, size: 50, color: Color(0xFF6B778C)),
SizedBox(height: 10),
Text(
"Upload PDF atau Word",
style: TextStyle(
fontSize: 16,
color: Color(0xFF6B778C),
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8),
Text(
"Max 10 MB",
style: TextStyle(
fontSize: 12,
color: Color(0xFF9FA8B2),
),
),
],
),
),
), ),
const SizedBox(height: 16),
GlobalButton(
text: context.tr('auto_generate_quiz'),
onPressed: controller.generateQuiz,
)
], ],
); );
} }

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:quiz_app/app/const/colors/app_colors.dart'; import 'package:quiz_app/app/const/colors/app_colors.dart';
import 'package:quiz_app/component/global_button.dart';
import 'package:quiz_app/feature/quiz_creation/controller/quiz_creation_controller.dart'; import 'package:quiz_app/feature/quiz_creation/controller/quiz_creation_controller.dart';
import 'package:quiz_app/feature/quiz_creation/view/component/custom_question_component.dart'; import 'package:quiz_app/feature/quiz_creation/view/component/custom_question_component.dart';
import 'package:quiz_app/feature/quiz_creation/view/component/generate_component.dart'; import 'package:quiz_app/feature/quiz_creation/view/component/generate_component.dart';
@ -40,11 +39,6 @@ class QuizCreationView extends GetView<QuizCreationController> {
_buildModeSelector(context), _buildModeSelector(context),
const SizedBox(height: 20), const SizedBox(height: 20),
Obx(() => controller.isGenerate.value ? const GenerateComponent() : const CustomQuestionComponent()), Obx(() => controller.isGenerate.value ? const GenerateComponent() : const CustomQuestionComponent()),
const SizedBox(height: 30),
GlobalButton(
text: context.tr('save_all'),
onPressed: controller.onDone,
)
], ],
), ),
), ),