feat: interface on the quiz creation
This commit is contained in:
parent
39ab35b2a8
commit
05a22f3360
|
@ -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);
|
||||||
|
}
|
|
@ -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/bindings/navigation_binding.dart';
|
||||||
import 'package:quiz_app/feature/navigation/views/navbar_view.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/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/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';
|
||||||
|
@ -48,6 +50,11 @@ class AppPages {
|
||||||
ProfileBinding(),
|
ProfileBinding(),
|
||||||
],
|
],
|
||||||
middlewares: [AuthMiddleware()],
|
middlewares: [AuthMiddleware()],
|
||||||
)
|
),
|
||||||
|
GetPage(
|
||||||
|
name: AppRoutes.quizCreatePage,
|
||||||
|
page: () => QuizCreationView(),
|
||||||
|
binding: QuizCreationBinding(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,4 +7,6 @@ abstract class AppRoutes {
|
||||||
static const homePage = '/home';
|
static const homePage = '/home';
|
||||||
|
|
||||||
static const mainPage = '/main';
|
static const mainPage = '/main';
|
||||||
|
|
||||||
|
static const quizCreatePage = "/quiz/creation";
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ class GlobalTextField extends StatelessWidget {
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final String? hintText;
|
final String? hintText;
|
||||||
final String? labelText;
|
final String? labelText;
|
||||||
|
final int limitTextLine;
|
||||||
final bool isPassword;
|
final bool isPassword;
|
||||||
final bool obscureText;
|
final bool obscureText;
|
||||||
final VoidCallback? onToggleVisibility;
|
final VoidCallback? onToggleVisibility;
|
||||||
|
@ -13,6 +14,7 @@ class GlobalTextField extends StatelessWidget {
|
||||||
required this.controller,
|
required this.controller,
|
||||||
this.hintText,
|
this.hintText,
|
||||||
this.labelText,
|
this.labelText,
|
||||||
|
this.limitTextLine = 1,
|
||||||
this.isPassword = false,
|
this.isPassword = false,
|
||||||
this.obscureText = false,
|
this.obscureText = false,
|
||||||
this.onToggleVisibility,
|
this.onToggleVisibility,
|
||||||
|
@ -23,31 +25,44 @@ class GlobalTextField extends StatelessWidget {
|
||||||
return TextField(
|
return TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
obscureText: isPassword ? obscureText : false,
|
obscureText: isPassword ? obscureText : false,
|
||||||
|
maxLines: limitTextLine, // <-- ini tambahan dari limitTextLine
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: labelText,
|
labelText: labelText,
|
||||||
labelStyle: const TextStyle(color: Color(0xFF6B778C), fontSize: 14),
|
labelStyle: const TextStyle(
|
||||||
|
color: Color(0xFF6B778C),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
hintStyle: const TextStyle(color: Color(0xFF6B778C), fontSize: 14),
|
hintStyle: const TextStyle(
|
||||||
|
color: Color(0xFF6B778C),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: const Color.fromARGB(255, 234, 234, 235), // Background soft white
|
fillColor: Color.fromARGB(255, 234, 234, 235),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
borderSide: BorderSide(color: Colors.transparent),
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
borderSide: BorderSide(color: Colors.transparent),
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
borderSide: const BorderSide(color: Color(0xFF0052CC), width: 2),
|
borderSide: const BorderSide(
|
||||||
|
color: Color(0xFF0052CC),
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
suffixIcon: isPassword
|
suffixIcon: isPassword
|
||||||
? IconButton(
|
? IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
obscureText ? Icons.visibility_off : Icons.visibility,
|
obscureText ? Icons.visibility_off : Icons.visibility,
|
||||||
color: const Color(0xFF6B778C),
|
color: Color(0xFF6B778C),
|
||||||
),
|
),
|
||||||
onPressed: onToggleVisibility,
|
onPressed: onToggleVisibility,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:quiz_app/app/const/colors/app_colors.dart';
|
||||||
|
|
||||||
class QuizContainerComponent extends StatelessWidget {
|
class QuizContainerComponent extends StatelessWidget {
|
||||||
const QuizContainerComponent({super.key});
|
const QuizContainerComponent({super.key});
|
||||||
|
@ -8,7 +9,7 @@ class QuizContainerComponent extends StatelessWidget {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Color(0xFFFAFBFC),
|
color: AppColors.background,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Color(0xFFE1E4E8),
|
color: Color(0xFFE1E4E8),
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:get/get.dart';
|
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';
|
import 'package:quiz_app/feature/home/controller/home_controller.dart';
|
||||||
|
|
||||||
class HomeBinding extends Bindings {
|
class HomeBinding extends Bindings {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:quiz_app/app/routes/app_pages.dart';
|
||||||
import 'package:quiz_app/data/controllers/user_controller.dart';
|
import 'package:quiz_app/data/controllers/user_controller.dart';
|
||||||
|
|
||||||
class HomeController extends GetxController {
|
class HomeController extends GetxController {
|
||||||
|
@ -6,4 +7,6 @@ class HomeController extends GetxController {
|
||||||
|
|
||||||
Rx<String> get userName => _userController.userName;
|
Rx<String> get userName => _userController.userName;
|
||||||
Rx<String?> get userImage => _userController.userImage;
|
Rx<String?> get userImage => _userController.userImage;
|
||||||
|
|
||||||
|
void goToQuizCreation() => Get.toNamed(AppRoutes.quizCreatePage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:quiz_app/app/const/colors/app_colors.dart';
|
||||||
|
|
||||||
class SearchComponent extends StatelessWidget {
|
class SearchComponent extends StatelessWidget {
|
||||||
const SearchComponent({super.key});
|
const SearchComponent({super.key});
|
||||||
|
@ -9,7 +10,7 @@ class SearchComponent extends StatelessWidget {
|
||||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFFAFBFC), // Soft background
|
color: AppColors.background, // Soft background
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
border: Border.all(color: Color(0xFFE1E4E8)), // Light border
|
border: Border.all(color: Color(0xFFE1E4E8)), // Light border
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
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/feature/home/controller/home_controller.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/button_option.dart';
|
||||||
import 'package:quiz_app/feature/home/view/component/recomendation_component.dart';
|
import 'package:quiz_app/feature/home/view/component/recomendation_component.dart';
|
||||||
|
@ -12,7 +13,7 @@ class HomeView extends GetView<HomeController> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xFFFAFBFC),
|
backgroundColor: AppColors.background,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
|
@ -32,7 +33,7 @@ class HomeView extends GetView<HomeController> {
|
||||||
),
|
),
|
||||||
// ButtonOption di luar Padding
|
// ButtonOption di luar Padding
|
||||||
ButtonOption(
|
ButtonOption(
|
||||||
onCreate: () {},
|
onCreate: controller.goToQuizCreation,
|
||||||
onCreateRoom: () {},
|
onCreateRoom: () {},
|
||||||
onJoinRoom: () {},
|
onJoinRoom: () {},
|
||||||
),
|
),
|
||||||
|
|
|
@ -73,7 +73,7 @@ class LoginController extends GetxController {
|
||||||
|
|
||||||
_userStorageService.isLogged = true;
|
_userStorageService.isLogged = true;
|
||||||
|
|
||||||
Get.toNamed(AppRoutes.homePage);
|
Get.toNamed(AppRoutes.mainPage);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
logC.e(e, stackTrace: stackTrace);
|
logC.e(e, stackTrace: stackTrace);
|
||||||
Get.snackbar("Error", "Failed to connect to server");
|
Get.snackbar("Error", "Failed to connect to server");
|
||||||
|
@ -104,7 +104,7 @@ class LoginController extends GetxController {
|
||||||
|
|
||||||
_userStorageService.isLogged = true;
|
_userStorageService.isLogged = true;
|
||||||
|
|
||||||
Get.toNamed(AppRoutes.homePage);
|
Get.toNamed(AppRoutes.mainPage);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
logC.e("Google Sign-In Error: $e", stackTrace: stackTrace);
|
logC.e("Google Sign-In Error: $e", stackTrace: stackTrace);
|
||||||
Get.snackbar("Error", "Google sign-in error");
|
Get.snackbar("Error", "Google sign-in error");
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
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/component/app_name.dart';
|
import 'package:quiz_app/component/app_name.dart';
|
||||||
import 'package:quiz_app/component/global_button.dart';
|
import 'package:quiz_app/component/global_button.dart';
|
||||||
import 'package:quiz_app/component/global_text_field.dart';
|
import 'package:quiz_app/component/global_text_field.dart';
|
||||||
|
@ -14,7 +15,7 @@ class LoginView extends GetView<LoginController> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xFFFAFBFC), // background soft clean
|
backgroundColor: AppColors.background, // background soft clean
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||||
|
|
|
@ -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>(() => QuizCreationController());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<QuestionType> currentQuestionType = QuestionType.fillTheBlank.obs;
|
||||||
|
|
||||||
|
onCreationTypeChange(bool value) => isGenerate.value = value;
|
||||||
|
|
||||||
|
onQuestionTypeChange(QuestionType type) => currentQuestionType.value = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum QuestionType { fillTheBlank, option, trueOrFalse }
|
|
@ -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<QuizCreationController> {
|
||||||
|
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<String>(
|
||||||
|
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) {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<QuizCreationController> {
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<TextEditingController> optionTCList;
|
||||||
|
|
||||||
|
const OptionQuestionComponent({
|
||||||
|
super.key,
|
||||||
|
required this.questionTC,
|
||||||
|
required this.optionTCList,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<OptionQuestionComponent> createState() => _OptionQuestionComponentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OptionQuestionComponentState extends State<OptionQuestionComponent> {
|
||||||
|
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<String>(
|
||||||
|
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<String>(
|
||||||
|
value: optionLabel,
|
||||||
|
child: Text(optionLabel),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
selectedCorrectAnswer = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<TrueFalseQuestionComponent> createState() => _TrueFalseQuestionComponentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TrueFalseQuestionComponentState extends State<TrueFalseQuestionComponent> {
|
||||||
|
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<bool>(
|
||||||
|
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),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<QuizCreationController> {
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
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/component/app_name.dart';
|
import 'package:quiz_app/component/app_name.dart';
|
||||||
import 'package:quiz_app/component/global_button.dart';
|
import 'package:quiz_app/component/global_button.dart';
|
||||||
import 'package:quiz_app/component/global_text_field.dart';
|
import 'package:quiz_app/component/global_text_field.dart';
|
||||||
|
@ -11,7 +12,7 @@ class RegisterView extends GetView<RegisterController> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xFFFAFBFC),
|
backgroundColor: AppColors.background,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
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/routes/app_pages.dart';
|
import 'package:quiz_app/app/routes/app_pages.dart';
|
||||||
import 'package:quiz_app/component/app_name.dart';
|
import 'package:quiz_app/component/app_name.dart';
|
||||||
import 'package:quiz_app/data/services/user_storage_service.dart';
|
import 'package:quiz_app/data/services/user_storage_service.dart';
|
||||||
|
@ -29,7 +30,7 @@ class SplashScreenView extends StatelessWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
return const Scaffold(
|
return const Scaffold(
|
||||||
backgroundColor: Color(0xFFFAFBFC),
|
backgroundColor: AppColors.background,
|
||||||
body: Center(
|
body: Center(
|
||||||
child: AppName(),
|
child: AppName(),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in New Issue