diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 08ef65c..ea6ad94 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -778,13 +778,13 @@ "aboutApp": "حول التطبيق", "dontHaveAccount": "ليس لديك حساب؟", "loginOrRegister": "تسجيل الدخول أو التسجيل", - "myFiles" : "ملفاتي", + "myFiles": "ملفاتي", "resultsPending": "النتائج معلقة", "resultsAvailable": "النتائج متاحة", "viewReport": "عرض التقرير", "checkAvailability": "التحقق من التوفر", "readInstructions": "قراءة التعليمات", - "searchLabReport" : "ابحث عن تقرير المختبر", + "searchLabReport": "ابحث عن تقرير المختبر", "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.", "receiveOtpToast": "أين تود تلقي رمز التحقق OTP؟", "enterPhoneNumber": "أدخل رقم الهاتف", @@ -807,10 +807,10 @@ "loginByOTP": "تسجيل الدخول بواسطة OTP", "guest": "زائر", "switchAccount": "تبديل الحساب", - "lastLoginBy":"آخر تسجيل دخول بواسطة", + "lastLoginBy": "آخر تسجيل دخول بواسطة", "allSet": "جاهز! الآن يمكنك تسجيل الدخول باستخدام Face ID / Biometric أو البصمة", - "enableQuickLogin":"تمكين تسجيل الدخول السريع", - "enableMsg":"تمكين تسجيل الدخول السريع سيسمح بالتحقق من خلال Face ID / Biometric الخاص بجهازك الحالي", + "enableQuickLogin": "تمكين تسجيل الدخول السريع", + "enableMsg": "تمكين تسجيل الدخول السريع سيسمح بالتحقق من خلال Face ID / Biometric الخاص بجهازك الحالي", "notNow": "ليس الآن", "pendingActivation": "في انتظار التنشيط", "awaitingApproval": "انتظر القبول", @@ -825,6 +825,27 @@ "selectFacility": "اختر المرافق", "selectFacilitiesSubTitle": "يرجى اختيار المرفق للموعد", "selectHospitalSubTitle": "يرجى اختيار المستشفى للموعد", - "iAcceptThe" : "أوافق على", + "iAcceptThe": "أوافق على", "personalDetailsVerification": "التحقق من التفاصيل الشخصية", + "otpVerification": "التحقق من OTP", + "weHaveSendOTP": "لقد أرسلنا OTP إلى", + "via": "عبر", + "forRegistrationVerification": "للتحقق من التسجيل", + "didntReceiveIt": "لم تستلمه؟", + "resendOTP": "إعادة إرسال", + "resendIn": "إعادة الإرسال في", + "pleaseEnterAnationalID": "يرجى إدخال رقم الهوية الوطنية", + "pleaseEnterAFileNumber": "يرجى إدخال رقم الملف", + "pleaseEnterAValidEmail": "يرجى إدخال بريد إلكتروني صالح", + "pleaseEnterFullName": "يرجى إدخال الاسم الكامل", + "pleaseAcceptTermsConditions": "يرجى قبول الشروط والأحكام", + "pleaseEnterAValidIqamaID": "يرجى إدخال رقم إقامة صالح", + "pleaseEnterAValidNationalID": "يرجى إدخال رقم هوية وطنية صالح", + "pleaseEnterAValidDateOfBirth": "يرجى إدخال تاريخ ميلاد صالح", + "pleaseEnterAValidName": "يرجى إدخال اسم صالح", + "pleaseSelectAGender": "يرجى اختيار الجنس", + "pleaseSelectAMaritalStatus": "يرجى اختيار الحالة الاجتماعية", + "pleaseSelectACountry": "يرجى اختيار الدولة", + "pleaseEnterEmail": "يرجى إدخال البريد الإلكتروني", + "pleaseEnterAValidEmailFormat": "يرجى إدخال تنسيق بريد إلكتروني صالح" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index b8506f4..b90557e 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -785,7 +785,7 @@ "viewReport": "View Report", "checkAvailability": "Check Availability", "readInstructions": "Read Instructions", - "searchLabReport" : "Search Lab Report", + "searchLabReport": "Search Lab Report", "prescriptionDeliveryError": "This clinic doesn't support refill", "prepareToElevate": "Prepared to elevate your health and well-being?", "iAcceptTermsConditions": "I Accept the Terms and Conditions", @@ -797,7 +797,7 @@ "sendOTPSMS": "Send me OTP on SMS", "fullName": "Full Name", "married": "Married", - "uae" : "United Arab Emirates", + "uae": "United Arab Emirates", "malE": "Male", "loginBy": "Login By", "loginByOTP": "Login By OTP", @@ -806,22 +806,43 @@ "lastloginBy": "Last login by", "allSet": "All Set! Now you can login with Face ID or Biometric", "enableQuickLogin": "Enable Quick Login", - "enableMsg": "Enabling the quick login will verify through your existing device Face ID / Biometric", - "notNow": "Not Now", + "enableMsg": "Enabling the quick login will verify through your existing device Face ID / Biometric", + "notNow": "Not Now", "pendingActivation": "Pending Activation", "awaitingApproval": "Awaiting Approval", "enterValidNationalId": "Please enter a valid national ID or file number", "enterValidPhoneNumber": "Please enter a valid phone number", "ready": "Ready", - "medicalCentersWithCount" : "{count} Medical Centers", - "medicalCenters" : " Medical Centers", - "hospitalsWithCount" : "{count} Hospitals", + "medicalCentersWithCount": "{count} Medical Centers", + "medicalCenters": " Medical Centers", + "hospitalsWithCount": "{count} Hospitals", "selectRegion": "Select Region", "selectFacility": "Select Facilities", "selectFacilitiesSubTitle": "Please select the facility for the appointment", "selectHospitalSubTitle": "Please select the hospital for the appointment", "news": "News", - "iAcceptThe" : "I Accept the", - "personalDetailsVerification": "Personal Details Verification" + "iAcceptThe": "I Accept the", + "personalDetailsVerification": "Personal Details Verification", + "otpVerification": "OTP Verification", + "weHaveSendOTP": "We have sent you the OTP code on", + "via": "via", + "forRegistrationVerification": "for registration verification", + "didntReceiveIt": "Didn't receive it?", + "resendOTP": "Resend", + "resendIn": "resend in", + "pleaseEnterAnationalID": "Please enter a national ID", + "pleaseEnterAFileNumber": "Please enter a file number", + "pleaseEnterAValidEmail": "Please enter a valid email", + "pleaseEnterFullName": "Please enter full name", + "pleaseAcceptTermsConditions": "Please accept the terms and conditions", + "pleaseEnterAValidIqamaID": "Please enter a valid Iqama ID", + "pleaseEnterAValidNationalID": "Please enter a valid national ID", + "pleaseEnterAValidDateOfBirth": "Please enter a valid date of birth", + "pleaseEnterAValidName": "Please enter a valid name", + "pleaseSelectAGender": "Please select a gender", + "pleaseSelectAMaritalStatus": "Please select a marital status", + "pleaseSelectACountry": "Please select a country", + "pleaseEnterEmail": "Please enter email", + "pleaseEnterAValidEmailFormat": "Please enter a valid email format" } \ No newline at end of file diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 14676cb..51cdb54 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -212,7 +212,7 @@ var GET_QR_PARKING = 'Services/SWP.svc/REST/GetQRParkingByID'; //URL to get clinic list var GET_CLINICS_LIST_URL = "Services/lists.svc/REST/GetClinicCentralized"; -var GET_CLINICS_LIST_WRT_HOSPITAL_URL = "Services/Lists.svc/REST/GetClinicFromDoctorSchedule"; +var GET_CLINICS_LIST_WRT_HOSPITAL_ID_URL = "Services/Lists.svc/REST/GetClinicFromDoctorSchedule"; //URL to get active appointment list var GET_ACTIVE_APPOINTMENTS_LIST_URL = "Services/Doctors.svc/Rest/Dr_GetAppointmentActiveNumber"; @@ -727,7 +727,7 @@ const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatu class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 05a6f43..d4eafff 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -94,7 +94,7 @@ extension LoginTypeExtension on LoginTypeEnum { String get displayName { AppState appState = getIt.get(); - bool isArabic = appState.getLanguageID() == "ar"; + bool isArabic = appState.getLanguageID() == 1 ? true : false; switch (this) { case LoginTypeEnum.sms: return isArabic ? 'رسالة نصية' : 'SMS'; diff --git a/lib/core/utils/doctor_response_mapper.dart b/lib/core/utils/doctor_response_mapper.dart index fd0cb79..bff4598 100644 --- a/lib/core/utils/doctor_response_mapper.dart +++ b/lib/core/utils/doctor_response_mapper.dart @@ -7,10 +7,9 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ class DoctorMapper{ static Future getMappedDoctor(List doctorList, - {bool isArabic = false}) async { + {bool isArabic = false,double lat = 0.0,double long = 0.0}) async { RegionList regionList = RegionList(); - final lat = await Utils.getNumFromPrefs(CacheConst.userLat); - final long = await Utils.getNumFromPrefs(CacheConst.userLat); + for (var element in doctorList) { String? region = element.getRegionName(isArabic); @@ -126,11 +125,9 @@ class DoctorMapper{ static Future getMappedHospitals( List hospitalList, { - bool isArabic = false, + bool isArabic = false, double lat = 0.0,double lng = 0.0 }) async { final regionList = RegionList(); - final lat = await Utils.getNumFromPrefs(CacheConst.userLat); - final long = await Utils.getNumFromPrefs(CacheConst.userLat); for (final hospital in hospitalList) { final region = hospital.getRegionName(isArabic); if (region == null) continue; @@ -173,13 +170,13 @@ class DoctorMapper{ } else if (distance < regionData.hmgDistance) { regionData.hmgDistance = distance; } - } else if ( lat != 0&& - long != 0 && + } else if ( lat != 0.0&& + lng != 0.0 && hospital.latitude != null && hospital.longitude != null) { double calculatedDistance = calculateDistance( - lat.toDouble(), - long.toDouble(), + lat, + lng, double.parse(hospital.latitude!), double.parse(hospital.longitude!), ).abs(); diff --git a/lib/core/utils/validation_utils.dart b/lib/core/utils/validation_utils.dart index e180eaa..e335c6b 100644 --- a/lib/core/utils/validation_utils.dart +++ b/lib/core/utils/validation_utils.dart @@ -29,32 +29,32 @@ class ValidationUtils { static bool isValidatedId({String? nationalId, required Function() onOkPress, CountryEnum? selectedCountry, bool? isTermsAccepted, String? dob}) { bool isCorrectID = true; if (nationalId == null || nationalId.isEmpty) { - _dialogService.showExceptionBottomSheet(message: "Please enter a national ID", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAnationalID.tr(), onOkPressed: onOkPress); isCorrectID = false; } if (nationalId != null && nationalId.isNotEmpty && selectedCountry != null) { if (selectedCountry == CountryEnum.saudiArabia) { if (!validateIqama(nationalId)) { - _dialogService.showExceptionBottomSheet(message: "Please enter a valid Iqama ID", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidIqamaID.tr(), onOkPressed: onOkPress); return false; } } if (selectedCountry == CountryEnum.unitedArabEmirates) { if (!validateUaeNationalId(nationalId)) { - _dialogService.showExceptionBottomSheet(message: "Please enter a valid national ID", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidNationalID.tr(), onOkPressed: onOkPress); return false; } } if (dob == null || dob.isEmpty) { - _dialogService.showExceptionBottomSheet(message: "Please enter a valid date of birth", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidDateOfBirth.tr(), onOkPressed: onOkPress); return false; } if (isTermsAccepted != null && !isTermsAccepted) { - _dialogService.showExceptionBottomSheet(message: "Please accept the terms and conditions", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseAcceptTermsConditions.tr(), onOkPressed: onOkPress); return false; } } @@ -63,7 +63,7 @@ class ValidationUtils { static bool isValidatePhone({String? phoneNumber, required Function() onOkPress}) { if (phoneNumber == null || phoneNumber.isEmpty) { - _dialogService.showExceptionBottomSheet(message: "Please enter a valid phone number", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.enterValidPhoneNumber.tr(), onOkPressed: onOkPress); return false; } return true; @@ -71,7 +71,7 @@ class ValidationUtils { static bool isValidate({String? phoneNumber, required Function() onOkPress}) { if (phoneNumber == null || phoneNumber.isEmpty) { - _dialogService.showExceptionBottomSheet(message: "Please enter a valid phone number", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.enterValidPhoneNumber.tr(), onOkPressed: onOkPress); return false; } return true; @@ -104,22 +104,22 @@ class ValidationUtils { static bool validateUaeRegistration({String? name, GenderTypeEnum? gender, NationalityCountries? country, MaritalStatusTypeEnum? maritalStatus, required Function() onOkPress}) { if (name == null || name.isEmpty) { - _dialogService.showExceptionBottomSheet(message: "Please enter a valid name", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidName.tr(), onOkPressed: onOkPress); return false; } if (gender == null) { - _dialogService.showExceptionBottomSheet(message: "Please select a gender", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseSelectAGender.tr(), onOkPressed: onOkPress); return false; } if (maritalStatus == null) { - _dialogService.showExceptionBottomSheet(message: "Please select a marital status", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseSelectAMaritalStatus.tr(), onOkPressed: onOkPress); return false; } if (country == null) { - _dialogService.showExceptionBottomSheet(message: "Please select a country", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseSelectACountry.tr(), onOkPressed: onOkPress); return false; } @@ -128,13 +128,13 @@ class ValidationUtils { static bool isValidateEmail({String? email, required Function() onOkPress}) { if (email == null || email.isEmpty) { - _dialogService.showExceptionBottomSheet(message: "Please enter email", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterEmail.tr(), onOkPressed: onOkPress); return false; } final bool emailIsValid = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}$").hasMatch(email); if (!emailIsValid) { - _dialogService.showExceptionBottomSheet(message: "Please enter a valid email format", onOkPressed: onOkPress); + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidEmailFormat.tr(), onOkPressed: onOkPress); return false; } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 41b604f..02e99f1 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -381,7 +381,7 @@ class FontUtils { /// Get the appropriate font family for a specific language static String getFontFamilyForLanguage(bool isArabic) { - return isArabic ? 'Cairo' : 'Poppins'; + return isArabic ? 'GESSTwo' : 'Poppins'; } } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index cd550c7..c0b20b5 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -59,7 +59,8 @@ class AuthenticationViewModel extends ChangeNotifier { required NavigationService navigationService, required CacheService cacheService, required LocalAuthService localAuthService, - }) : _navigationService = navigationService, + }) + : _navigationService = navigationService, _dialogService = dialogService, _errorHandlerService = errorHandlerService, _appState = appState, @@ -204,13 +205,13 @@ class AuthenticationViewModel extends ChangeNotifier { final result = await _authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken); result.fold( - (failure) async { + (failure) async { // LoadingUtils.hideFullScreenLoader(); // await _errorHandlerService.handleError(failure: failure); LoaderBottomSheet.hideLoader(); _navigationService.pushPage(page: LoginScreen()); }, - (apiResponse) { + (apiResponse) { // LoadingUtils.hideFullScreenLoader(); log("apiResponse: ${apiResponse.data.toString()}"); log("messageStatus: ${apiResponse.messageStatus.toString()}"); @@ -300,7 +301,8 @@ class AuthenticationViewModel extends ChangeNotifier { final result = await _authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq); result.fold( - (failure) async => await _errorHandlerService.handleError( + (failure) async => + await _errorHandlerService.handleError( failure: failure, onUnHandledFailure: (failure) async { LoaderBottomSheet.hideLoader(); @@ -314,7 +316,7 @@ class AuthenticationViewModel extends ChangeNotifier { _navigationService.pop(); }); }), - (apiResponse) async { + (apiResponse) async { if (apiResponse.messageStatus == 2) { LoaderBottomSheet.hideLoader(); await _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage ?? "ErrorEmpty", onOkPressed: () {}); @@ -357,13 +359,13 @@ class AuthenticationViewModel extends ChangeNotifier { patientOutSA: isForRegister ? isPatientOutsideSA(request: payload) : selectedCountrySignup.countryCode == CountryEnum.saudiArabia - ? false - : true, + ? false + : true, payload: payload, ); // TODO: GET APP SMS SIGNATURE HERE - request.sMSSignature =await getSignature(); + request.sMSSignature = await getSignature(); if (checkIsUserComingForRegister(request: payload)) { _appState.setUserRegistrationPayload = RegistrationDataModelPayload.fromJson(payload); @@ -372,8 +374,8 @@ class AuthenticationViewModel extends ChangeNotifier { final resultEither = await _authenticationRepo.sendActivationCodeRepo(sendActivationCodeReq: request, isRegister: checkIsUserComingForRegister(request: payload), languageID: 'er'); resultEither.fold( - (failure) async => await _errorHandlerService.handleError(failure: failure), - (apiResponse) async { + (failure) async => await _errorHandlerService.handleError(failure: failure), + (apiResponse) async { if (apiResponse.messageStatus == 2) { LoaderBottomSheet.hideLoader(); await _dialogService.showCommonBottomSheetWithoutH( @@ -407,27 +409,27 @@ class AuthenticationViewModel extends ChangeNotifier { bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1); final request = RequestUtils.getCommonRequestWelcome( - phoneNumber: phoneNumberController.text, - otpTypeEnum: otpTypeEnum, - deviceToken: _appState.deviceToken, - // patientOutSA: _appState.getUserRegistrationPayload.projectOutSa == 1 ? true : false, - patientOutSA: isForRegister - ? _appState.getUserRegistrationPayload.projectOutSa == true - ? true - : false - : _appState.getSelectDeviceByImeiRespModelElement != null - ? _appState.getSelectDeviceByImeiRespModelElement!.outSa! - : selectedCountrySignup == CountryEnum.saudiArabia - ? false - : true, - loginTokenID: _appState.appAuthToken, - registeredData: isForRegister ? _appState.getUserRegistrationPayload : null, - nationIdText: nationalIdController.text, - countryCode: _appState.getSelectDeviceByImeiRespModelElement != null && _appState.getSelectDeviceByImeiRespModelElement!.outSa == true - ? CountryEnum.unitedArabEmirates.countryCode - : selectedCountrySignup.countryCode, - //TODO: Error Here IN Zip Code. - loginType: loginTypeEnum.toInt) + phoneNumber: phoneNumberController.text, + otpTypeEnum: otpTypeEnum, + deviceToken: _appState.deviceToken, + // patientOutSA: _appState.getUserRegistrationPayload.projectOutSa == 1 ? true : false, + patientOutSA: isForRegister + ? _appState.getUserRegistrationPayload.projectOutSa == true + ? true + : false + : _appState.getSelectDeviceByImeiRespModelElement != null + ? _appState.getSelectDeviceByImeiRespModelElement!.outSa! + : selectedCountrySignup == CountryEnum.saudiArabia + ? false + : true, + loginTokenID: _appState.appAuthToken, + registeredData: isForRegister ? _appState.getUserRegistrationPayload : null, + nationIdText: nationalIdController.text, + countryCode: _appState.getSelectDeviceByImeiRespModelElement != null && _appState.getSelectDeviceByImeiRespModelElement!.outSa == true + ? CountryEnum.unitedArabEmirates.countryCode + : selectedCountrySignup.countryCode, + //TODO: Error Here IN Zip Code. + loginType: loginTypeEnum.toInt) .toJson(); LoaderBottomSheet.showLoader(); if (isForRegister) { @@ -443,7 +445,8 @@ class AuthenticationViewModel extends ChangeNotifier { LoaderBottomSheet.hideLoader(); resultEither.fold( - (failure) async => await _errorHandlerService.handleError( + (failure) async => + await _errorHandlerService.handleError( failure: failure, onUnHandledFailure: (failure) async { LoaderBottomSheet.hideLoader(); @@ -470,7 +473,8 @@ class AuthenticationViewModel extends ChangeNotifier { final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: CheckActivationCodeRegisterReq.fromJson(request), activationCode: activationCode, isRegister: false); resultEither.fold( - (failure) async => await _errorHandlerService.handleError( + (failure) async => + await _errorHandlerService.handleError( failure: failure, onUnHandledFailure: (failure) async { LoaderBottomSheet.hideLoader(); @@ -617,8 +621,8 @@ class AuthenticationViewModel extends ChangeNotifier { } else { // authenticated = true; await insertPatientIMEIData(loginTypeEnum.toInt); + LoaderBottomSheet.hideLoader(); } - LoaderBottomSheet.hideLoader(); notifyListeners(); // navigateToHomeScreen(); } else { @@ -654,15 +658,15 @@ class AuthenticationViewModel extends ChangeNotifier { bool isOutSidePatient = selectedCountrySignup.countryCode == CountryEnum.unitedArabEmirates.countryCode ? true : false; LoaderBottomSheet.showLoader(); final request = await RequestUtils.getPatientAuthenticationRequest( - phoneNumber: phoneNumberController.text, - nationId: nationalIdController.text, - patientOutSA: isOutSidePatient, - otpTypeEnum: otpTypeEnum, - isForRegister: true, - patientId: 0, - zipCode: selectedCountrySignup.countryCode, - calenderType: calenderType, - dob: dob) + phoneNumber: phoneNumberController.text, + nationId: nationalIdController.text, + patientOutSA: isOutSidePatient, + otpTypeEnum: otpTypeEnum, + isForRegister: true, + patientId: 0, + zipCode: selectedCountrySignup.countryCode, + calenderType: calenderType, + dob: dob) .toJson(); var nRequest = Map.from(request); @@ -798,22 +802,22 @@ class AuthenticationViewModel extends ChangeNotifier { Future insertPatientIMEIData(int loginType) async { final resultEither = await _authenticationRepo.insertPatientIMEIData( patientIMEIDataRequest: PatientInsertDeviceImei( - imei: _appState.deviceToken, - deviceTypeId: _appState.getDeviceTypeID(), - patientId: _appState.getAuthenticatedUser()!.patientId!, - patientIdentificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!, - identificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!, - firstName: _appState.getAuthenticatedUser()!.firstName!, - lastName: _appState.getAuthenticatedUser()!.lastName!, - patientTypeId: _appState.getAuthenticatedUser()!.patientType, - mobileNo: _appState.getAuthenticatedUser()!.mobileNumber!, - logInTypeId: loginType, - patientOutSa: _appState.getAuthenticatedUser()!.outSa!, - outSa: _appState.getAuthenticatedUser()!.outSa == 1 ? true : false, - biometricEnabled: loginType == 1 || loginType == 2 ? false : true, - firstNameN: _appState.getAuthenticatedUser()!.firstNameN, - lastNameN: _appState.getAuthenticatedUser()!.lastNameN, - ).toJson()); + imei: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getAuthenticatedUser()!.patientId!, + patientIdentificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!, + identificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!, + firstName: _appState.getAuthenticatedUser()!.firstName!, + lastName: _appState.getAuthenticatedUser()!.lastName!, + patientTypeId: _appState.getAuthenticatedUser()!.patientType, + mobileNo: _appState.getAuthenticatedUser()!.mobileNumber!, + logInTypeId: loginType, + patientOutSa: _appState.getAuthenticatedUser()!.outSa!, + outSa: _appState.getAuthenticatedUser()!.outSa == 1 ? true : false, + biometricEnabled: loginType == 1 || loginType == 2 ? false : true, + firstNameN: _appState.getAuthenticatedUser()!.firstNameN, + lastNameN: _appState.getAuthenticatedUser()!.lastNameN, + ).toJson()); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 1) { log("Insert IMEI Success"); @@ -827,20 +831,20 @@ class AuthenticationViewModel extends ChangeNotifier { Future insertPatientDeviceData(int loginType) async { final resultEither = await _authenticationRepo.insertPatientDeviceData( patientDeviceDataRequest: InsertPatientMobileDeviceInfo( - deviceToken: _appState.deviceToken, - deviceTypeId: _appState.getDeviceTypeID(), - patientId: _appState.getAuthenticatedUser()!.patientId!, - patientTypeId: _appState.getAuthenticatedUser()!.patientType, - patientOutSa: _appState.getAuthenticatedUser()!.outSa!, - loginType: loginType, - languageId: _appState.getLanguageID(), - latitude: _appState.userLat, - longitude: _appState.userLong, - voipToken: "", - deviceType: _appState.deviceTypeID, - patientMobileNumber: _appState.getAuthenticatedUser()!.mobileNumber, - nationalId: _appState.getAuthenticatedUser()!.patientIdentificationNo, - gender: _appState.getAuthenticatedUser()!.gender) + deviceToken: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getAuthenticatedUser()!.patientId!, + patientTypeId: _appState.getAuthenticatedUser()!.patientType, + patientOutSa: _appState.getAuthenticatedUser()!.outSa!, + loginType: loginType, + languageId: _appState.getLanguageID(), + latitude: _appState.userLat, + longitude: _appState.userLong, + voipToken: "", + deviceType: _appState.deviceTypeID, + patientMobileNumber: _appState.getAuthenticatedUser()!.mobileNumber, + nationalId: _appState.getAuthenticatedUser()!.patientIdentificationNo, + gender: _appState.getAuthenticatedUser()!.gender) .toJson()); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 1) { @@ -854,18 +858,18 @@ class AuthenticationViewModel extends ChangeNotifier { Future getPatientDeviceData(int loginType) async { final resultEither = await _authenticationRepo.getPatientDeviceData( patientDeviceDataRequest: GetUserMobileDeviceData( - deviceToken: _appState.deviceToken, - deviceTypeId: _appState.getDeviceTypeID(), - patientId: _appState.getSelectDeviceByImeiRespModelElement!.patientId!, - patientType: _appState.getSelectDeviceByImeiRespModelElement!.patientType, - patientOutSa: _appState.getSelectDeviceByImeiRespModelElement!.outSa == true ? 1 : 0, - loginType: loginType, - languageId: _appState.getLanguageID(), - latitude: _appState.userLat, - longitude: _appState.userLong, - mobileNo: _appState.getSelectDeviceByImeiRespModelElement!.mobile!, - patientMobileNumber: int.parse(_appState.getSelectDeviceByImeiRespModelElement!.mobile!), - nationalId: _appState.getSelectDeviceByImeiRespModelElement!.identificationNo) + deviceToken: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getSelectDeviceByImeiRespModelElement!.patientId!, + patientType: _appState.getSelectDeviceByImeiRespModelElement!.patientType, + patientOutSa: _appState.getSelectDeviceByImeiRespModelElement!.outSa == true ? 1 : 0, + loginType: loginType, + languageId: _appState.getLanguageID(), + latitude: _appState.userLat, + longitude: _appState.userLong, + mobileNo: _appState.getSelectDeviceByImeiRespModelElement!.mobile!, + patientMobileNumber: int.parse(_appState.getSelectDeviceByImeiRespModelElement!.mobile!), + nationalId: _appState.getSelectDeviceByImeiRespModelElement!.identificationNo) .toJson()); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 1) { @@ -873,6 +877,7 @@ class AuthenticationViewModel extends ChangeNotifier { getDeviceLastLogin = deviceInfo.first['LoginType']; await checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {}); await insertPatientIMEIData(loginTypeEnum.toInt); + LoaderBottomSheet.hideLoader(); } if (apiResponse.messageStatus == 2) { LoaderBottomSheet.hideLoader(); @@ -880,7 +885,7 @@ class AuthenticationViewModel extends ChangeNotifier { message: apiResponse.errorMessage ?? "", label: LocaleKeys.notice.tr(), onOkPressed: () { - _dialogService.showPhoneNumberPickerSheet(onSMSPress: () { + _dialogService.showPhoneNumberPickerSheet(label:"Where would you like to receive OTP?", message:"Please select from the below options to receive OTP.", onSMSPress: () { checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); }, onWhatsappPress: () { checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); @@ -909,8 +914,8 @@ class AuthenticationViewModel extends ChangeNotifier { List projectDetailListModel = []; resultEither.fold( - (failure) async => await _errorHandlerService.handleError(failure: failure), - (apiResponse) async { + (failure) async => await _errorHandlerService.handleError(failure: failure), + (apiResponse) async { if (apiResponse.messageStatus == 2) { await _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage ?? "ErrorEmpty"); } else { @@ -937,6 +942,7 @@ class AuthenticationViewModel extends ChangeNotifier { }, ); } + Future getSignature() async { if (Platform.isAndroid) { return await SmsVerification.getAppSignature(); @@ -944,5 +950,4 @@ class AuthenticationViewModel extends ChangeNotifier { return null; } } - } diff --git a/lib/features/authentication/widgets/otp_verification_screen.dart b/lib/features/authentication/widgets/otp_verification_screen.dart index 6d66061..5f8cdf0 100644 --- a/lib/features/authentication/widgets/otp_verification_screen.dart +++ b/lib/features/authentication/widgets/otp_verification_screen.dart @@ -1,13 +1,22 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/animation.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'package:get_it/get_it.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/cache_consts.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart'; @@ -50,7 +59,8 @@ class OTPWidget extends StatefulWidget { final FocusNode? focusNode; final AnimatedSwitcherTransitionBuilder? pinTextAnimatedSwitcherTransition; final Duration pinTextAnimatedSwitcherDuration; - final TextDirection textDirection; + + // final TextDirection textDirection; final TextInputType keyboardType; final EdgeInsets pinBoxOuterPadding; @@ -71,7 +81,6 @@ class OTPWidget extends StatefulWidget { this.onTextChanged, this.autoFocus = false, this.focusNode, - this.textDirection = TextDirection.ltr, this.keyboardType = TextInputType.number, this.pinBoxOuterPadding = const EdgeInsets.symmetric(horizontal: 4.0), this.pinBoxColor = Colors.white, @@ -95,6 +104,8 @@ class OTPWidgetState extends State with SingleTickerProviderStateMixi bool hasFocus = false; AuthenticationViewModel? authVm; + final CacheService cacheService = GetIt.instance(); + @override void didUpdateWidget(OTPWidget oldWidget) { super.didUpdateWidget(oldWidget); @@ -138,6 +149,7 @@ class OTPWidgetState extends State with SingleTickerProviderStateMixi } focusNode.addListener(_focusListener); authVm?.otpScreenNotifier.addListener(_onOtpScreenNotifierChanged); + cacheService.remove(key: CacheConst.quickLoginEnabled); } void _controllerListener() { @@ -176,17 +188,9 @@ class OTPWidgetState extends State with SingleTickerProviderStateMixi widget.controller!.clear(); } - // Remove focus from the input if (focusNode.hasFocus) { focusNode.unfocus(); } - - // Optionally refocus after a short delay to allow user to re-enter OTP - Future.delayed(const Duration(milliseconds: 100), () { - if (mounted && widget.autoFocus) { - FocusScope.of(context).requestFocus(focusNode); - } - }); } } @@ -396,58 +400,6 @@ class OTPWidgetState extends State with SingleTickerProviderStateMixi ); } - // Widget _buildPinCode(int i, BuildContext context) { - // Color pinBoxColor = widget.pinBoxColor; - // - // if (widget.hasError) { - // pinBoxColor = widget.errorBorderColor; - // } else if (i < text.length) { - // pinBoxColor = AppColors.blackBgColor; // Custom color for filled boxes - // } else { - // pinBoxColor = widget.pinBoxColor; - // } - // - // // Change color to success when all fields are complete - // if (text.length == widget.maxLength) { - // pinBoxColor = AppColors.successColor; - // } - // - // EdgeInsets insets; - // if (i == 0) { - // insets = EdgeInsets.only( - // left: 0, - // top: widget.pinBoxOuterPadding.top, - // right: widget.pinBoxOuterPadding.right, - // bottom: widget.pinBoxOuterPadding.bottom, - // ); - // } else if (i == strList.length - 1) { - // insets = EdgeInsets.only( - // left: widget.pinBoxOuterPadding.left, - // top: widget.pinBoxOuterPadding.top, - // right: 0, - // bottom: widget.pinBoxOuterPadding.bottom, - // ); - // } else { - // insets = widget.pinBoxOuterPadding; - // } - // - // return AnimatedContainer( - // duration: const Duration(milliseconds: 200), - // curve: Curves.easeInOut, - // key: ValueKey("container$i"), - // alignment: Alignment.center, - // padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0), - // margin: insets, - // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - // color: pinBoxColor, - // borderRadius: widget.pinBoxRadius, - // ), - // width: widget.pinBoxWidth, - // height: widget.pinBoxHeight, - // child: _animatedTextBox(strList[i], i), - // ); - // } - Widget _animatedTextBox(String text, int i) { if (widget.pinTextAnimatedSwitcherTransition != null) { return AnimatedSwitcher( @@ -580,6 +532,7 @@ class _OTPVerificationScreenState extends State { @override Widget build(BuildContext context) { + AuthenticationViewModel authVM = context.read(); return Scaffold( backgroundColor: AppColors.scaffoldBgColor, appBar: CustomAppBar( @@ -595,19 +548,22 @@ class _OTPVerificationScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 40.h), - Text( - 'OTP Verification', - style: TextStyle(fontSize: 24.fSize, fontWeight: FontWeight.bold), - ), - SizedBox(height: 16.h), - Text( - 'We have sent you the OTP code on ${_getMaskedPhoneNumber()} via SMS for registration verification', - style: TextStyle(fontSize: 16.fSize, color: Colors.grey), + SizedBox(height: 10.h), + LocaleKeys.otpVerification.tr().toText24(isBold: true), + SizedBox(height: 20.h), + Wrap( + spacing: 4.h, + runSpacing: 8.0, + children: [ + LocaleKeys.weHaveSendOTP.tr().toText16(color: AppColors.inputLabelTextColor), + _getMaskedPhoneNumber().toText16(color: AppColors.inputLabelTextColor, isBold: true), + LocaleKeys.via.tr().toText16(color: AppColors.inputLabelTextColor), + authVM.loginTypeEnum.displayName.toText16(color: AppColors.inputLabelTextColor), + LocaleKeys.forRegistrationVerification.tr().toText16(color: AppColors.inputLabelTextColor), + ], ), - SizedBox(height: 40.h), - // OTP Input Fields using new OTP Widget + SizedBox(height: 16.h), Center( child: OTPWidget( maxLength: _otpLength, @@ -629,38 +585,32 @@ class _OTPVerificationScreenState extends State { ), ), ), - - const SizedBox(height: 32), + SizedBox(height: 32.h), // Resend OTP Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text("Didn't receive it? "), + LocaleKeys.didntReceiveIt.tr().toText16(color: AppColors.inputLabelTextColor), + SizedBox(width: 5.h), if (_resendTime > 0) Builder( - // Use a Builder to easily calculate minutes and seconds inline builder: (context) { - final minutes = (_resendTime ~/ 60) - .toString() - .padLeft(2, '0'); // Integer division for minutes final seconds = (_resendTime % 60).toString().padLeft(2, '0'); // Modulo for remaining seconds - final seconds = (_resendTime % 60).toString().padLeft(2, '0'); // Modulo for remaining seconds // <--- HERE IT IS - return Text( - 'resend in ($minutes:$seconds). ', - style: const TextStyle(color: Colors.grey), + final minutes = (_resendTime ~/ 60).toString().padLeft(2, '0'); + final seconds = (_resendTime % 60).toString().padLeft(2, '0'); + return Row( + children: [ + LocaleKeys.resendIn.tr().toText16(color: AppColors.inputLabelTextColor), + SizedBox(width: 2.h), + ' ($minutes:$seconds). '.toText16(color: AppColors.inputLabelTextColor) + ], ); }, ) else GestureDetector( onTap: _resendOtp, - child: const Text( - 'Resend', - style: TextStyle( - color: AppColors.primaryRedColor, - fontWeight: FontWeight.bold, - ), - ), + child: LocaleKeys.resendOTP.tr().toText16(color: AppColors.primaryRedColor), ), ], ), @@ -672,7 +622,6 @@ class _OTPVerificationScreenState extends State { } void _verifyOtp(String otp) { - debugPrint('Verifying OTP: $otp'); widget.checkActivationCode(int.parse(otp)); } @@ -683,6 +632,6 @@ class _OTPVerificationScreenState extends State { _isVerifying = false; _otpController.text = otp; setState(() {}); - _onOtpChanged(otp); // Ensure verification and color update + _onOtpChanged(otp); } } diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index f41fbc4..7454b80 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -44,6 +44,9 @@ abstract class BookAppointmentsRepo { Future>>> getProjectList(); + + Future>>> getClinicsWithRespectToClinicId(String projectID); + } class BookAppointmentsRepoImp implements BookAppointmentsRepo { @@ -409,4 +412,42 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getClinicsWithRespectToClinicId(String projectID) async { + Map mapDevice = {"ProjectID": projectID}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_CLINICS_LIST_WRT_HOSPITAL_ID_URL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListClinic']; + + final clinicsList = list.map((item) => GetClinicsListResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: clinicsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 271db44..c74b176 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -70,6 +70,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { FacilitySelection currentlySelectedFacility = FacilitySelection.ALL; bool isRegionListLoading = false; + ///this will be used to call the clinic call when navigating from my REGION SELECTION to book appointment screen + bool shouldLoadSpecificClinic = false; + String? currentlySelectedHospitalFromRegionFlow; + BookAppointmentsViewModel({required this.bookAppointmentsRepo, required this.errorHandlerService, required this.navigationService, required this.myAppointmentsViewModel}); void initializeFilteredList() { @@ -109,7 +113,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { clinicsList.clear(); } isClinicsListLoading = value; - notifyListeners(); + // notifyListeners(); } setSelectedClinic(GetClinicsListResponseModel clinic) { @@ -148,7 +152,18 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } - Future getClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async { + /// this function will decide which clinic api to be called + /// either api for region flow or the select clinic api + Future getClinics() async + { + if(shouldLoadSpecificClinic) { + getRegionSelectedClinics(); + } else { + getAllClinics(); + } + } + + Future getAllClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await bookAppointmentsRepo.getClinics(); result.fold( @@ -173,6 +188,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { Future getDoctorsList( {int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { doctorsList.clear(); + projectID = currentlySelectedHospitalFromRegionFlow != null?int.parse(currentlySelectedHospitalFromRegionFlow!):projectID; final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName); result.fold( @@ -391,6 +407,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { } Future getRegionMappedProjectList() async { + //todo handle the case in the location is switch on if(hospitalList != null && hospitalList!.registeredDoctorMap != null && hospitalList!.registeredDoctorMap!.isNotEmpty){ filteredHospitalList = hospitalList; return; @@ -408,7 +425,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { var projectList = apiResponse.data!; hospitalList = await DoctorMapper.getMappedHospitals(projectList, - isArabic: false); + isArabic: false, + lat: _appState.userLat, + lng: _appState.userLong, + ); var lat = await Utils.getNumFromPrefs(CacheConst.userLat); var lng = await Utils.getNumFromPrefs(CacheConst.userLong); @@ -469,4 +489,33 @@ class BookAppointmentsViewModel extends ChangeNotifier { isLocationEnabled().then((value) => isLocationAvaiable = value); return isLocationAvaiable; } + + void setLoadSpecificClinic(bool status) { + shouldLoadSpecificClinic = status; + } + + void setProjectID(String? mainProjectID) { + currentlySelectedHospitalFromRegionFlow = mainProjectID; + } + + Future getRegionSelectedClinics() async{ + final result = await bookAppointmentsRepo.getClinicsWithRespectToClinicId(currentlySelectedHospitalFromRegionFlow??""); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + clinicsList = apiResponse.data!; + isClinicsListLoading = false; + initializeFilteredList(); + notifyListeners(); + } + }, + ); + } + void resetFilterList(){ + filteredHospitalList = hospitalList; + } } diff --git a/lib/features/my_appointments/appointment_via_region_viewmodel.dart b/lib/features/my_appointments/appointment_via_region_viewmodel.dart index 7bcd0a6..a49e1c4 100644 --- a/lib/features/my_appointments/appointment_via_region_viewmodel.dart +++ b/lib/features/my_appointments/appointment_via_region_viewmodel.dart @@ -1,4 +1,8 @@ import 'package:flutter/foundation.dart' show ChangeNotifier; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; enum AppointmentViaRegionState { REGION_SELECTION, @@ -11,9 +15,13 @@ enum AppointmentViaRegionState { class AppointmentViaRegionViewmodel extends ChangeNotifier { String? selectedRegionId; String? selectedFacilityType; + PatientDoctorAppointmentList? selectedHospital; + final NavigationService navigationService; AppointmentViaRegionState bottomSheetState = AppointmentViaRegionState.REGION_SELECTION; + AppointmentViaRegionViewmodel({required this.navigationService}); + void setSelectedRegionId(String? regionId) { selectedRegionId = regionId; notifyListeners(); @@ -29,10 +37,16 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { notifyListeners(); } + void handleLastStep(){ + navigationService.pop(); + navigationService.push(FadePage( + page: SelectClinicPage(), + ),); + } + void handleBackPress() { switch (bottomSheetState) { case AppointmentViaRegionState.REGION_SELECTION: - // Do nothing or exit the bottom sheet break; case AppointmentViaRegionState.TYPE_SELECTION: setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION); @@ -41,19 +55,6 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { case AppointmentViaRegionState.HOSPITAL_SELECTION: setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION); break; - - // case AppointmentViaRegionState.HOSPITAL_SELECTION: - // case AppointmentViaRegionState.CLINIC_SELECTION: - // setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION); - // setFacility(null); - // break; - // case AppointmentViaRegionState.DOCTOR_SELECTION: - // if (selectedFacilityType == 'Hospital') { - // setBottomSheetState(AppointmentViaRegionState.HOSPITAL_SELECTION); - // } else if (selectedFacilityType == 'Medical Center') { - // setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION); - // } - // break; default: } } @@ -63,4 +64,8 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { setFacility(null); setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION); } + + void setHospitalModel(PatientDoctorAppointmentList? hospital) { + selectedHospital = hospital; + } } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 85c4d6d..6445de8 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -825,5 +825,26 @@ abstract class LocaleKeys { static const selectHospitalSubTitle = 'selectHospitalSubTitle'; static const iAcceptThe = 'iAcceptThe'; static const personalDetailsVerification = 'personalDetailsVerification'; + static const otpVerification = 'otpVerification'; + static const weHaveSendOTP = 'weHaveSendOTP'; + static const via = 'via'; + static const forRegistrationVerification = 'forRegistrationVerification'; + static const didntReceiveIt = 'didntReceiveIt'; + static const resendOTP = 'resendOTP'; + static const resendIn = 'resendIn'; + static const pleaseEnterAnationalID = 'pleaseEnterAnationalID'; + static const pleaseEnterAFileNumber = 'pleaseEnterAFileNumber'; + static const pleaseEnterAValidEmail = 'pleaseEnterAValidEmail'; + static const pleaseEnterFullName = 'pleaseEnterFullName'; + static const pleaseAcceptTermsConditions = 'pleaseAcceptTermsConditions'; + static const pleaseEnterAValidIqamaID = 'pleaseEnterAValidIqamaID'; + static const pleaseEnterAValidNationalID = 'pleaseEnterAValidNationalID'; + static const pleaseEnterAValidDateOfBirth = 'pleaseEnterAValidDateOfBirth'; + static const pleaseEnterAValidName = 'pleaseEnterAValidName'; + static const pleaseSelectAGender = 'pleaseSelectAGender'; + static const pleaseSelectAMaritalStatus = 'pleaseSelectAMaritalStatus'; + static const pleaseSelectACountry = 'pleaseSelectACountry'; + static const pleaseEnterEmail = 'pleaseEnterEmail'; + static const pleaseEnterAValidEmailFormat = 'pleaseEnterAValidEmailFormat'; } diff --git a/lib/main.dart b/lib/main.dart index 02f8dd1..c75cf91 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -146,7 +146,8 @@ void main() async { ), ), ChangeNotifierProvider( - create: (_) => AppointmentViaRegionViewmodel()) + create: (_) => + AppointmentViaRegionViewmodel(navigationService: getIt())) ], child: MyApp()), ), ); diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart index 242c7ca..71cc07d 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; @@ -76,21 +77,28 @@ class HospitalBottomSheetBody extends StatelessWidget { SizedBox( height: MediaQuery.sizeOf(context).height * .4, child: ListView.separated( - itemBuilder: (_, index) => HospitalListItem( - hospitalData: regionalViewModel.selectedFacilityType == - FacilitySelection.HMG.name - ? appointmentsViewModel - .filteredHospitalList! - .registeredDoctorMap![ - regionalViewModel.selectedRegionId!]! - .hmgDoctorList![index] - : appointmentsViewModel - .filteredHospitalList - ?.registeredDoctorMap?[ - regionalViewModel.selectedRegionId!] - ?.hmcDoctorList?[index], - isLocationEnabled: appointmentsViewModel.getLocationStatus(), - ), + itemBuilder: (_, index) + { + var hospital = regionalViewModel.selectedFacilityType == + FacilitySelection.HMG.name + ? appointmentsViewModel + .filteredHospitalList! + .registeredDoctorMap![ + regionalViewModel.selectedRegionId!]! + .hmgDoctorList![index] + : appointmentsViewModel + .filteredHospitalList + ?.registeredDoctorMap?[ + regionalViewModel.selectedRegionId!] + ?.hmcDoctorList?[index]; + return HospitalListItem( + hospitalData: hospital, + isLocationEnabled: appointmentsViewModel.getLocationStatus(), + ).onPress(() { + regionalViewModel.setHospitalModel(hospital); + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION); + regionalViewModel.handleLastStep(); + });}, separatorBuilder: (_, __) => SizedBox( height: 16.h, ), diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index 5150b0c..df1c8c7 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -93,7 +93,7 @@ class _RegisterNew extends State { CustomCountryDropdown( countryList: CountryEnum.values, onCountryChange: authVm.onCountryChange, - isRtl: Directionality.of(context) == TextDirection.LTR, + // isRtl: Directionality.of(context) == TextDirection.LTR, ).withVerticalPadding(8.h), Divider(height: 1.h), TextInputWidget( @@ -189,7 +189,7 @@ class _RegisterNew extends State { ), SizedBox(height: 25.h), CustomButton( - text: "Register", + text: LocaleKeys.registernow.tr(), icon: AppAssets.note_edit, onPressed: () { // Dismiss keyboard before proceeding diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index 23ea7ae..8dbe8e8 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -43,6 +43,9 @@ class _RegisterNew extends State { @override Widget build(BuildContext context) { AppState appState = getIt.get(); + var name = appState.getLanguageCode() == "en" + ? ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}") + : ("${appState.getNHICUserData.firstNameAr!.toUpperCase()} ${appState.getNHICUserData.lastNameAr!.toUpperCase()}"); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, appBar: CustomAppBar( @@ -71,8 +74,7 @@ class _RegisterNew extends State { children: [ TextInputWidget( labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), - hintText: - authVM!.isUserFromUAE() ? LocaleKeys.enterNameHere.tr() : ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}"), + hintText: authVM!.isUserFromUAE() ? LocaleKeys.enterNameHere.tr() : (name), controller: authVM!.isUserFromUAE() ? authVM!.nameController : null, isEnable: true, prefix: null, @@ -332,7 +334,7 @@ class _RegisterNew extends State { Padding( padding: const EdgeInsets.only(bottom: 10), child: CustomButton( - text: LocaleKeys.submit, + text: LocaleKeys.submit.tr(), onPressed: () { if (ValidationUtils.isValidateEmail( email: authVM!.emailController.text, diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index a6d6c5e..482d864 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -122,6 +122,8 @@ class _BookAppointmentPageState extends State { ], ).onPress(() { bookAppointmentsViewModel.setIsClinicsListLoading(true); + bookAppointmentsViewModel.setLoadSpecificClinic(false); + bookAppointmentsViewModel.setProjectID(null); Navigator.of(context).push( FadePage( page: SelectClinicPage(), @@ -194,6 +196,7 @@ class _BookAppointmentPageState extends State { } void openRegionListBottomSheet(BuildContext context) { + regionalViewModel.flush(); // AppointmentViaRegionViewmodel? viewmodel = null; showCommonBottomSheetWithoutHeight(context, title: "", @@ -203,7 +206,6 @@ class _BookAppointmentPageState extends State { child: Consumer(builder: (_, data, __) { return getRegionalSelectionWidget(data); }), callBackFunc: () { - regionalViewModel.flush(); }); } @@ -212,11 +214,20 @@ class _BookAppointmentPageState extends State { return RegionBottomSheetBody(); } if(data.bottomSheetState == AppointmentViaRegionState.TYPE_SELECTION){ + bookAppointmentsViewModel.resetFilterList(); return FacilityTypeSelectionWidget(selectedRegion: data.selectedRegionId??"",); } if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) { return HospitalBottomSheetBody(); - } else { + } + if (data.bottomSheetState == AppointmentViaRegionState.CLINIC_SELECTION) { + // Navigator.of(context).pop(); + bookAppointmentsViewModel.setIsClinicsListLoading(true); + bookAppointmentsViewModel.setLoadSpecificClinic(true); + bookAppointmentsViewModel.setProjectID(regionalViewModel.selectedHospital?.hospitalList.first?.mainProjectID.toString()); + + } + else { SizedBox.shrink(); } return SizedBox.shrink(); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 2d40869..9f8ae79 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -93,6 +93,7 @@ class _LandingPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + appState.isAuthenticated ? WelcomeWidget( onTap: () { diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index 981363e..8feeb5f 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -27,10 +27,10 @@ class _LandingNavigationState extends State { physics: const NeverScrollableScrollPhysics(), children: [ const LandingPage(), - appState.isAuthenticated ? MedicalFilePage() :/* need add feedback page */ const LandingPage(), + appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ const LandingPage(), BookAppointmentPage(), const LandingPage(), - appState.isAuthenticated ? /* need add news page */ LandingPage() : const LandingPage(), + appState.isAuthenticated ? /* need add news page */ LandingPage() : const LandingPage(), ], ), bottomNavigationBar: BottomNavigation( diff --git a/lib/widgets/appbar/app_bar_widget.dart b/lib/widgets/appbar/app_bar_widget.dart index 1b6b8f1..b446f90 100644 --- a/lib/widgets/appbar/app_bar_widget.dart +++ b/lib/widgets/appbar/app_bar_widget.dart @@ -39,7 +39,12 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { alignment: context.locale.languageCode == "ar" ? Alignment.centerRight : Alignment.centerLeft, child: GestureDetector( onTap: onBackPressed, - child: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 32.h, height: 32.h), + child: context.locale.languageCode == "ar" + ? RotatedBox( + quarterTurns: 90, + child: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 32.h, height: 32.h), + ) + : Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 32.h, height: 32.h), ), ), ), diff --git a/lib/widgets/bottomsheet/generic_bottom_sheet.dart b/lib/widgets/bottomsheet/generic_bottom_sheet.dart index 03145c8..05275fa 100644 --- a/lib/widgets/bottomsheet/generic_bottom_sheet.dart +++ b/lib/widgets/bottomsheet/generic_bottom_sheet.dart @@ -78,7 +78,6 @@ class _GenericBottomSheetState extends State { bottom: Platform.isIOS ? false : true, child: GestureDetector( onTap: () { - // Dismiss keyboard and unfocus text field _textFieldFocusNode.unfocus(); FocusScope.of(context).unfocus(); }, @@ -130,7 +129,7 @@ class _GenericBottomSheetState extends State { else ...[ widget.textController != null ? TextInputWidget( - labelText: widget.isForEmail ? LocaleKeys.email : LocaleKeys.phoneNumber, + labelText: widget.isForEmail ? LocaleKeys.email.tr() : LocaleKeys.phoneNumber.tr(), hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx", controller: widget.textController!, focusNode: _textFieldFocusNode, diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index 3ece752..85c13f2 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -41,7 +41,6 @@ class AppCustomChipWidget extends StatelessWidget { @override Widget build(BuildContext context) { - print("detected icon: $deleteIcon"); return ChipTheme( data: ChipThemeData( padding: EdgeInsets.all(0.0), diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 576e923..c50ffad 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -109,11 +109,11 @@ void showCommonBottomSheetWithoutHeight( required Widget child, required VoidCallback callBackFunc, String title = "", - - bool isCloseButtonVisible = true, - bool isFullScreen = true, - bool isDismissible = true, - Widget? titleWidget,}) { + bool isCloseButtonVisible = true, + bool isFullScreen = true, + bool isDismissible = true, + Widget? titleWidget, +}) { showModalBottomSheet( sheetAnimationStyle: AnimationStyle( duration: Duration(milliseconds: 500), // Custom animation duration @@ -141,7 +141,7 @@ void showCommonBottomSheetWithoutHeight( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ - titleWidget ?? title.toText20(weight: FontWeight.w600), + titleWidget ?? Expanded(child: title.toText20(weight: FontWeight.w600)), Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() { Navigator.of(context).pop(); }), diff --git a/lib/widgets/dropdown/country_dropdown_widget.dart b/lib/widgets/dropdown/country_dropdown_widget.dart index dd45df4..772bf39 100644 --- a/lib/widgets/dropdown/country_dropdown_widget.dart +++ b/lib/widgets/dropdown/country_dropdown_widget.dart @@ -14,7 +14,6 @@ class CustomCountryDropdown extends StatefulWidget { final List countryList; final Function(CountryEnum)? onCountryChange; final Function(String)? onPhoneNumberChanged; - final bool isRtl; final bool isFromBottomSheet; final bool isEnableTextField; Widget? textField; @@ -24,7 +23,6 @@ class CustomCountryDropdown extends StatefulWidget { required this.countryList, this.onCountryChange, this.onPhoneNumberChanged, - required this.isRtl, this.isFromBottomSheet = false, this.isEnableTextField = false, this.textField, @@ -147,11 +145,23 @@ class _CustomCountryDropdownState extends State { void _openDropdown() { if (textFocusNode.hasFocus) { textFocusNode.unfocus(); + // Wait for keyboard to close before calculating position + Future.delayed(Duration(milliseconds: 300), () { + _showDropdown(); + }); + } else { + _showDropdown(); } + } + + void _showDropdown() { AppState appState = getIt.get(); RenderBox renderBox = context.findRenderObject() as RenderBox; Offset offset = renderBox.localToGlobal(Offset.zero); + bool isRtl = appState.getLanguageCode() == "ar"; + double leftPosition = isRtl ? offset.dx + 8 + renderBox.size.width - (!widget.isFromBottomSheet ? renderBox.size.width : 60.h) : offset.dx; + _overlayEntry = OverlayEntry( builder: (context) => Stack( children: [ @@ -164,7 +174,7 @@ class _CustomCountryDropdownState extends State { ), Positioned( top: offset.dy + renderBox.size.height, - left: widget.isRtl ? offset.dx + 6.h : offset.dx - 6.h, + left: leftPosition, width: !widget.isFromBottomSheet ? renderBox.size.width : 60.h, child: Material( child: Container( @@ -209,6 +219,71 @@ class _CustomCountryDropdownState extends State { }); } + // void _openDropdown() { + // if (textFocusNode.hasFocus) { + // textFocusNode.unfocus(); + // } + // AppState appState = getIt.get(); + // RenderBox renderBox = context.findRenderObject() as RenderBox; + // Offset offset = renderBox.localToGlobal(Offset.zero); + // + // _overlayEntry = OverlayEntry( + // builder: (context) => Stack( + // children: [ + // Positioned.fill( + // child: GestureDetector( + // onTap: _closeDropdown, + // behavior: HitTestBehavior.translucent, + // child: Container(), + // ), + // ), + // Positioned( + // top: offset.dy + renderBox.size.height, + // left: offset.dx, + // width: !widget.isFromBottomSheet ? renderBox.size.width : 60.h, + // child: Material( + // child: Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 12), + // child: Column( + // children: widget.countryList + // .map( + // (country) => GestureDetector( + // onTap: () { + // setState(() { + // selectedCountry = country; + // }); + // widget.onCountryChange?.call(country); + // _closeDropdown(); + // }, + // child: Container( + // padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 16.h), + // child: Row( + // children: [ + // Utils.buildSvgWithAssets(icon: country.iconPath, width: 38.h, height: 38.h), + // if (!widget.isFromBottomSheet) SizedBox(width: 12.h), + // if (!widget.isFromBottomSheet) + // Text(appState.getLanguageCode() == "ar" ? country.nameArabic : country.displayName, + // style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2)), + // ], + // ), + // )), + // ) + // .toList(), + // ), + // ), + // ), + // ), + // ], + // ), + // ); + // + // Overlay.of(context)?.insert(_overlayEntry); + // setState(() { + // _isDropdownOpen = true; + // }); + // } + void _closeDropdown() { _overlayEntry.remove(); setState(() { diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index fa2d873..a527054 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -126,7 +126,6 @@ class TextInputWidget extends StatelessWidget { ? CustomCountryDropdown( countryList: CountryEnum.values, onCountryChange: onCountryChange, - isRtl: Directionality.of(context) == TextDirection.rtl, isFromBottomSheet: isCountryDropDown, isEnableTextField: true, onPhoneNumberChanged: onChange, @@ -191,6 +190,7 @@ class TextInputWidget extends StatelessWidget { switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h), language: appState.getLanguageCode()!, initialDate: DateTime.now(), + fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins", okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), onCalendarTypeChanged: (bool value) {