diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 924c8ab..13d9a90 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -10,6 +10,8 @@ import 'package:quiz_app/feature/navigation/views/navbar_view.dart'; import 'package:quiz_app/feature/profile/binding/profile_binding.dart'; import 'package:quiz_app/feature/quiz_creation/binding/quiz_creation_binding.dart'; import 'package:quiz_app/feature/quiz_creation/view/quiz_creation_view.dart'; +import 'package:quiz_app/feature/quiz_preview/binding/quiz_preview_binding.dart'; +import 'package:quiz_app/feature/quiz_preview/view/quiz_preview.dart'; import 'package:quiz_app/feature/register/binding/register_binding.dart'; import 'package:quiz_app/feature/register/view/register_page.dart'; import 'package:quiz_app/feature/search/binding/search_binding.dart'; @@ -56,5 +58,10 @@ class AppPages { page: () => QuizCreationView(), binding: QuizCreationBinding(), ), + GetPage( + name: AppRoutes.quizPreviewPage, + page: () => QuizPreviewPage(), + binding: QuizPreviewBinding(), + ), ]; } diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index 52906e7..e1f43d9 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -9,4 +9,5 @@ abstract class AppRoutes { static const mainPage = '/main'; static const quizCreatePage = "/quiz/creation"; + static const quizPreviewPage = "/quiz/preview"; } diff --git a/lib/feature/quiz_creation/controller/quiz_creation_controller.dart b/lib/feature/quiz_creation/controller/quiz_creation_controller.dart index 1df9d85..fbc5434 100644 --- a/lib/feature/quiz_creation/controller/quiz_creation_controller.dart +++ b/lib/feature/quiz_creation/controller/quiz_creation_controller.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; import 'package:quiz_app/app/const/enums/question_type.dart'; +import 'package:quiz_app/app/routes/app_pages.dart'; import 'package:quiz_app/data/models/quiz/quiestion_data_model.dart'; class QuizCreationController extends GetxController { @@ -123,4 +124,8 @@ class QuizCreationController extends GetxController { void updateTOFAnswer(bool answer) { _updateCurrentQuestion(answer: answer.toString()); } + + void onDone() { + Get.toNamed(AppRoutes.quizPreviewPage, arguments: quizData); + } } diff --git a/lib/feature/quiz_creation/view/quiz_creation_view.dart b/lib/feature/quiz_creation/view/quiz_creation_view.dart index 6c2076c..1ef3a01 100644 --- a/lib/feature/quiz_creation/view/quiz_creation_view.dart +++ b/lib/feature/quiz_creation/view/quiz_creation_view.dart @@ -42,7 +42,7 @@ class QuizCreationView extends GetView { () => controller.isGenerate.value ? GenerateComponent() : CustomQuestionComponent(), ), const SizedBox(height: 30), - GlobalButton(text: "simpan semua", onPressed: () {}) + GlobalButton(text: "simpan semua", onPressed: controller.onDone) ], ), ), diff --git a/lib/feature/quiz_preview/binding/quiz_preview_binding.dart b/lib/feature/quiz_preview/binding/quiz_preview_binding.dart new file mode 100644 index 0000000..2403b46 --- /dev/null +++ b/lib/feature/quiz_preview/binding/quiz_preview_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:quiz_app/feature/quiz_preview/controller/quiz_preview_controller.dart'; + +class QuizPreviewBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => QuizPreviewController()); + } +} diff --git a/lib/feature/quiz_preview/controller/quiz_preview_controller.dart b/lib/feature/quiz_preview/controller/quiz_preview_controller.dart new file mode 100644 index 0000000..edcf152 --- /dev/null +++ b/lib/feature/quiz_preview/controller/quiz_preview_controller.dart @@ -0,0 +1,193 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.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/data/models/quiz/quiestion_data_model.dart'; + +class QuizPreviewController extends GetxController { + final TextEditingController titleController = TextEditingController(); + final TextEditingController descriptionController = TextEditingController(); + + late final List data; + + @override + void onInit() { + super.onInit(); + loadData(); + } + + void loadData() { + if (Get.arguments is List) { + data = Get.arguments as List; + } else { + data = []; // Default aman supaya gak crash + Get.snackbar('Error', 'Data soal tidak ditemukan'); + } + } + + void onSaveQuiz() { + final title = titleController.text.trim(); + final description = descriptionController.text.trim(); + + if (title.isEmpty || description.isEmpty) { + Get.snackbar('Error', 'Judul dan deskripsi tidak boleh kosong!'); + return; + } + + Get.snackbar('Sukses', 'Kuis berhasil disimpan!'); + } + + Widget buildQuestionCard(QuestionData question) { + return Container( + width: double.infinity, + margin: const EdgeInsets.only(bottom: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColors.borderLight), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 6, + offset: const Offset(2, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Soal ${question.index}', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: AppColors.darkText)), + const SizedBox(height: 6), + Text( + _mapQuestionTypeToText(question.type), + style: const TextStyle(fontSize: 12, color: AppColors.softGrayText, fontStyle: FontStyle.italic), + ), + const SizedBox(height: 12), + Text( + question.question ?? '-', + style: const TextStyle(fontSize: 16, color: AppColors.darkText), + ), + const SizedBox(height: 16), + _buildAnswerSection(question), + const SizedBox(height: 10), + const Text( + 'Durasi: 0 detik', + style: TextStyle(fontSize: 14, color: AppColors.softGrayText), + ), + ], + ), + ); + } + + Widget _buildAnswerSection(QuestionData question) { + if (question.type == QuestionType.option && question.options != null) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: question.options!.map((option) { + bool isCorrect = question.correctAnswerIndex == option.index; + return Container( + margin: const EdgeInsets.symmetric(vertical: 4), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + decoration: BoxDecoration( + color: isCorrect ? AppColors.primaryBlue.withOpacity(0.1) : Colors.transparent, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon( + isCorrect ? Icons.check_circle_rounded : Icons.circle_outlined, + size: 18, + color: isCorrect ? AppColors.primaryBlue : AppColors.softGrayText, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + option.text, + style: TextStyle( + fontWeight: isCorrect ? FontWeight.bold : FontWeight.normal, + color: isCorrect ? AppColors.primaryBlue : AppColors.darkText, + ), + ), + ), + ], + ), + ); + }).toList(), + ); + } else if (question.type == QuestionType.fillTheBlank) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildFillTheBlankPossibilities(question.answer ?? '-'), + ); + } else if (question.type == QuestionType.trueOrFalse) { + return Text( + 'Jawaban: ${question.answer ?? '-'}', + style: const TextStyle(color: AppColors.softGrayText), + ); + } else { + return const SizedBox(); + } + } + + String _mapQuestionTypeToText(QuestionType? type) { + switch (type) { + case QuestionType.option: + return 'Tipe: Pilihan Ganda'; + case QuestionType.fillTheBlank: + return 'Tipe: Isian Kosong'; + case QuestionType.trueOrFalse: + return 'Tipe: Benar / Salah'; + default: + return 'Tipe: Tidak diketahui'; + } + } + + List _buildFillTheBlankPossibilities(String answer) { + List possibilities = [ + _capitalizeEachWord(answer), + answer.toLowerCase(), + _capitalizeFirstWordOnly(answer), + ]; + + return possibilities.map((option) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + children: [ + const Icon(Icons.arrow_right, size: 18, color: AppColors.softGrayText), + const SizedBox(width: 6), + Text( + option, + style: const TextStyle(color: AppColors.darkText), + ), + ], + ), + ); + }).toList(); + } + + String _capitalizeEachWord(String text) { + return text.split(' ').map((word) { + if (word.isEmpty) return word; + return word[0].toUpperCase() + word.substring(1).toLowerCase(); + }).join(' '); + } + + String _capitalizeFirstWordOnly(String text) { + if (text.isEmpty) return text; + List parts = text.split(' '); + parts[0] = parts[0][0].toUpperCase() + parts[0].substring(1).toLowerCase(); + for (int i = 1; i < parts.length; i++) { + parts[i] = parts[i].toLowerCase(); + } + return parts.join(' '); + } + + @override + void onClose() { + titleController.dispose(); + descriptionController.dispose(); + super.onClose(); + } +} diff --git a/lib/feature/quiz_preview/view/quiz_preview.dart b/lib/feature/quiz_preview/view/quiz_preview.dart new file mode 100644 index 0000000..21fa974 --- /dev/null +++ b/lib/feature/quiz_preview/view/quiz_preview.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.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_preview/controller/quiz_preview_controller.dart'; + +class QuizPreviewPage extends GetView { + const QuizPreviewPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar( + backgroundColor: AppColors.background, + elevation: 0, + title: const Text( + 'Preview Quiz', + style: TextStyle( + color: AppColors.darkText, + fontWeight: FontWeight.bold, + ), + ), + centerTitle: true, + iconTheme: const IconThemeData(color: AppColors.darkText), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTextField( + label: 'Judul Kuis', + controller: controller.titleController, + ), + const SizedBox(height: 20), + _buildTextField( + label: 'Deskripsi Kuis', + controller: controller.descriptionController, + maxLines: 3, + ), + const SizedBox(height: 30), + const Divider(thickness: 1.2, color: AppColors.borderLight), + const SizedBox(height: 20), + _buildQuestionContent(), + const SizedBox(height: 30), + GlobalButton( + onPressed: controller.onSaveQuiz, + text: "Simpan Kuis", + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildTextField({ + required String label, + required TextEditingController controller, + int maxLines = 1, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: const TextStyle(fontWeight: FontWeight.w600, color: AppColors.softGrayText)), + const SizedBox(height: 8), + TextField( + controller: controller, + maxLines: maxLines, + decoration: InputDecoration( + hintText: 'Masukkan $label', + hintStyle: const TextStyle(color: AppColors.softGrayText), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.borderLight), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.borderLight), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.primaryBlue, width: 2), + ), + ), + ), + ], + ); + } + + Widget _buildQuestionContent() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: controller.data.map((question) { + return controller.buildQuestionCard(question); + }).toList(), + ); + } +}