diff --git a/sigap-mobile/lib/src/cores/services/azure_ocr_service.dart b/sigap-mobile/lib/src/cores/services/azure_ocr_service.dart index 9473ec9..875bfc1 100644 --- a/sigap-mobile/lib/src/cores/services/azure_ocr_service.dart +++ b/sigap-mobile/lib/src/cores/services/azure_ocr_service.dart @@ -455,18 +455,19 @@ class AzureOCRService { if (gender.contains('laki') || gender.contains('pria') || gender == 'l' || - gender == 'male') { - extractedInfo['gender'] = 'Male'; + gender == 'male' || + gender == 'm') { extractedInfo['jenis_kelamin'] = 'LAKI-LAKI'; } else if (gender.contains('perempuan') || gender.contains('wanita') || gender == 'p' || - gender == 'female') { - extractedInfo['gender'] = 'Female'; + gender == 'female' || + gender == 'f' || + gender == 'w') { extractedInfo['jenis_kelamin'] = 'PEREMPUAN'; } else { - extractedInfo['gender'] = _normalizeCase(genderText); - extractedInfo['jenis_kelamin'] = _normalizeCase(genderText); + // Default to LAKI-LAKI if gender can't be determined + extractedInfo['jenis_kelamin'] = 'LAKI-LAKI'; } } @@ -1411,7 +1412,7 @@ class AzureOCRService { // Check if KTP has all required fields bool isKtpValid(Map extractedInfo) { // Required fields for KTP validation - final requiredFields = ['nik', 'nama', 'jenis_kelamin', 'kewarganegaraan']; + final requiredFields = ['nik', 'nama', 'kewarganegaraan']; // Check that all required fields are present and not empty for (var field in requiredFields) { @@ -1452,17 +1453,20 @@ class AzureOCRService { // } // // Gender should be either "L" or "P" - final gender = extractedInfo['jenis_kelamin'] ?? ''; - if (gender.isEmpty || gender != 'LAKI-LAKI' || gender != 'PEREMPUAN') { - print('KTP validation failed: Missing or invalid gender'); - return false; - } + // final gender = extractedInfo['jenis_kelamin'] ?? ''; + + // if (gender.isEmpty || (gender != 'LAKI-LAKI' && gender != 'PEREMPUAN')) { + // print('KTP validation failed: Missing or invalid gender'); + // return false; + // } // // Nationality should be "WNI" - final nationality = extractedInfo['kewarganegaraan'] ?? 'WNI'; + final nationality = extractedInfo['kewarganegaraan'] ?? 'Wbi'; - if (nationality.isEmpty || nationality != 'WNI') { - print('KTP validation failed: Missing or invalid'); + print('Nationality: $nationality'); + + if (nationality.isEmpty || nationality != 'Wni') { + print('KTP validation failed: Missing or invalid nationality'); return false; } diff --git a/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/main/registration_form_controller.dart b/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/main/registration_form_controller.dart index 7cbfbfa..9dddc0e 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/main/registration_form_controller.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/main/registration_form_controller.dart @@ -19,6 +19,7 @@ import 'package:sigap/src/features/personalization/data/repositories/profile_rep import 'package:sigap/src/features/personalization/data/repositories/roles_repository.dart'; import 'package:sigap/src/features/personalization/data/repositories/users_repository.dart'; import 'package:sigap/src/shared/widgets/state_screeen/state_screen.dart'; +import 'package:sigap/src/utils/constants/image_strings.dart'; import 'package:sigap/src/utils/constants/num_int.dart'; import 'package:sigap/src/utils/helpers/network_manager.dart'; import 'package:sigap/src/utils/popups/loaders.dart'; @@ -74,6 +75,8 @@ class FormRegistrationController extends GetxController { final RxString submitMessage = RxString(''); final RxBool isSubmitSuccess = RxBool(false); + + @override void onInit() { super.onInit(); @@ -714,6 +717,17 @@ class FormRegistrationController extends GetxController { try { isSubmitting.value = true; + // validate all controllers before proceeding + // if (!formKey!.currentState!.validate()) { + // TLoaders.errorSnackBar( + // title: 'Validation Error', + // message: 'Please fill in all required fields correctly.', + // ); + // isSubmitting.value = false; + // submitMessage.value = 'Please fill in all required fields correctly.'; + // return; + // } + final isConnected = await NetworkManager.instance.isConnected(); if (!isConnected) { TLoaders.errorSnackBar( @@ -721,6 +735,8 @@ class FormRegistrationController extends GetxController { message: 'Please check your internet connection and try again.', ); isSubmitting.value = false; + submitMessage.value = + 'Failed to complete registration: No internet connection'; return; } @@ -744,8 +760,6 @@ class FormRegistrationController extends GetxController { birthDate: _parseBirthDate(identityController.birthDateController.text), ); - Logger().d('Created user model for update: ${updateUser.toJson()}'); - // Check if NIK is already registered (but ignore if it's the current user's NIK) final isNikTaken = await UserRepository.instance.isNikExists( updateProfile!.nik!, @@ -757,6 +771,7 @@ class FormRegistrationController extends GetxController { message: 'NIK already registered to another user!', ); isSubmitting.value = false; + submitMessage.value = 'NIK already registered to another user!'; return; } @@ -773,28 +788,27 @@ class FormRegistrationController extends GetxController { message: 'Failed to update user profile. Please try again.', ); isSubmitting.value = false; + submitMessage.value = + 'Failed to update user profile. Please try again.'; return; } - // Update user metadata with common profile info - final metadata = - UserMetadataModel(profileStatus: 'completed').toAuthMetadataJson(); - // Update metadata first - await userRepository.updateUserMetadata(metadata); + await userRepository.updateProfileStatus('completed'); Logger().d('User metadata updated successfully'); isSubmitSuccess.value = true; - submitMessage.value = 'Registration completed successfully'; + // submitMessage.value = 'Registration completed successfully'; Get.off( () => StateScreen( title: 'Registration Completed', subtitle: 'Your registration has been successfully completed.', - icon: Icons.check_circle, + image: TImages.womanHuggingEarth, + isSvg: true, showButton: true, - primaryButtonTitle: 'Go to Home', + primaryButtonTitle: 'Back to sign in', onPressed: () => AuthenticationRepository.instance.screenRedirect(), ), ); diff --git a/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/identity-verification/identity_verification_controller.dart b/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/identity-verification/identity_verification_controller.dart index e9b176a..9cec4bd 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/identity-verification/identity_verification_controller.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/identity-verification/identity_verification_controller.dart @@ -546,15 +546,8 @@ class IdentityVerificationController extends GetxController { // Save registration data using the centralized model void saveRegistrationData() async { try { - // Call the appropriate registration update function based on user type - if (isOfficer) { - // For officers, use the officer registration update function - // mainController.updateOfficerRegistrationData(); - } - // For regular users, call the three required updates mainController.updateUserRegistrationData(); - } catch (e) { isDataSaved.value = false; dataSaveMessage.value = 'Error saving registration data: $e'; @@ -564,8 +557,6 @@ class IdentityVerificationController extends GetxController { } } - - @override void onClose() { // Dispose form controllers diff --git a/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/selfie-verification/facial_verification_controller.dart b/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/selfie-verification/facial_verification_controller.dart index de7c12a..54dbace 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/selfie-verification/facial_verification_controller.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/selfie-verification/facial_verification_controller.dart @@ -48,7 +48,7 @@ class FacialVerificationService { final detectedFaces = await _edgeFunctionService.detectFaces(image); return detectedFaces.isNotEmpty; } catch (e) { - print('Error detecting face: $e'); + print('Error detecting face. Please try again later.'); return false; } } diff --git a/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/selfie-verification/selfie_verification_controller.dart b/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/selfie-verification/selfie_verification_controller.dart index 8efb51c..2a3c996 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/selfie-verification/selfie_verification_controller.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/selfie-verification/selfie_verification_controller.dart @@ -280,7 +280,7 @@ class SelfieVerificationController extends GetxController { await _compareWithIdCard(); } catch (e) { dev.log('Face detection API error: $e', name: 'SELFIE_VERIFICATION'); - selfieError.value = 'Error detecting face: $e'; + selfieError.value = 'Error detecting face: Please try again.'; isVerifyingFace.value = false; isSelfieValid.value = false; } diff --git a/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/viewer-information/personal_info_controller.dart b/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/viewer-information/personal_info_controller.dart index 8288752..17e77cf 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/viewer-information/personal_info_controller.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/controllers/signup/step/viewer-information/personal_info_controller.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:sigap/src/features/auth/presentasion/controllers/signup/main/registration_form_controller.dart'; import 'package:sigap/src/utils/validators/validation.dart'; class PersonalInfoController extends GetxController { @@ -23,6 +24,8 @@ class PersonalInfoController extends GetxController { final RxString bioError = ''.obs; final RxString addressError = ''.obs; + final RxString personalInfoError = ''.obs; // General error for personal info + // Manual validation as fallback final RxBool isFormValid = RxBool(true); @@ -113,6 +116,18 @@ class PersonalInfoController extends GetxController { return isFormValid.value; } + void handleNextStep( + GlobalKey formKey, + FormRegistrationController mainController, + ) { + if (!validate(formKey)) { + personalInfoError.value = 'Please fill the required information.'; + return; + } + mainController.nextStep(); + } + + void clearErrors() { firstNameError.value = ''; lastNameError.value = ''; diff --git a/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/main/registraion_form_screen.dart b/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/main/registraion_form_screen.dart index b6e7545..7d967b2 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/main/registraion_form_screen.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/main/registraion_form_screen.dart @@ -97,16 +97,26 @@ class FormRegistrationScreen extends StatelessWidget { currentStep: controller.currentStep.value, totalSteps: controller.totalSteps, stepTitles: controller.getStepTitles(), - onStepTapped: controller.goToStep, + onStepTapped: (index) { + // If stepping forward, use nextStep() to validate + if (index > controller.currentStep.value) { + controller.nextStep(); + } else { + // If stepping backward, just go to that step without validation + controller.goToStep(index); + } + }, style: StepIndicatorStyle.standard, ), ), ); } + // Add navigation buttons + Widget _buildStepContent(FormRegistrationController controller) { final isOfficer = controller.selectedRole.value?.isOfficer ?? false; - + // Different step content for officer vs viewer if (isOfficer) { // Officer registration flow (3 steps) diff --git a/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/identity-verification/identity_verification_step.dart b/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/identity-verification/identity_verification_step.dart index 096085a..b859a06 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/identity-verification/identity_verification_step.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/identity-verification/identity_verification_step.dart @@ -8,6 +8,7 @@ import 'package:sigap/src/features/auth/presentasion/controllers/signup/step/off import 'package:sigap/src/features/auth/presentasion/controllers/signup/step/selfie-verification/selfie_verification_controller.dart'; import 'package:sigap/src/features/auth/presentasion/controllers/signup/step/viewer-information/personal_info_controller.dart'; import 'package:sigap/src/shared/widgets/form/form_section_header.dart'; +import 'package:sigap/src/shared/widgets/navigation/step_navigation_buttons.dart'; import 'package:sigap/src/utils/constants/colors.dart'; import 'package:sigap/src/utils/constants/sizes.dart'; @@ -58,7 +59,7 @@ class IdentityVerificationStep extends StatelessWidget { onPressed: ctrl.isSavingData.value ? null - : () => _submitRegistrationData(controller, context), + : () => _submitRegistrationData(controller), style: ElevatedButton.styleFrom( backgroundColor: TColors.primary, foregroundColor: Colors.white, @@ -89,6 +90,18 @@ class IdentityVerificationStep extends StatelessWidget { ), ), + Obx( + () => StepNavigationButtons( + showPrevious: mainController.currentStep.value > 0, + isLastStep: false, + onPrevious: mainController.previousStep, + onNext: () => _submitRegistrationData(controller), + isLoading: mainController.isSubmitting.value, + errorMessage: mainController.submitMessage.value, + nextButtonText: 'Continue', + ), + ), + // Save Result Message GetBuilder( id: 'saveMessage', @@ -474,10 +487,8 @@ class IdentityVerificationStep extends StatelessWidget { // Submit registration data void _submitRegistrationData( IdentityVerificationController controller, - BuildContext context, ) async { - - // Call saveRegistrationData with all the collected data + // Call saveRegistrati`onData with all the collected data controller.saveRegistrationData(); // The rest of submission logic would be handled in the controller diff --git a/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/personal-information/personal_info_step.dart b/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/personal-information/personal_info_step.dart index 7cf78fb..c6e4e7b 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/personal-information/personal_info_step.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/personal-information/personal_info_step.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:sigap/src/features/auth/presentasion/controllers/signup/main/registration_form_controller.dart'; import 'package:sigap/src/features/auth/presentasion/controllers/signup/step/viewer-information/personal_info_controller.dart'; import 'package:sigap/src/shared/widgets/form/form_section_header.dart'; +import 'package:sigap/src/shared/widgets/navigation/step_navigation_buttons.dart'; import 'package:sigap/src/shared/widgets/text/custom_text_field.dart'; import 'package:sigap/src/utils/constants/sizes.dart'; import 'package:sigap/src/utils/validators/validation.dart'; @@ -127,6 +128,20 @@ class PersonalInfoStep extends StatelessWidget { }, ), ), + + const SizedBox(height: TSizes.spaceBtwSections), + + // Navigation buttons + Obx( + () => StepNavigationButtons( + showPrevious: mainController.currentStep.value > 0, + isLastStep: false, + onPrevious: mainController.previousStep, + onNext: () => controller.handleNextStep(formKey, mainController), + errorMessage: controller.personalInfoError.value, + nextButtonText: 'Continue', + ) + ), ], ), ); diff --git a/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/selfie-verification/selfie_verification_step.dart b/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/selfie-verification/selfie_verification_step.dart index d3974e9..c1b4194 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/selfie-verification/selfie_verification_step.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/pages/signup/step/selfie-verification/selfie_verification_step.dart @@ -805,6 +805,8 @@ class SelfieVerificationStep extends StatelessWidget { SelfieVerificationController controller, BuildContext context, ) { + final isDark = THelperFunctions.isDarkMode(context); + switch (status) { case VerificationStatus.initial: case VerificationStatus.performingLiveness: @@ -817,7 +819,7 @@ class SelfieVerificationStep extends StatelessWidget { isLoading: true, title: 'Detecting Face', icon: Icons.face_retouching_natural, - customColor: TColors.primary, + customColor: isDark ? TColors.accent : TColors.primary, ); case VerificationStatus.comparingWithID: @@ -827,7 +829,7 @@ class SelfieVerificationStep extends StatelessWidget { isLoading: true, title: 'Face Matching', icon: Icons.compare, - customColor: TColors.primary, + customColor: isDark ? TColors.accent : TColors.primary, ); case VerificationStatus.livenessCompleted: