feat: done on quiz preview

This commit is contained in:
akhdanre 2025-04-27 14:16:15 +07:00
parent e801962e4c
commit 9db744679d
7 changed files with 322 additions and 1 deletions

View File

@ -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/profile/binding/profile_binding.dart';
import 'package:quiz_app/feature/quiz_creation/binding/quiz_creation_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_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/binding/register_binding.dart';
import 'package:quiz_app/feature/register/view/register_page.dart'; import 'package:quiz_app/feature/register/view/register_page.dart';
import 'package:quiz_app/feature/search/binding/search_binding.dart'; import 'package:quiz_app/feature/search/binding/search_binding.dart';
@ -56,5 +58,10 @@ class AppPages {
page: () => QuizCreationView(), page: () => QuizCreationView(),
binding: QuizCreationBinding(), binding: QuizCreationBinding(),
), ),
GetPage(
name: AppRoutes.quizPreviewPage,
page: () => QuizPreviewPage(),
binding: QuizPreviewBinding(),
),
]; ];
} }

View File

@ -9,4 +9,5 @@ abstract class AppRoutes {
static const mainPage = '/main'; static const mainPage = '/main';
static const quizCreatePage = "/quiz/creation"; static const quizCreatePage = "/quiz/creation";
static const quizPreviewPage = "/quiz/preview";
} }

View File

@ -1,6 +1,7 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.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/app/routes/app_pages.dart';
import 'package:quiz_app/data/models/quiz/quiestion_data_model.dart'; import 'package:quiz_app/data/models/quiz/quiestion_data_model.dart';
class QuizCreationController extends GetxController { class QuizCreationController extends GetxController {
@ -123,4 +124,8 @@ class QuizCreationController extends GetxController {
void updateTOFAnswer(bool answer) { void updateTOFAnswer(bool answer) {
_updateCurrentQuestion(answer: answer.toString()); _updateCurrentQuestion(answer: answer.toString());
} }
void onDone() {
Get.toNamed(AppRoutes.quizPreviewPage, arguments: quizData);
}
} }

View File

@ -42,7 +42,7 @@ class QuizCreationView extends GetView<QuizCreationController> {
() => controller.isGenerate.value ? GenerateComponent() : CustomQuestionComponent(), () => controller.isGenerate.value ? GenerateComponent() : CustomQuestionComponent(),
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
GlobalButton(text: "simpan semua", onPressed: () {}) GlobalButton(text: "simpan semua", onPressed: controller.onDone)
], ],
), ),
), ),

View File

@ -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>(() => QuizPreviewController());
}
}

View File

@ -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<QuestionData> data;
@override
void onInit() {
super.onInit();
loadData();
}
void loadData() {
if (Get.arguments is List<QuestionData>) {
data = Get.arguments as List<QuestionData>;
} 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<Widget> _buildFillTheBlankPossibilities(String answer) {
List<String> 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<String> 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();
}
}

View File

@ -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<QuizPreviewController> {
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(),
);
}
}