feat: done on quiz preview
This commit is contained in:
parent
e801962e4c
commit
9db744679d
|
@ -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(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue