From 05a22f33602407b7a4040df6c19922c6f5b5f044 Mon Sep 17 00:00:00 2001 From: akhdanre Date: Sun, 27 Apr 2025 00:30:25 +0700 Subject: [PATCH] feat: interface on the quiz creation --- lib/app/const/colors/app_colors.dart | 14 ++ lib/app/routes/app_pages.dart | 9 +- lib/app/routes/app_routes.dart | 2 + lib/component/global_text_field.dart | 31 +++- lib/component/quiz_container_component.dart | 3 +- lib/feature/home/binding/home_binding.dart | 1 - .../home/controller/home_controller.dart | 3 + .../home/view/component/search_component.dart | 3 +- lib/feature/home/view/home_page.dart | 5 +- .../login/controllers/login_controller.dart | 4 +- lib/feature/login/view/login_page.dart | 3 +- .../binding/quiz_creation_binding.dart | 9 + .../controller/quiz_creation_controller.dart | 17 ++ .../component/custom_question_component.dart | 156 ++++++++++++++++++ .../component/fill_the_blank_component.dart | 31 ++++ .../view/component/generate_component.dart | 60 +++++++ .../component/option_question_component.dart | 84 ++++++++++ .../component/true_or_false_component.dart | 70 ++++++++ .../view/quiz_creation_view.dart | 99 +++++++++++ lib/feature/register/view/register_page.dart | 3 +- .../presentation/splash_screen_page.dart | 3 +- 21 files changed, 591 insertions(+), 19 deletions(-) create mode 100644 lib/app/const/colors/app_colors.dart create mode 100644 lib/feature/quiz_creation/binding/quiz_creation_binding.dart create mode 100644 lib/feature/quiz_creation/controller/quiz_creation_controller.dart create mode 100644 lib/feature/quiz_creation/view/component/custom_question_component.dart create mode 100644 lib/feature/quiz_creation/view/component/fill_the_blank_component.dart create mode 100644 lib/feature/quiz_creation/view/component/generate_component.dart create mode 100644 lib/feature/quiz_creation/view/component/option_question_component.dart create mode 100644 lib/feature/quiz_creation/view/component/true_or_false_component.dart create mode 100644 lib/feature/quiz_creation/view/quiz_creation_view.dart diff --git a/lib/app/const/colors/app_colors.dart b/lib/app/const/colors/app_colors.dart new file mode 100644 index 0000000..2e8cf87 --- /dev/null +++ b/lib/app/const/colors/app_colors.dart @@ -0,0 +1,14 @@ +import 'dart:ui'; + +class AppColors { + static const Color primaryBlue = Color(0xFF0052CC); + static const Color darkText = Color(0xFF172B4D); + static const Color softGrayText = Color(0xFF6B778C); + static const Color background = Color(0xFFFAFBFC); + + static const Color borderLight = Color(0xFFE1E4E8); + static const Color accentBlue = Color(0xFFD6E4FF); + static const Color shadowPrimary = Color(0x330052CC); + static const Color disabledBackground = Color(0xFFE0E0E0); + static const Color disabledText = Color(0xFF9E9E9E); +} diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 522118a..924c8ab 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -8,6 +8,8 @@ import 'package:quiz_app/feature/login/view/login_page.dart'; import 'package:quiz_app/feature/navigation/bindings/navigation_binding.dart'; 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/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'; @@ -48,6 +50,11 @@ class AppPages { ProfileBinding(), ], middlewares: [AuthMiddleware()], - ) + ), + GetPage( + name: AppRoutes.quizCreatePage, + page: () => QuizCreationView(), + binding: QuizCreationBinding(), + ), ]; } diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index 00e7b0f..52906e7 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -7,4 +7,6 @@ abstract class AppRoutes { static const homePage = '/home'; static const mainPage = '/main'; + + static const quizCreatePage = "/quiz/creation"; } diff --git a/lib/component/global_text_field.dart b/lib/component/global_text_field.dart index f855ad4..ca8369d 100644 --- a/lib/component/global_text_field.dart +++ b/lib/component/global_text_field.dart @@ -4,6 +4,7 @@ class GlobalTextField extends StatelessWidget { final TextEditingController controller; final String? hintText; final String? labelText; + final int limitTextLine; final bool isPassword; final bool obscureText; final VoidCallback? onToggleVisibility; @@ -13,6 +14,7 @@ class GlobalTextField extends StatelessWidget { required this.controller, this.hintText, this.labelText, + this.limitTextLine = 1, this.isPassword = false, this.obscureText = false, this.onToggleVisibility, @@ -23,31 +25,44 @@ class GlobalTextField extends StatelessWidget { return TextField( controller: controller, obscureText: isPassword ? obscureText : false, + maxLines: limitTextLine, // <-- ini tambahan dari limitTextLine decoration: InputDecoration( labelText: labelText, - labelStyle: const TextStyle(color: Color(0xFF6B778C), fontSize: 14), + labelStyle: const TextStyle( + color: Color(0xFF6B778C), + fontSize: 14, + ), hintText: hintText, - hintStyle: const TextStyle(color: Color(0xFF6B778C), fontSize: 14), + hintStyle: const TextStyle( + color: Color(0xFF6B778C), + fontSize: 14, + ), filled: true, - fillColor: const Color.fromARGB(255, 234, 234, 235), // Background soft white - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + fillColor: Color.fromARGB(255, 234, 234, 235), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), - borderSide: BorderSide(color: Colors.transparent), + borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), - borderSide: BorderSide(color: Colors.transparent), + borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), - borderSide: const BorderSide(color: Color(0xFF0052CC), width: 2), + borderSide: const BorderSide( + color: Color(0xFF0052CC), + width: 2, + ), ), suffixIcon: isPassword ? IconButton( icon: Icon( obscureText ? Icons.visibility_off : Icons.visibility, - color: const Color(0xFF6B778C), + color: Color(0xFF6B778C), ), onPressed: onToggleVisibility, ) diff --git a/lib/component/quiz_container_component.dart b/lib/component/quiz_container_component.dart index 0a1d448..b70cb57 100644 --- a/lib/component/quiz_container_component.dart +++ b/lib/component/quiz_container_component.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:quiz_app/app/const/colors/app_colors.dart'; class QuizContainerComponent extends StatelessWidget { const QuizContainerComponent({super.key}); @@ -8,7 +9,7 @@ class QuizContainerComponent extends StatelessWidget { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( - color: Color(0xFFFAFBFC), + color: AppColors.background, borderRadius: BorderRadius.circular(12), border: Border.all( color: Color(0xFFE1E4E8), diff --git a/lib/feature/home/binding/home_binding.dart b/lib/feature/home/binding/home_binding.dart index 78eabad..9adecc5 100644 --- a/lib/feature/home/binding/home_binding.dart +++ b/lib/feature/home/binding/home_binding.dart @@ -1,5 +1,4 @@ import 'package:get/get.dart'; -import 'package:quiz_app/data/services/user_storage_service.dart'; import 'package:quiz_app/feature/home/controller/home_controller.dart'; class HomeBinding extends Bindings { diff --git a/lib/feature/home/controller/home_controller.dart b/lib/feature/home/controller/home_controller.dart index a388524..862f335 100644 --- a/lib/feature/home/controller/home_controller.dart +++ b/lib/feature/home/controller/home_controller.dart @@ -1,4 +1,5 @@ import 'package:get/get.dart'; +import 'package:quiz_app/app/routes/app_pages.dart'; import 'package:quiz_app/data/controllers/user_controller.dart'; class HomeController extends GetxController { @@ -6,4 +7,6 @@ class HomeController extends GetxController { Rx get userName => _userController.userName; Rx get userImage => _userController.userImage; + + void goToQuizCreation() => Get.toNamed(AppRoutes.quizCreatePage); } diff --git a/lib/feature/home/view/component/search_component.dart b/lib/feature/home/view/component/search_component.dart index eef2c2f..7409fa2 100644 --- a/lib/feature/home/view/component/search_component.dart +++ b/lib/feature/home/view/component/search_component.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:quiz_app/app/const/colors/app_colors.dart'; class SearchComponent extends StatelessWidget { const SearchComponent({super.key}); @@ -9,7 +10,7 @@ class SearchComponent extends StatelessWidget { margin: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), decoration: BoxDecoration( - color: const Color(0xFFFAFBFC), // Soft background + color: AppColors.background, // Soft background borderRadius: BorderRadius.circular(10), border: Border.all(color: Color(0xFFE1E4E8)), // Light border ), diff --git a/lib/feature/home/view/home_page.dart b/lib/feature/home/view/home_page.dart index 5ac1288..a696a49 100644 --- a/lib/feature/home/view/home_page.dart +++ b/lib/feature/home/view/home_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:quiz_app/app/const/colors/app_colors.dart'; import 'package:quiz_app/feature/home/controller/home_controller.dart'; import 'package:quiz_app/feature/home/view/component/button_option.dart'; import 'package:quiz_app/feature/home/view/component/recomendation_component.dart'; @@ -12,7 +13,7 @@ class HomeView extends GetView { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFFAFBFC), + backgroundColor: AppColors.background, body: SafeArea( child: ListView( children: [ @@ -32,7 +33,7 @@ class HomeView extends GetView { ), // ButtonOption di luar Padding ButtonOption( - onCreate: () {}, + onCreate: controller.goToQuizCreation, onCreateRoom: () {}, onJoinRoom: () {}, ), diff --git a/lib/feature/login/controllers/login_controller.dart b/lib/feature/login/controllers/login_controller.dart index 63eac8d..c052869 100644 --- a/lib/feature/login/controllers/login_controller.dart +++ b/lib/feature/login/controllers/login_controller.dart @@ -73,7 +73,7 @@ class LoginController extends GetxController { _userStorageService.isLogged = true; - Get.toNamed(AppRoutes.homePage); + Get.toNamed(AppRoutes.mainPage); } catch (e, stackTrace) { logC.e(e, stackTrace: stackTrace); Get.snackbar("Error", "Failed to connect to server"); @@ -104,7 +104,7 @@ class LoginController extends GetxController { _userStorageService.isLogged = true; - Get.toNamed(AppRoutes.homePage); + Get.toNamed(AppRoutes.mainPage); } catch (e, stackTrace) { logC.e("Google Sign-In Error: $e", stackTrace: stackTrace); Get.snackbar("Error", "Google sign-in error"); diff --git a/lib/feature/login/view/login_page.dart b/lib/feature/login/view/login_page.dart index 9840f42..85d7258 100644 --- a/lib/feature/login/view/login_page.dart +++ b/lib/feature/login/view/login_page.dart @@ -1,5 +1,6 @@ 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/app_name.dart'; import 'package:quiz_app/component/global_button.dart'; import 'package:quiz_app/component/global_text_field.dart'; @@ -14,7 +15,7 @@ class LoginView extends GetView { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFFAFBFC), // background soft clean + backgroundColor: AppColors.background, // background soft clean body: SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), diff --git a/lib/feature/quiz_creation/binding/quiz_creation_binding.dart b/lib/feature/quiz_creation/binding/quiz_creation_binding.dart new file mode 100644 index 0000000..4c3154a --- /dev/null +++ b/lib/feature/quiz_creation/binding/quiz_creation_binding.dart @@ -0,0 +1,9 @@ +import "package:get/get.dart"; +import "package:quiz_app/feature/quiz_creation/controller/quiz_creation_controller.dart"; + +class QuizCreationBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => QuizCreationController()); + } +} diff --git a/lib/feature/quiz_creation/controller/quiz_creation_controller.dart b/lib/feature/quiz_creation/controller/quiz_creation_controller.dart new file mode 100644 index 0000000..04623e6 --- /dev/null +++ b/lib/feature/quiz_creation/controller/quiz_creation_controller.dart @@ -0,0 +1,17 @@ +import 'package:flutter/widgets.dart'; +import 'package:get/get.dart'; + +class QuizCreationController extends GetxController { + TextEditingController questionTC = TextEditingController(); + TextEditingController answerTC = TextEditingController(); + + RxBool isGenerate = true.obs; + + Rx currentQuestionType = QuestionType.fillTheBlank.obs; + + onCreationTypeChange(bool value) => isGenerate.value = value; + + onQuestionTypeChange(QuestionType type) => currentQuestionType.value = type; +} + +enum QuestionType { fillTheBlank, option, trueOrFalse } diff --git a/lib/feature/quiz_creation/view/component/custom_question_component.dart b/lib/feature/quiz_creation/view/component/custom_question_component.dart new file mode 100644 index 0000000..0c02854 --- /dev/null +++ b/lib/feature/quiz_creation/view/component/custom_question_component.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:quiz_app/app/const/colors/app_colors.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/option_question_component.dart'; +import 'package:quiz_app/feature/quiz_creation/view/component/true_or_false_component.dart'; + +class CustomQuestionComponent extends GetView { + const CustomQuestionComponent({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + _buildNumberPicker(), + const SizedBox(height: 20), + _buildQuizTypeSelector(), + const SizedBox(height: 20), + _questionTypeValue(), + const SizedBox(height: 20), + _buildDurationDropdown(), + ], + ); + } + + Widget _questionTypeValue() { + return Obx(() { + switch (controller.currentQuestionType.value) { + case QuestionType.fillTheBlank: + return FillTheBlankComponent( + questionTC: controller.questionTC, + answerTC: controller.answerTC, + ); + case QuestionType.option: + return OptionQuestionComponent( + questionTC: TextEditingController(), + optionTCList: List.generate(4, (index) => TextEditingController()), + ); + case QuestionType.trueOrFalse: + return TrueFalseQuestionComponent(questionTC: controller.questionTC); + } + }); + } + + Widget _buildNumberPicker() { + return Wrap( + spacing: 8, + runSpacing: 8, + children: List.generate(14, (index) { + return Container( + width: 42, + height: 42, + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppColors.borderLight), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 4, + offset: const Offset(2, 2), + ), + ], + ), + child: Text( + '${index + 1}', + style: const TextStyle( + fontWeight: FontWeight.w600, + color: AppColors.darkText, + ), + ), + ); + }), + ); + } + + Widget _buildQuizTypeSelector() { + return Container( + decoration: BoxDecoration( + color: AppColors.background, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColors.borderLight), + ), + child: Row( + children: [ + _buildQuizTypeButton( + 'Fill the Blanks', + type: QuestionType.fillTheBlank, + ), + _buildQuizTypeButton( + 'Option', + type: QuestionType.option, + ), + _buildQuizTypeButton( + 'True / False', + type: QuestionType.trueOrFalse, + ), + ], + ), + ); + } + + Widget _buildQuizTypeButton(String label, {required QuestionType type}) { + return Expanded( + child: Obx(() { + final bool isSelected = controller.currentQuestionType.value == type; + return GestureDetector( + onTap: () => controller.onQuestionTypeChange(type), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 14), + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryBlue : Colors.transparent, + borderRadius: BorderRadius.circular(12), + ), + alignment: Alignment.center, + child: Text( + label, + style: TextStyle( + color: isSelected ? Colors.white : AppColors.softGrayText, + fontWeight: FontWeight.w600, + ), + textAlign: TextAlign.center, + ), + ), + ); + }), + ); + } + + Widget _buildDurationDropdown() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColors.borderLight), + ), + child: DropdownButtonFormField( + value: '1 minute', + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 14), + ), + style: const TextStyle(color: AppColors.darkText, fontWeight: FontWeight.w500), + items: const [ + DropdownMenuItem(value: '30 seconds', child: Text('30 seconds')), + DropdownMenuItem(value: '1 minute', child: Text('1 minute')), + DropdownMenuItem(value: '2 minutes', child: Text('2 minutes')), + ], + onChanged: (value) {}, + ), + ); + } +} diff --git a/lib/feature/quiz_creation/view/component/fill_the_blank_component.dart b/lib/feature/quiz_creation/view/component/fill_the_blank_component.dart new file mode 100644 index 0000000..ab411ea --- /dev/null +++ b/lib/feature/quiz_creation/view/component/fill_the_blank_component.dart @@ -0,0 +1,31 @@ +import 'package:flutter/widgets.dart'; +import 'package:quiz_app/component/global_text_field.dart'; +import 'package:quiz_app/component/label_text_field.dart'; + +class FillTheBlankComponent extends StatelessWidget { + final TextEditingController questionTC; + final TextEditingController answerTC; + + const FillTheBlankComponent({super.key, required this.questionTC, required this.answerTC}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + LabelTextField(label: "Pertanyaan"), + GlobalTextField( + controller: questionTC, + limitTextLine: 3, + hintText: "Tulis Pertanyaan", + ), + const SizedBox(height: 15), + LabelTextField(label: "Jawaban"), + GlobalTextField( + controller: answerTC, + hintText: "Tulis Jawaban", + ), + const SizedBox(height: 20), + ], + ); + } +} diff --git a/lib/feature/quiz_creation/view/component/generate_component.dart b/lib/feature/quiz_creation/view/component/generate_component.dart new file mode 100644 index 0000000..70ef06a --- /dev/null +++ b/lib/feature/quiz_creation/view/component/generate_component.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:quiz_app/feature/quiz_creation/controller/quiz_creation_controller.dart'; + +class GenerateComponent extends GetView { + const GenerateComponent({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Unggah file materi kamu (PDF atau Word) untuk membuat soal otomatis.", + style: TextStyle( + fontSize: 14, + color: Color(0xFF6B778C), + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 16), + GestureDetector( + onTap: () {}, + child: Container( + width: double.infinity, + 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), + ), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/feature/quiz_creation/view/component/option_question_component.dart b/lib/feature/quiz_creation/view/component/option_question_component.dart new file mode 100644 index 0000000..1e46db0 --- /dev/null +++ b/lib/feature/quiz_creation/view/component/option_question_component.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:quiz_app/component/global_text_field.dart'; +import 'package:quiz_app/component/label_text_field.dart'; + +class OptionQuestionComponent extends StatefulWidget { + final TextEditingController questionTC; + final List optionTCList; + + const OptionQuestionComponent({ + super.key, + required this.questionTC, + required this.optionTCList, + }); + + @override + State createState() => _OptionQuestionComponentState(); +} + +class _OptionQuestionComponentState extends State { + String? selectedCorrectAnswer; // A, B, C, D + + @override + Widget build(BuildContext context) { + return Column( + children: [ + // Pertanyaan + LabelTextField(label: "Pertanyaan"), + GlobalTextField( + controller: widget.questionTC, + limitTextLine: 3, + hintText: "Tulis Pertanyaan", + ), + const SizedBox(height: 15), + + // Pilihan A, B, C, D + ...List.generate(widget.optionTCList.length, (index) { + return Column( + children: [ + LabelTextField(label: "Pilihan ${String.fromCharCode(65 + index)}"), + GlobalTextField( + controller: widget.optionTCList[index], + hintText: "Tulis Pilihan ${String.fromCharCode(65 + index)}", + ), + const SizedBox(height: 10), + ], + ); + }), + + // Jawaban Benar Dropdown + const SizedBox(height: 10), + LabelTextField(label: "Jawaban Benar"), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: selectedCorrectAnswer, + hint: const Text('Pilih Jawaban Benar'), + isExpanded: true, + items: List.generate(widget.optionTCList.length, (index) { + final optionLabel = String.fromCharCode(65 + index); // 'A', 'B', 'C', etc. + return DropdownMenuItem( + value: optionLabel, + child: Text(optionLabel), + ); + }), + onChanged: (value) { + setState(() { + selectedCorrectAnswer = value; + }); + }, + ), + ), + ), + + const SizedBox(height: 20), + ], + ); + } +} diff --git a/lib/feature/quiz_creation/view/component/true_or_false_component.dart b/lib/feature/quiz_creation/view/component/true_or_false_component.dart new file mode 100644 index 0000000..7523842 --- /dev/null +++ b/lib/feature/quiz_creation/view/component/true_or_false_component.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:quiz_app/component/global_text_field.dart'; +import 'package:quiz_app/component/label_text_field.dart'; + +class TrueFalseQuestionComponent extends StatefulWidget { + final TextEditingController questionTC; + + const TrueFalseQuestionComponent({ + super.key, + required this.questionTC, + }); + + @override + State createState() => _TrueFalseQuestionComponentState(); +} + +class _TrueFalseQuestionComponentState extends State { + bool? selectedAnswer; // true or false + + @override + Widget build(BuildContext context) { + return Column( + children: [ + // Pertanyaan + LabelTextField(label: "Pertanyaan"), + GlobalTextField( + controller: widget.questionTC, + limitTextLine: 3, + hintText: "Tulis Pertanyaan", + ), + const SizedBox(height: 15), + + // Jawaban Dropdown + LabelTextField(label: "Jawaban Benar"), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: selectedAnswer, + hint: const Text('Pilih Jawaban Benar'), + isExpanded: true, + items: const [ + DropdownMenuItem( + value: true, + child: Text('True'), + ), + DropdownMenuItem( + value: false, + child: Text('False'), + ), + ], + onChanged: (value) { + setState(() { + selectedAnswer = value; + }); + }, + ), + ), + ), + + const SizedBox(height: 20), + ], + ); + } +} diff --git a/lib/feature/quiz_creation/view/quiz_creation_view.dart b/lib/feature/quiz_creation/view/quiz_creation_view.dart new file mode 100644 index 0000000..eede0b6 --- /dev/null +++ b/lib/feature/quiz_creation/view/quiz_creation_view.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:quiz_app/app/const/colors/app_colors.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/generate_component.dart'; + +class QuizCreationView extends GetView { + const QuizCreationView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar( + backgroundColor: AppColors.background, + elevation: 0, + title: const Text( + 'Create Quiz', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppColors.darkText, + ), + ), + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios_new_rounded, color: AppColors.darkText), + onPressed: () => Navigator.pop(context), + ), + centerTitle: true, + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildModeSelector(), + const SizedBox(height: 20), + Obx( + () => controller.isGenerate.value ? GenerateComponent() : CustomQuestionComponent(), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildModeSelector() { + return Container( + decoration: BoxDecoration( + color: AppColors.background, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColors.borderLight), + ), + child: Row( + children: [ + _buildModeButton('Generate', controller.isGenerate, true), + _buildModeButton('Manual', controller.isGenerate, false), + ], + ), + ); + } + + Widget _buildModeButton(String label, RxBool isSelected, bool base) { + return Expanded( + child: InkWell( + onTap: () => controller.onCreationTypeChange(base), + child: Obx( + () => Container( + padding: const EdgeInsets.symmetric(vertical: 14), + decoration: BoxDecoration( + color: isSelected.value == base ? AppColors.primaryBlue : Colors.transparent, + borderRadius: base + ? BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10), + ) + : BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + alignment: Alignment.center, + child: Text( + label, + style: TextStyle( + color: isSelected.value == base ? Colors.white : AppColors.softGrayText, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/feature/register/view/register_page.dart b/lib/feature/register/view/register_page.dart index 0063f4a..e36cbdd 100644 --- a/lib/feature/register/view/register_page.dart +++ b/lib/feature/register/view/register_page.dart @@ -1,5 +1,6 @@ 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/app_name.dart'; import 'package:quiz_app/component/global_button.dart'; import 'package:quiz_app/component/global_text_field.dart'; @@ -11,7 +12,7 @@ class RegisterView extends GetView { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFFAFBFC), + backgroundColor: AppColors.background, body: SafeArea( child: Padding( padding: const EdgeInsets.all(16.0), diff --git a/lib/feature/splash_screen/presentation/splash_screen_page.dart b/lib/feature/splash_screen/presentation/splash_screen_page.dart index eec56b5..e1d9101 100644 --- a/lib/feature/splash_screen/presentation/splash_screen_page.dart +++ b/lib/feature/splash_screen/presentation/splash_screen_page.dart @@ -1,5 +1,6 @@ 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/routes/app_pages.dart'; import 'package:quiz_app/component/app_name.dart'; import 'package:quiz_app/data/services/user_storage_service.dart'; @@ -29,7 +30,7 @@ class SplashScreenView extends StatelessWidget { }); return const Scaffold( - backgroundColor: Color(0xFFFAFBFC), + backgroundColor: AppColors.background, body: Center( child: AppName(), ),