From 0a138793ae11a6b968e4c594cf155b2521a32c05 Mon Sep 17 00:00:00 2001 From: akhdanre Date: Thu, 12 Jun 2025 04:11:25 +0700 Subject: [PATCH] fix: adding back limitation --- lib/core/utils/custom_floating_loading.dart | 4 +- .../history/view/detail_history_view.dart | 20 +- .../controller/join_room_controller.dart | 8 + .../join_room/view/join_room_view.dart | 330 +++++++++--------- .../login/controllers/login_controller.dart | 12 + lib/feature/login/view/login_page.dart | 130 +++---- .../controller/profile_controller.dart | 5 +- .../controller/update_profile_controller.dart | 9 + .../profile/view/update_profile_view.dart | 104 +++--- .../controller/quiz_creation_controller.dart | 51 ++- .../view/quiz_creation_view.dart | 56 +-- .../controller/quiz_preview_controller.dart | 4 + .../component/subject_dropdown_component.dart | 1 + .../quiz_preview/view/quiz_preview.dart | 18 +- .../controller/register_controller.dart | 5 + 15 files changed, 421 insertions(+), 336 deletions(-) diff --git a/lib/core/utils/custom_floating_loading.dart b/lib/core/utils/custom_floating_loading.dart index 6ea9a5d..0bf1aa4 100644 --- a/lib/core/utils/custom_floating_loading.dart +++ b/lib/core/utils/custom_floating_loading.dart @@ -14,7 +14,9 @@ class CustomFloatingLoading { color: Colors.black.withValues(alpha: 0.5), ), const Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator( + color: Colors.white, + ), ), ], ), diff --git a/lib/feature/history/view/detail_history_view.dart b/lib/feature/history/view/detail_history_view.dart index 9818076..1d8999d 100644 --- a/lib/feature/history/view/detail_history_view.dart +++ b/lib/feature/history/view/detail_history_view.dart @@ -43,15 +43,17 @@ class DetailHistoryView extends GetView { List quizListings() { return controller.quizAnswer.questionListings - .map((e) => QuizItemWAComponent( - index: e.index, - isCorrect: e.isCorrect, - question: e.question, - targetAnswer: e.targetAnswer, - timeSpent: e.timeSpent, - type: e.type, - userAnswer: e.userAnswer, - options: e.options, + .asMap() + .entries + .map((entry) => QuizItemWAComponent( + index: entry.key + 1, + isCorrect: entry.value.isCorrect, + question: entry.value.question, + targetAnswer: entry.value.targetAnswer, + timeSpent: entry.value.timeSpent, + type: entry.value.type, + userAnswer: entry.value.userAnswer, + options: entry.value.options, )) .toList(); } diff --git a/lib/feature/join_room/controller/join_room_controller.dart b/lib/feature/join_room/controller/join_room_controller.dart index c03e583..65d6c42 100644 --- a/lib/feature/join_room/controller/join_room_controller.dart +++ b/lib/feature/join_room/controller/join_room_controller.dart @@ -24,6 +24,7 @@ class JoinRoomController extends GetxController { ); final TextEditingController codeController = TextEditingController(); + RxBool isLoading = false.obs; void joinRoom(BuildContext context) { if (!_connectionService.isCurrentlyConnected) { @@ -42,12 +43,14 @@ class JoinRoomController extends GetxController { return; } CustomFloatingLoading.showLoading(Get.overlayContext!); + isLoading.value = true; _socketService.initSocketConnection(); _socketService.joinRoom(sessionCode: code, userId: _userController.userData!.id); _socketService.errors.listen((error) { CustomNotification.error(title: "not found", message: "Ruangan tidak ditemukan"); CustomFloatingLoading.hideLoading(); + isLoading.value = false; }); _socketService.roomMessages.listen((data) { @@ -57,6 +60,7 @@ class JoinRoomController extends GetxController { final Map quizInfoJson = dataPayload["quiz_info"]; CustomFloatingLoading.hideLoading(); + isLoading.value = false; Get.toNamed( AppRoutes.waitRoomPage, arguments: WaitingRoomDTO( @@ -73,6 +77,10 @@ class JoinRoomController extends GetxController { }); } + void onGoBack() { + if (!isLoading.value) Get.back(); + } + @override void onClose() { codeController.dispose(); diff --git a/lib/feature/join_room/view/join_room_view.dart b/lib/feature/join_room/view/join_room_view.dart index 7dd1777..b37330f 100644 --- a/lib/feature/join_room/view/join_room_view.dart +++ b/lib/feature/join_room/view/join_room_view.dart @@ -12,185 +12,189 @@ class JoinRoomView extends GetView { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - extendBodyBehindAppBar: true, - appBar: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - leading: IconButton( - icon: const Icon(LucideIcons.arrowLeft, color: Colors.black87), - onPressed: () => Get.back(), + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) => controller.onGoBack(), + child: Scaffold( + backgroundColor: Colors.white, + extendBodyBehindAppBar: true, + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + leading: IconButton( + icon: const Icon(LucideIcons.arrowLeft, color: Colors.black87), + onPressed: () => Get.back(), + ), ), - ), - body: Container( - color: Colors.white, - child: SafeArea( - child: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 20), + body: Container( + color: Colors.white, + child: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 20), - // TweenAnimationBuilder( - // duration: const Duration(seconds: 1), - // tween: Tween(begin: 0.0, end: 1.0), - // builder: (context, value, child) { - // return Transform.scale( - // scale: value, - // child: child, - // ); - // }, - // child: Container( - // padding: EdgeInsets.all(22), - // decoration: BoxDecoration( - // color: AppColors.primaryBlue.withValues(alpha: 0.05), - // shape: BoxShape.circle, - // border: Border.all( - // color: AppColors.primaryBlue.withValues(alpha: 0.15), - // width: 2, - // ), - // ), - // child: Icon( - // LucideIcons.trophy, - // size: 70, - // color: AppColors.primaryBlue, - // ), - // ), - // ), + // TweenAnimationBuilder( + // duration: const Duration(seconds: 1), + // tween: Tween(begin: 0.0, end: 1.0), + // builder: (context, value, child) { + // return Transform.scale( + // scale: value, + // child: child, + // ); + // }, + // child: Container( + // padding: EdgeInsets.all(22), + // decoration: BoxDecoration( + // color: AppColors.primaryBlue.withValues(alpha: 0.05), + // shape: BoxShape.circle, + // border: Border.all( + // color: AppColors.primaryBlue.withValues(alpha: 0.15), + // width: 2, + // ), + // ), + // child: Icon( + // LucideIcons.trophy, + // size: 70, + // color: AppColors.primaryBlue, + // ), + // ), + // ), - const SizedBox(height: 30), + const SizedBox(height: 30), - TweenAnimationBuilder( - duration: const Duration(milliseconds: 800), - tween: Tween(begin: 0.0, end: 1.0), - builder: (context, value, child) { - return Opacity( - opacity: value, - child: Transform.translate( - offset: Offset(0, 20 * (1 - value)), - child: child, - ), - ); - }, - child: Text( - context.tr("ready_to_compete"), - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - textAlign: TextAlign.center, - ), - ), - - const SizedBox(height: 15), - - // Animated Subtitle - TweenAnimationBuilder( - duration: const Duration(milliseconds: 800), - tween: Tween(begin: 0.0, end: 1.0), - builder: (context, value, child) { - return Opacity( - opacity: value, - child: Transform.translate( - offset: Offset(0, 20 * (1 - value)), - child: child, - ), - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), + TweenAnimationBuilder( + duration: const Duration(milliseconds: 800), + tween: Tween(begin: 0.0, end: 1.0), + builder: (context, value, child) { + return Opacity( + opacity: value, + child: Transform.translate( + offset: Offset(0, 20 * (1 - value)), + child: child, + ), + ); + }, child: Text( - context.tr("enter_code_to_join"), + context.tr("ready_to_compete"), style: const TextStyle( - fontSize: 16, - color: Colors.black54, - height: 1.4, + fontSize: 28, + fontWeight: FontWeight.bold, + color: Colors.black87, ), textAlign: TextAlign.center, ), ), - ), - const SizedBox(height: 40), + const SizedBox(height: 15), - TweenAnimationBuilder( - duration: const Duration(milliseconds: 1000), - tween: Tween(begin: 0.0, end: 1.0), - builder: (context, value, child) { - return Opacity( - opacity: value, - child: Transform.translate( - offset: Offset(0, 30 * (1 - value)), - child: child, + // Animated Subtitle + TweenAnimationBuilder( + duration: const Duration(milliseconds: 800), + tween: Tween(begin: 0.0, end: 1.0), + builder: (context, value, child) { + return Opacity( + opacity: value, + child: Transform.translate( + offset: Offset(0, 20 * (1 - value)), + child: child, + ), + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + context.tr("enter_code_to_join"), + style: const TextStyle( + fontSize: 16, + color: Colors.black54, + height: 1.4, + ), + textAlign: TextAlign.center, ), - ); - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 30), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(24), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.08), - blurRadius: 15, - offset: const Offset(0, 5), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - LucideIcons.keySquare, - color: AppColors.primaryBlue, - size: 24, - ), - const SizedBox(width: 12), - Text( - context.tr("enter_room_code"), - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - ], - ), - const SizedBox(height: 25), - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Colors.grey.shade200, - width: 1, - ), - ), - child: GlobalTextField( - controller: controller.codeController, - hintText: context.tr("room_code_hint"), - textInputType: TextInputType.text, - forceUpperCase: true, - ), - ), - const SizedBox(height: 30), - GlobalButton( - text: context.tr("join_quiz_now"), - onPressed: () => controller.joinRoom(context), - ), - ], ), ), - ), - const SizedBox(height: 30), - ], + const SizedBox(height: 40), + + TweenAnimationBuilder( + duration: const Duration(milliseconds: 1000), + tween: Tween(begin: 0.0, end: 1.0), + builder: (context, value, child) { + return Opacity( + opacity: value, + child: Transform.translate( + offset: Offset(0, 30 * (1 - value)), + child: child, + ), + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 30), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.grey.withValues(alpha: 0.08), + blurRadius: 15, + offset: const Offset(0, 5), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + LucideIcons.keySquare, + color: AppColors.primaryBlue, + size: 24, + ), + const SizedBox(width: 12), + Text( + context.tr("enter_room_code"), + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 25), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.grey.shade200, + width: 1, + ), + ), + child: GlobalTextField( + controller: controller.codeController, + hintText: context.tr("room_code_hint"), + textInputType: TextInputType.text, + forceUpperCase: true, + ), + ), + const SizedBox(height: 30), + GlobalButton( + text: context.tr("join_quiz_now"), + onPressed: () => controller.joinRoom(context), + ), + ], + ), + ), + ), + + const SizedBox(height: 30), + ], + ), ), ), ), diff --git a/lib/feature/login/controllers/login_controller.dart b/lib/feature/login/controllers/login_controller.dart index 71f66e2..c1cb0f8 100644 --- a/lib/feature/login/controllers/login_controller.dart +++ b/lib/feature/login/controllers/login_controller.dart @@ -102,10 +102,12 @@ class LoginController extends GetxController { _userController.setUserFromEntity(userEntity); _userStorageService.isLogged = true; CustomFloatingLoading.hideLoading(); + isLoading.value = false; Get.offAllNamed(AppRoutes.mainPage); } catch (e, stackTrace) { logC.e(e, stackTrace: stackTrace); CustomFloatingLoading.hideLoading(); + isLoading.value = false; CustomNotification.error(title: "Gagal", message: "Periksa kembali email dan kata sandi Anda"); } } @@ -117,11 +119,13 @@ class LoginController extends GetxController { } try { CustomFloatingLoading.showLoading(Get.overlayContext!); + isLoading.value = true; final user = await _googleAuthService.signIn(); if (user == null) { Get.snackbar("Kesalahan", "Masuk dengan Google dibatalkan"); CustomFloatingLoading.hideLoading(); + isLoading.value = false; return; } @@ -130,6 +134,7 @@ class LoginController extends GetxController { Get.snackbar("Kesalahan", "Tidak menerima ID Token dari Google"); CustomFloatingLoading.hideLoading(); + isLoading.value = false; return; } @@ -140,13 +145,20 @@ class LoginController extends GetxController { _userController.setUserFromEntity(userEntity); _userStorageService.isLogged = true; CustomFloatingLoading.hideLoading(); + isLoading.value = false; Get.offAllNamed(AppRoutes.mainPage); } catch (e, stackTrace) { logC.e("Google Sign-In Error: $e", stackTrace: stackTrace); Get.snackbar("Error", "Google sign-in error"); + CustomFloatingLoading.hideLoading(); + isLoading.value = false; } } + void onGoBack() { + if (!isLoading.value) Get.back(); + } + void goToRegsPage() => Get.toNamed(AppRoutes.registerPage); UserEntity _convertLoginResponseToUserEntity(LoginResponseModel response) { diff --git a/lib/feature/login/view/login_page.dart b/lib/feature/login/view/login_page.dart index 2011f91..30633eb 100644 --- a/lib/feature/login/view/login_page.dart +++ b/lib/feature/login/view/login_page.dart @@ -15,70 +15,74 @@ class LoginView extends GetView { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.background, - body: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - child: ListView( - children: [ - const SizedBox(height: 40), - const AppName(), - const SizedBox(height: 40), - LabelTextField( - label: context.tr("log_in"), - fontSize: 28, - fontWeight: FontWeight.bold, - color: Color(0xFF172B4D), - ), - const SizedBox(height: 24), - LabelTextField( - label: context.tr("email"), - color: Color(0xFF6B778C), - fontSize: 14, - ), - const SizedBox(height: 6), - GlobalTextField( - controller: controller.emailController, - hintText: context.tr("enter_your_email"), - ), - const SizedBox(height: 20), - LabelTextField( - label: context.tr("password"), - color: Color(0xFF6B778C), - fontSize: 14, - ), - const SizedBox(height: 6), - Obx( - () => GlobalTextField( - controller: controller.passwordController, - isPassword: true, - obscureText: controller.isPasswordHidden.value, - onToggleVisibility: controller.togglePasswordVisibility, - hintText: context.tr("enter_your_password"), + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) => controller.onGoBack(), + child: Scaffold( + backgroundColor: AppColors.background, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: ListView( + children: [ + const SizedBox(height: 40), + const AppName(), + const SizedBox(height: 40), + LabelTextField( + label: context.tr("log_in"), + fontSize: 28, + fontWeight: FontWeight.bold, + color: Color(0xFF172B4D), ), - ), - const SizedBox(height: 32), - Obx(() => GlobalButton( - onPressed: controller.loginWithEmail, - text: context.tr("sign_in"), - type: controller.isButtonEnabled.value, - )), - const SizedBox(height: 24), - LabelTextField( - label: context.tr("or"), - alignment: Alignment.center, - color: Color(0xFF6B778C), - ), - const SizedBox(height: 24), - GoogleButton( - onPress: controller.loginWithGoogle, - ), - const SizedBox(height: 32), - RegisterTextButton( - onTap: controller.goToRegsPage, - ), - ], + const SizedBox(height: 24), + LabelTextField( + label: context.tr("email"), + color: Color(0xFF6B778C), + fontSize: 14, + ), + const SizedBox(height: 6), + GlobalTextField( + controller: controller.emailController, + hintText: context.tr("enter_your_email"), + ), + const SizedBox(height: 20), + LabelTextField( + label: context.tr("password"), + color: Color(0xFF6B778C), + fontSize: 14, + ), + const SizedBox(height: 6), + Obx( + () => GlobalTextField( + controller: controller.passwordController, + isPassword: true, + obscureText: controller.isPasswordHidden.value, + onToggleVisibility: controller.togglePasswordVisibility, + hintText: context.tr("enter_your_password"), + ), + ), + const SizedBox(height: 32), + Obx(() => GlobalButton( + onPressed: controller.loginWithEmail, + text: context.tr("sign_in"), + type: controller.isButtonEnabled.value, + )), + const SizedBox(height: 24), + LabelTextField( + label: context.tr("or"), + alignment: Alignment.center, + color: Color(0xFF6B778C), + ), + const SizedBox(height: 24), + GoogleButton( + onPress: controller.loginWithGoogle, + ), + const SizedBox(height: 32), + RegisterTextButton( + onTap: controller.goToRegsPage, + ), + ], + ), ), ), ), diff --git a/lib/feature/profile/controller/profile_controller.dart b/lib/feature/profile/controller/profile_controller.dart index cf08049..4fd78d8 100644 --- a/lib/feature/profile/controller/profile_controller.dart +++ b/lib/feature/profile/controller/profile_controller.dart @@ -71,7 +71,6 @@ class ProfileController extends GetxController { birthDate.value = _userController.userData?.birthDate ?? ""; phoneNumber.value = _userController.userData?.phone ?? ""; joinDate.value = _userController.userData?.createdAt ?? ""; - } catch (e, stackTrace) { logC.e("Failed to load user profile data: $e", stackTrace: stackTrace); } @@ -114,9 +113,9 @@ class ProfileController extends GetxController { } void editProfile() async { - final bool resultUpdate = await Get.toNamed(AppRoutes.updateProfilePage); + final resultUpdate = await Get.toNamed(AppRoutes.updateProfilePage); - if (resultUpdate) { + if (resultUpdate == true) { loadUserProfileData(); } } diff --git a/lib/feature/profile/controller/update_profile_controller.dart b/lib/feature/profile/controller/update_profile_controller.dart index 57d4e9b..f8beac4 100644 --- a/lib/feature/profile/controller/update_profile_controller.dart +++ b/lib/feature/profile/controller/update_profile_controller.dart @@ -29,6 +29,8 @@ class UpdateProfileController extends GetxController { var selectedLocale = 'en-US'.obs; + RxBool isLoading = false.obs; + final Map localeMap = { 'English': 'en-US', 'Indonesian': 'id-ID', @@ -78,6 +80,7 @@ class UpdateProfileController extends GetxController { try { CustomFloatingLoading.showLoading(Get.overlayContext!); + isLoading.value = true; final isSuccessUpdate = await _userService.updateProfileData( _userController.userData!.id, nameController.text.trim(), @@ -111,11 +114,13 @@ class UpdateProfileController extends GetxController { } } CustomFloatingLoading.hideLoading(); + isLoading.value = false; Get.back(result: true); CustomNotification.success(title: "Success", message: "Profile updated successfully"); } catch (e) { CustomNotification.success(title: "something wrong", message: "failed to update profile"); + isLoading.value = false; logC.e(e); } } @@ -124,4 +129,8 @@ class UpdateProfileController extends GetxController { final regex = RegExp(r'^([0-2][0-9]|(3)[0-1])\-((0[1-9])|(1[0-2]))\-\d{4}$'); return regex.hasMatch(date); } + + void onGoBack() { + if (!isLoading.value) Get.back(); + } } diff --git a/lib/feature/profile/view/update_profile_view.dart b/lib/feature/profile/view/update_profile_view.dart index 8d80650..291c32e 100644 --- a/lib/feature/profile/view/update_profile_view.dart +++ b/lib/feature/profile/view/update_profile_view.dart @@ -11,58 +11,62 @@ import 'package:quiz_app/feature/profile/controller/update_profile_controller.da class UpdateProfilePage extends GetView { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.background2, - appBar: AppBar( + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) => controller.onGoBack(), + child: Scaffold( backgroundColor: AppColors.background2, - title: Text('Update Profile'), - centerTitle: true, - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: ListView( - children: [ - LabelTextField(label: "Name"), - GlobalTextField(controller: controller.nameController), - SizedBox(height: 16), - LabelTextField(label: "Phone"), - GlobalTextField( - controller: controller.phoneController, - hintText: 'Enter your phone number', - ), - SizedBox(height: 16), - LabelTextField(label: "Birth Date"), - GlobalTextField( - controller: controller.birthDateController, - hintText: 'Enter your birth date', - ), - SizedBox(height: 16), - LabelTextField(label: "Locale"), - Obx(() => GlobalDropdownField( - value: controller.selectedLocale.value, - items: controller.localeMap.entries.map>((entry) { - return DropdownMenuItem( - value: entry.value, - child: Text(entry.key), // Display country name - ); - }).toList(), - onChanged: (String? newValue) { - if (newValue != null) { - controller.selectedLocale.value = newValue; - final parts = newValue.split('-'); - if (parts.length == 2) { - Get.updateLocale(Locale(parts[0], parts[1])); - } else { - Get.updateLocale(Locale(newValue)); + appBar: AppBar( + backgroundColor: AppColors.background2, + title: Text('Update Profile'), + centerTitle: true, + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: ListView( + children: [ + LabelTextField(label: "Name"), + GlobalTextField(controller: controller.nameController), + SizedBox(height: 16), + LabelTextField(label: "Phone"), + GlobalTextField( + controller: controller.phoneController, + hintText: 'Enter your phone number', + ), + SizedBox(height: 16), + LabelTextField(label: "Birth Date"), + GlobalTextField( + controller: controller.birthDateController, + hintText: 'Enter your birth date', + ), + SizedBox(height: 16), + LabelTextField(label: "Locale"), + Obx(() => GlobalDropdownField( + value: controller.selectedLocale.value, + items: controller.localeMap.entries.map>((entry) { + return DropdownMenuItem( + value: entry.value, + child: Text(entry.key), // Display country name + ); + }).toList(), + onChanged: (String? newValue) { + if (newValue != null) { + controller.selectedLocale.value = newValue; + final parts = newValue.split('-'); + if (parts.length == 2) { + Get.updateLocale(Locale(parts[0], parts[1])); + } else { + Get.updateLocale(Locale(newValue)); + } } - } - }, - )), - SizedBox(height: 32), - Center( - child: GlobalButton(text: tr("save_changes"), onPressed: controller.saveProfile), - ), - ], + }, + )), + SizedBox(height: 32), + Center( + child: GlobalButton(text: tr("save_changes"), onPressed: controller.saveProfile), + ), + ], + ), ), ), ); diff --git a/lib/feature/quiz_creation/controller/quiz_creation_controller.dart b/lib/feature/quiz_creation/controller/quiz_creation_controller.dart index ad9e2ec..c33a4ab 100644 --- a/lib/feature/quiz_creation/controller/quiz_creation_controller.dart +++ b/lib/feature/quiz_creation/controller/quiz_creation_controller.dart @@ -35,6 +35,8 @@ class QuizCreationController extends GetxController { RxInt currentDuration = 30.obs; + RxBool isLoading = false.obs; + @override void onInit() { super.onInit(); @@ -199,7 +201,7 @@ class QuizCreationController extends GetxController { void onBack(BuildContext context) { if (quizData.length <= 1) { - Navigator.pop(context); + Get.back(); } else { AppDialog.showExitConfirmationDialog(context); } @@ -240,50 +242,67 @@ class QuizCreationController extends GetxController { return; } CustomFloatingLoading.showLoading(Get.overlayContext!); + isLoading.value = true; try { BaseResponseModel> response = await _quizService.createQuizAuto(inputSentenceTC.text); - if (response.data != null) { - final previousLength = quizData.length; + if (response.data != null && response.data!.isNotEmpty) { + // Check if we should remove the initial empty question + bool shouldRemoveInitial = quizData.length == 1 && quizData[0].question == null && quizData[0].answer == null; - if (previousLength == 1) quizData.removeAt(0); + if (shouldRemoveInitial) { + quizData.clear(); + } - for (final i in response.data!) { + // Add new questions + for (final quizItem in response.data!) { QuestionType type = QuestionType.fillTheBlank; - if (i.answer.toString().toLowerCase() == 'true' || i.answer.toString().toLowerCase() == 'false') { + if (quizItem.answer.toString().toLowerCase() == 'true' || quizItem.answer.toString().toLowerCase() == 'false') { type = QuestionType.trueOrFalse; } quizData.add(QuestionData( index: quizData.length + 1, - question: i.qustion, - answer: i.answer, + question: quizItem.qustion, + answer: quizItem.answer, type: type, )); } - if (response.data!.isNotEmpty) { - selectedQuizIndex.value = previousLength; + // Set the selected index to the first newly added question + if (shouldRemoveInitial) { + selectedQuizIndex.value = 0; + } else { + // If we didn't remove initial data, select the first new question + selectedQuizIndex.value = quizData.length - response.data!.length; + } + + // Update UI with the selected question data + if (selectedQuizIndex.value < quizData.length) { final data = quizData[selectedQuizIndex.value]; questionTC.text = data.question ?? ""; answerTC.text = data.answer ?? ""; currentDuration.value = data.duration; currentQuestionType.value = data.type ?? QuestionType.fillTheBlank; - return; } } } catch (e) { logC.e("Error while generating quiz: $e"); + CustomFloatingLoading.hideLoading(); } finally { CustomFloatingLoading.hideLoading(); + isLoading.value = false; isGenerate.value = false; - inputSentenceTC.text = ""; - if (quizData.isNotEmpty && selectedQuizIndex.value == 0) { - final data = quizData[0]; + if (quizData.isNotEmpty && selectedQuizIndex.value >= quizData.length) { + selectedQuizIndex.value = 0; + } + + if (quizData.isNotEmpty) { + final data = quizData[selectedQuizIndex.value]; questionTC.text = data.question ?? ""; answerTC.text = data.answer ?? ""; currentDuration.value = data.duration; @@ -291,4 +310,8 @@ class QuizCreationController extends GetxController { } } } + + onGoBack(BuildContext context, bool didPop) { + if (!isLoading.value) onBack(context); + } } diff --git a/lib/feature/quiz_creation/view/quiz_creation_view.dart b/lib/feature/quiz_creation/view/quiz_creation_view.dart index 114db26..b687139 100644 --- a/lib/feature/quiz_creation/view/quiz_creation_view.dart +++ b/lib/feature/quiz_creation/view/quiz_creation_view.dart @@ -11,35 +11,39 @@ class QuizCreationView extends GetView { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.background, - appBar: AppBar( + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) => controller.onGoBack(context, didPop), + child: Scaffold( backgroundColor: AppColors.background, - elevation: 0, - title: Text( - context.tr('create_quiz_title'), - style: const TextStyle( - fontWeight: FontWeight.bold, - color: AppColors.darkText, + appBar: AppBar( + backgroundColor: AppColors.background, + elevation: 0, + title: Text( + context.tr('create_quiz_title'), + style: const TextStyle( + fontWeight: FontWeight.bold, + color: AppColors.darkText, + ), ), + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios_new_rounded, color: AppColors.darkText), + onPressed: () => controller.onBack(context), + ), + centerTitle: true, ), - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new_rounded, color: AppColors.darkText), - onPressed: () => controller.onBack(context), - ), - centerTitle: true, - ), - body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(20.0), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildModeSelector(context), - const SizedBox(height: 20), - Obx(() => controller.isGenerate.value ? const GenerateComponent() : const CustomQuestionComponent()), - ], + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildModeSelector(context), + const SizedBox(height: 20), + Obx(() => controller.isGenerate.value ? const GenerateComponent() : const CustomQuestionComponent()), + ], + ), ), ), ), diff --git a/lib/feature/quiz_preview/controller/quiz_preview_controller.dart b/lib/feature/quiz_preview/controller/quiz_preview_controller.dart index 08f4711..e4871be 100644 --- a/lib/feature/quiz_preview/controller/quiz_preview_controller.dart +++ b/lib/feature/quiz_preview/controller/quiz_preview_controller.dart @@ -180,6 +180,10 @@ class QuizPreviewController extends GetxController { subjectIndex.value = index; } + void onBack() { + if (!isLoading.value) Get.back(); + } + @override void onClose() { titleController.dispose(); diff --git a/lib/feature/quiz_preview/view/component/subject_dropdown_component.dart b/lib/feature/quiz_preview/view/component/subject_dropdown_component.dart index 8e982f9..123cd9f 100644 --- a/lib/feature/quiz_preview/view/component/subject_dropdown_component.dart +++ b/lib/feature/quiz_preview/view/component/subject_dropdown_component.dart @@ -34,6 +34,7 @@ class SubjectDropdownComponent extends StatelessWidget { } } }, + dropdownColor: Colors.white, decoration: InputDecoration( filled: true, fillColor: Colors.white, diff --git a/lib/feature/quiz_preview/view/quiz_preview.dart b/lib/feature/quiz_preview/view/quiz_preview.dart index 668263c..f5cdf4f 100644 --- a/lib/feature/quiz_preview/view/quiz_preview.dart +++ b/lib/feature/quiz_preview/view/quiz_preview.dart @@ -14,13 +14,17 @@ class QuizPreviewPage extends GetView { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.background, - appBar: _buildAppBar(context), - body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(20.0), - child: _buildContent(context), + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) => controller.onBack(), + child: Scaffold( + backgroundColor: AppColors.background, + appBar: _buildAppBar(context), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: _buildContent(context), + ), ), ), ); diff --git a/lib/feature/register/controller/register_controller.dart b/lib/feature/register/controller/register_controller.dart index 09db64a..b31810a 100644 --- a/lib/feature/register/controller/register_controller.dart +++ b/lib/feature/register/controller/register_controller.dart @@ -26,6 +26,8 @@ class RegisterController extends GetxController { var isPasswordHidden = true.obs; var isConfirmPasswordHidden = true.obs; + RxBool isLoading = false.obs; + @override void onReady() { if (!_connectionService.isCurrentlyConnected) { @@ -82,6 +84,7 @@ class RegisterController extends GetxController { try { CustomFloatingLoading.showLoading(Get.overlayContext!); + isLoading.value = true; await _authService.register( RegisterRequestModel( email: email, @@ -94,9 +97,11 @@ class RegisterController extends GetxController { Get.back(); CustomFloatingLoading.hideLoading(); + isLoading.value = false; CustomNotification.success(title: "Pendaftaran Berhasil", message: "Akun berhasil dibuat"); } catch (e) { CustomFloatingLoading.hideLoading(); + isLoading.value = false; String errorMessage = e.toString().replaceFirst("Exception: ", "");