From d053f58b7deb730dd263efbab232862046d2a32d Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 26 Mar 2026 22:34:25 +0300 Subject: [PATCH] Updates --- assets/langs/ar-SA.json | 7 +- assets/langs/en-US.json | 7 +- ios/Runner.xcodeproj/project.pbxproj | 14 +- ios/Runner/Info.plist | 1 - lib/core/api_consts.dart | 6 +- lib/core/dependencies.dart | 8 +- lib/core/utils/validation_utils.dart | 8 + lib/extensions/string_extensions.dart | 4 +- .../authentication_view_model.dart | 7 +- .../widgets/otp_verification_screen.dart | 42 +++-- .../hmg_services/hmg_services_view_model.dart | 5 + .../insurance/insurance_view_model.dart | 2 +- .../my_appointments_view_model.dart | 1 + .../profile_settings_repo.dart | 100 ++++++++++ .../profile_settings_view_model.dart | 99 +++++++++- lib/generated/locale_keys.g.dart | 1 + .../appointments/my_doctors_page.dart | 4 +- .../widgets/doctor_rating_details.dart | 23 ++- lib/presentation/home/landing_page.dart | 4 +- .../home/widgets/large_service_card.dart | 22 ++- .../insurance_update_details_card.dart | 7 +- lib/presentation/lab/lab_order_by_test.dart | 2 +- .../medical_file/medical_file_page.dart | 173 +++++++++++------- .../widgets/patient_sick_leave_card.dart | 23 ++- .../my_family/widget/family_cards.dart | 8 +- .../profile_settings/profile_settings.dart | 84 ++++++--- .../widgets/update_email_widget.dart | 106 +++++++++++ .../symptoms_checker/user_info_selection.dart | 2 +- .../widgets/condition_card.dart | 4 +- lib/widgets/appbar/collapsing_list_view.dart | 2 +- pubspec.yaml | 4 +- 31 files changed, 598 insertions(+), 182 deletions(-) create mode 100644 lib/features/profile_settings/profile_settings_repo.dart create mode 100644 lib/presentation/profile_settings/widgets/update_email_widget.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 958a4902..268bf0a3 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -811,7 +811,7 @@ "awaitingApproval": "انتظر القبول", "news": "أخبار", "ready": "جاهز", - "enterValidNationalId": "الرجاء إدخال رقم الهوية الوطنية أو رقم الملف الصحيح", + "enterValidNationalId": "رقم الهوية أو رقم الملف غير صحيح", "enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح", "cannotEnterSaudiOrUAENumber": "لا يمكنك إدخال أرقام هواتف السعودية (00966) أو الإمارات (00971) عند اختيار دولة 'أخرى'", "medicalCentersWithCount": "{count} مراكز طبية", @@ -1274,7 +1274,7 @@ "noVitalSignsRecordedYet": "لا توجد علامات حيوية مسجلة بعد", "appointmentsAndVisits": "المواعيد والزيارات", "labAndRadiology": "المختبر والأشعة", - "activeMedicationsAndPrescriptions": "الأدوية النشطة والوصفات الطبية", + "activeMedicationsAndPrescriptions": "الوصفات الطبية", "allPrescriptions": "جميع الوصفات", "allMedications": "جميع الأدوية", "youDontHaveAnyPrescriptionsYet": "ليس لديك أي وصفات طبية بعد.", @@ -1586,5 +1586,6 @@ "generateAiAnalysisResult": "قم بإجراء تحليل لهذا المختبر AI", "ratings": "التقييمات", "hmgPharmacyText": "صيدلية الحبيب، المتجر الصيدلاني الإلكتروني المتكامل الذي تقدمه لكم مجموعة خدمات الدكتور سليمان الحبيب الطبية.", - "insuranceRequestSubmittedSuccessfully": "تم إرسال طلب تحديث بيانات التأمين بنجاح. سيتم إعلامك بمجرد الانتهاء." + "insuranceRequestSubmittedSuccessfully": "تم إرسال طلب تحديث بيانات التأمين بنجاح. سيتم إعلامك بمجرد الانتهاء.", + "updatingEmailAddress": "جارٍ تحديث عنوان البريد الإلكتروني، يرجى الانتظار..." } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index cea0082a..d1a48222 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -802,7 +802,7 @@ "notNow": "Not Now", "pendingActivation": "Pending Activation", "awaitingApproval": "Awaiting Approval", - "enterValidNationalId": "Please enter a valid national ID or file number", + "enterValidNationalId": "Invalid Identification or file number", "enterValidPhoneNumber": "Please enter a valid phone number", "cannotEnterSaudiOrUAENumber": "You cannot enter Saudi Arabia (00966) or UAE (00971) phone numbers when 'Others' country is selected", "ready": "Ready", @@ -1266,7 +1266,7 @@ "noVitalSignsRecordedYet": "No vital signs recorded yet", "appointmentsAndVisits": "Appointments & visits", "labAndRadiology": "Lab & Radiology", - "activeMedicationsAndPrescriptions": "Active Medications & Prescriptions", + "activeMedicationsAndPrescriptions": "Recent Prescriptions", "allPrescriptions": "All Prescriptions", "allMedications": "All Medications", "youDontHaveAnyPrescriptionsYet": "You don't have any prescriptions yet.", @@ -1580,5 +1580,6 @@ "generateAiAnalysisResult": "Generate AI analysis for this result", "ratings": "Ratings", "hmgPharmacyText": "Al Habib Pharmacy, the complete online Pharmaceutical store brought to you by Dr. Sulaiman Al Habib Medical Services Group.", - "insuranceRequestSubmittedSuccessfully": "Your insurance update request has been successfully submitted. You will be notified once completed." + "insuranceRequestSubmittedSuccessfully": "Your insurance update request has been successfully submitted. You will be notified once completed.", + "updatingEmailAddress": "Updating email address, Please wait..." } diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 8ed9d257..6a81b151 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -343,10 +343,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; @@ -360,10 +364,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; @@ -523,7 +531,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -709,7 +717,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -738,7 +746,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 9c805ac5..f412b006 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -101,7 +101,6 @@ audio fetch - location remote-notification voip diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 6ad7b7ce..5fae22b1 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -233,7 +233,7 @@ class ApiConsts { static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; // ************ static values for Api **************** - static final double appVersionID = 20.3; + static final double appVersionID = 20.5; // static final double appVersionID = 50.7; static final int appChannelId = 3; @@ -920,6 +920,10 @@ const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard'; const SEND_PATIENT_IMMEDIATE_UPDATE_INSURANCE_REQUEST = 'Services/OUTPs.svc/REST/PatientCompanyUpdate'; +const PROFILE_SETTING = 'Services/Patients.svc/REST/GetPateintInfoForUpdate'; + +const SAVE_SETTING = 'Services/Patients.svc/REST/UpdatePateintInfo'; + class ApiKeyConstants { static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng'; } diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 4266aa3b..657d03ea 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -50,6 +50,7 @@ import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; +import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_repo.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; @@ -172,6 +173,7 @@ class AppDependencies { getIt.registerLazySingleton(() => NotificationsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => AskDoctorRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ServicesPriceListRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => ProfileSettingsRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -224,7 +226,11 @@ class AppDependencies { authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), ); - getIt.registerLazySingleton(() => ProfileSettingsViewModel(cacheService: getIt())); + getIt.registerLazySingleton(() => ProfileSettingsViewModel( + cacheService: getIt(), + profileSettingsRepo: getIt(), + errorHandlerService: getIt(), + )); getIt.registerLazySingleton(() => DateRangeSelectorRangeViewModel()); diff --git a/lib/core/utils/validation_utils.dart b/lib/core/utils/validation_utils.dart index a8bc03d3..b5651648 100644 --- a/lib/core/utils/validation_utils.dart +++ b/lib/core/utils/validation_utils.dart @@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.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/utils.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -32,6 +33,13 @@ class ValidationUtils { isCorrectID = false; } + if(nationalId!.length == 10) { + if (Utils.isSAUDIIDValid(nationalId!) == false) { + _dialogService.showExceptionBottomSheet(message: LocaleKeys.enterValidNationalId.tr(), onOkPressed: onOkPress); + isCorrectID = false; + } + } + if (nationalId != null && nationalId.isNotEmpty && selectedCountry != null) { if (selectedCountry == CountryEnum.saudiArabia) { if (!validateIqama(nationalId)) { diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 93901497..6e28ffda 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -320,9 +320,9 @@ extension EmailValidator on String { height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal), ); - Widget toText44({Color? color, bool isBold = false}) => Text( + Widget toText44({Color? color, bool isBold = false, bool isEnglishOnly = false,}) => Text( this, - style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),), ); Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index ce54c69f..336b35ae 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -28,6 +28,7 @@ import 'package:hmg_patient_app_new/features/authentication/models/resp_models/a import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_activation_code_resp_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -619,11 +620,12 @@ class AuthenticationViewModel extends ChangeNotifier { } else { activation.list!.first.isParentUser = true; } - activation.list!.first.bloodGroup = activation.patientBlodType; + activation.list!.first.bloodGroup = activation.patientBloodType; activation.list!.first.zipCode = selectedCountrySignup == CountryEnum.others ? '0' : selectedCountrySignup.countryCode; _appState.setAuthenticatedUser(activation.list!.first); _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); - _appState.setUserBloodGroup = activation.patientBlodType ?? "N/A"; + // _appState.setUserBloodGroup = activation.patientBlodType ?? "N/A"; + _appState.setUserBloodGroup = activation.patientBloodType ?? "N/A"; } // _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); _appState.setAppAuthToken = activation.authenticationTokenId; @@ -646,6 +648,7 @@ class AuthenticationViewModel extends ChangeNotifier { await clearDefaultInputValues(); myAppointmentsVM.setIsAppointmentDataToBeLoaded(true); getIt.get().setIsInsuranceDataToBeLoaded(true); + getIt.get().setHasVitalSignDataLoaded(false); if (isUserAgreedBefore) { LoaderBottomSheet.hideLoader(); navigateToHomeScreen(); diff --git a/lib/features/authentication/widgets/otp_verification_screen.dart b/lib/features/authentication/widgets/otp_verification_screen.dart index 7ddccda8..9e80dbb9 100644 --- a/lib/features/authentication/widgets/otp_verification_screen.dart +++ b/lib/features/authentication/widgets/otp_verification_screen.dart @@ -20,6 +20,8 @@ import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; import 'package:provider/provider.dart'; import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart'; +import 'dart:ui' as ui; + typedef OnDone = void Function(String text); class ProvidedPinBoxTextAnimation { @@ -564,23 +566,27 @@ class _OTPVerificationScreenState extends State { SizedBox(height: 16.h), Center( - child: OTPWidget( - maxLength: _otpLength, - controller: _otpController, - pinBoxWidth: 70.h, - pinBoxHeight: 100.h, - pinBoxRadius: 16, - pinBoxBorderWidth: 0, - pinBoxOuterPadding: EdgeInsets.symmetric(horizontal: 4.h), - defaultBorderColor: Colors.transparent, - textBorderColor: Colors.transparent, - pinBoxColor: AppColors.whiteColor, - autoFocus: true, - onTextChanged: _onOtpChanged, - pinTextStyle: TextStyle( - fontSize: 40.f, - fontWeight: FontWeight.bold, - color: AppColors.whiteColor, + child: Directionality( + textDirection: ui.TextDirection.ltr, + child: OTPWidget( + maxLength: _otpLength, + controller: _otpController, + pinBoxWidth: 70.h, + pinBoxHeight: 100.h, + pinBoxRadius: 16, + pinBoxBorderWidth: 0, + pinBoxOuterPadding: EdgeInsets.symmetric(horizontal: 4.h), + defaultBorderColor: Colors.transparent, + textBorderColor: Colors.transparent, + pinBoxColor: AppColors.whiteColor, + autoFocus: true, + onTextChanged: _onOtpChanged, + pinTextStyle: TextStyle( + fontSize: 40.f, + fontWeight: FontWeight.bold, + color: AppColors.whiteColor, + fontFamily: "Poppins" + ), ), ), ), @@ -601,7 +607,7 @@ class _OTPVerificationScreenState extends State { children: [ LocaleKeys.resendIn.tr(context: context).toText16(color: AppColors.inputLabelTextColor), SizedBox(width: 2.h), - ' ($minutes:$seconds). '.toText16(color: AppColors.inputLabelTextColor) + ' ($minutes:$seconds). '.toText16(color: AppColors.inputLabelTextColor, isEnglishOnly: true) ], ); }, diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index 5bf450ba..7b32cc6e 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -85,6 +85,11 @@ class HmgServicesViewModel extends ChangeNotifier { List covidTestProcedureList = []; Covid19GetPaymentInfo? covidPaymentInfo; + void setHasVitalSignDataLoaded(bool hasVitalSignDataLoaded) { + this.hasVitalSignDataLoaded = hasVitalSignDataLoaded; + notifyListeners(); + } + Future getOrdersList() async {} // HHC multiple services selection diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart index d66a7a22..d1423113 100644 --- a/lib/features/insurance/insurance_view_model.dart +++ b/lib/features/insurance/insurance_view_model.dart @@ -88,7 +88,7 @@ class InsuranceViewModel extends ChangeNotifier { (failure) async { debugPrint("InsuranceViewModel: API call failed - ${failure.toString()}"); isInsuranceLoading = false; - isInsuranceDataToBeLoaded = false; + isInsuranceDataToBeLoaded = true; isInsuranceExpired = false; isInsuranceActive = false; notifyListeners(); diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index d2296a66..1584f891 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -634,6 +634,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { Future getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async { // if (!isAppointmentDataToBeLoaded) return; isPatientMyDoctorsLoading = true; + patientMyDoctorsList.clear(); notifyListeners(); final result = await myAppointmentsRepo.getPatientDoctorsList(); diff --git a/lib/features/profile_settings/profile_settings_repo.dart b/lib/features/profile_settings/profile_settings_repo.dart new file mode 100644 index 00000000..253bf505 --- /dev/null +++ b/lib/features/profile_settings/profile_settings_repo.dart @@ -0,0 +1,100 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class ProfileSettingsRepo { + /// Updates general patient info (name, phone, etc.). + Future>> updatePatientInfo({ + required Map patientInfo, + }); + + /// Deactivates (deletes) the patient's account. + Future>> deactivateAccount(); +} + +class ProfileSettingsRepoImp implements ProfileSettingsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + ProfileSettingsRepoImp({ + required this.loggerService, + required this.apiClient, + }); + + @override + Future>> updatePatientInfo({ + required Map patientInfo, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + SAVE_SETTING, + body: patientInfo, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + } 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())); + } + } + + @override + Future>> deactivateAccount() async { + final Map body = {}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + // TODO: Replace with actual deactivate-account endpoint once available + 'Services/Patients.svc/REST/Patient_DeactivateAccount', + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + } 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/profile_settings/profile_settings_view_model.dart b/lib/features/profile_settings/profile_settings_view_model.dart index e001d1bd..92f11528 100644 --- a/lib/features/profile_settings/profile_settings_view_model.dart +++ b/lib/features/profile_settings/profile_settings_view_model.dart @@ -1,18 +1,42 @@ import 'package:flutter/foundation.dart'; +import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_repo.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class ProfileSettingsViewModel extends ChangeNotifier { static const String _darkModeKey = 'is_dark_mode'; final CacheService _cacheService; + final ProfileSettingsRepo profileSettingsRepo; + final ErrorHandlerService errorHandlerService; + // ── Dark-mode state ────────────────────────────────────────────────── bool _isDarkMode = false; - bool get isDarkMode => _isDarkMode; - ProfileSettingsViewModel({required CacheService cacheService}) - : _cacheService = cacheService; + // ── Update email state ─────────────────────────────────────────────── + bool isUpdateEmailLoading = false; + bool isUpdateEmailSuccess = false; + String? updateEmailError; + + // ── Update patient info state ──────────────────────────────────────── + bool isUpdatePatientInfoLoading = false; + bool isUpdatePatientInfoSuccess = false; + String? updatePatientInfoError; + + // ── Deactivate account state ───────────────────────────────────────── + bool isDeactivateAccountLoading = false; + bool isDeactivateAccountSuccess = false; + String? deactivateAccountError; + + ProfileSettingsViewModel({ + required CacheService cacheService, + required this.profileSettingsRepo, + required this.errorHandlerService, + }) : _cacheService = cacheService; + + // ── Dark mode ──────────────────────────────────────────────────────── /// Call once at app startup (before the first frame) to restore the /// persisted dark-mode preference. @@ -30,6 +54,75 @@ class ProfileSettingsViewModel extends ChangeNotifier { notifyListeners(); } + // ── Update patient info ────────────────────────────────────────────── + + Future updatePatientInfo({ + required Map patientInfo, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isUpdatePatientInfoLoading = true; + isUpdatePatientInfoSuccess = false; + updatePatientInfoError = null; + notifyListeners(); + + final result = await profileSettingsRepo.updatePatientInfo(patientInfo: patientInfo); + + result.fold( + (failure) { + isUpdatePatientInfoLoading = false; + updatePatientInfoError = failure.message; + notifyListeners(); + if (onError != null) { + onError(failure.message); + } else { + errorHandlerService.handleError(failure: failure); + } + }, + (response) { + isUpdatePatientInfoLoading = false; + isUpdatePatientInfoSuccess = true; + notifyListeners(); + onSuccess?.call(response.data); + }, + ); + } + + // ── Deactivate account ─────────────────────────────────────────────── + + Future deactivateAccount({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isDeactivateAccountLoading = true; + isDeactivateAccountSuccess = false; + deactivateAccountError = null; + notifyListeners(); + + final result = await profileSettingsRepo.deactivateAccount(); + + result.fold( + (failure) { + isDeactivateAccountLoading = false; + deactivateAccountError = failure.message; + notifyListeners(); + if (onError != null) { + onError(failure.message); + } else { + errorHandlerService.handleError(failure: failure); + } + }, + (response) { + isDeactivateAccountLoading = false; + isDeactivateAccountSuccess = true; + notifyListeners(); + onSuccess?.call(response.data); + }, + ); + } + + // ── Helpers ────────────────────────────────────────────────────────── + void notify() { notifyListeners(); } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 2aeb87f1..031e1be9 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1581,5 +1581,6 @@ abstract class LocaleKeys { static const ratings = 'ratings'; static const hmgPharmacyText = 'hmgPharmacyText'; static const insuranceRequestSubmittedSuccessfully = 'insuranceRequestSubmittedSuccessfully'; + static const updatingEmailAddress = 'updatingEmailAddress'; } diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart index d1ea0e98..002f537c 100644 --- a/lib/presentation/appointments/my_doctors_page.dart +++ b/lib/presentation/appointments/my_doctors_page.dart @@ -93,8 +93,6 @@ class _MyDoctorsPageState extends State { borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, - - ), SizedBox(width: 8.h), CustomButton( @@ -280,7 +278,7 @@ class _MyDoctorsPageState extends State { runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: isSortByClinic ? (doctor?.clinicName ?? "") : (doctor?.projectName ?? ""), + labelText: isSortByClinic ? (doctor?.projectName ?? "") : (doctor?.clinicName ?? ""), ), ], ), diff --git a/lib/presentation/book_appointment/widgets/doctor_rating_details.dart b/lib/presentation/book_appointment/widgets/doctor_rating_details.dart index 6d9e1cb2..1a5eedfe 100644 --- a/lib/presentation/book_appointment/widgets/doctor_rating_details.dart +++ b/lib/presentation/book_appointment/widgets/doctor_rating_details.dart @@ -20,13 +20,20 @@ class DoctorRatingDetails extends StatelessWidget { : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - bookAppointmentsVM.doctorsProfileResponseModel.actualDoctorRate!.ceilToDouble().toString().toText44(isBold: true), + bookAppointmentsVM.doctorsProfileResponseModel.actualDoctorRate!.ceilToDouble().toString().toText44(isBold: true, isEnglishOnly: true), SizedBox(height: 4.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "${bookAppointmentsVM.doctorsProfileResponseModel.noOfPatientsRate} ${LocaleKeys.reviews.tr(context: context)}" - .toText16(weight: FontWeight.w500, color: AppColors.greyInfoTextColor), + Row( + children: [ + "${bookAppointmentsVM.doctorsProfileResponseModel.noOfPatientsRate} " + .toText16(weight: FontWeight.w500, color: AppColors.greyInfoTextColor, isEnglishOnly: true), + LocaleKeys.reviews.tr(context: context) + .toText16(weight: FontWeight.w500, color: AppColors.greyInfoTextColor,), + ], + ), + RatingBar( initialRating: bookAppointmentsVM.doctorsProfileResponseModel.actualDoctorRate!.toDouble(), direction: Axis.horizontal, @@ -75,7 +82,7 @@ class DoctorRatingDetails extends StatelessWidget { ), Container( margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), - child: Text("${getRatingWidth(bookAppointmentsVM.doctorDetailsList[0].ratio).round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600)), + child: Text("${getRatingWidth(bookAppointmentsVM.doctorDetailsList[0].ratio).round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600, fontFamily: "Poppins")), ), ], ), @@ -95,7 +102,7 @@ class DoctorRatingDetails extends StatelessWidget { ), Container( margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), - child: Text("${bookAppointmentsVM.doctorDetailsList[1].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600)), + child: Text("${bookAppointmentsVM.doctorDetailsList[1].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600, fontFamily: "Poppins")), ), ], ), @@ -115,7 +122,7 @@ class DoctorRatingDetails extends StatelessWidget { ), Container( margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), - child: Text("${bookAppointmentsVM.doctorDetailsList[2].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600)), + child: Text("${bookAppointmentsVM.doctorDetailsList[2].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600, fontFamily: "Poppins")), ), ], ), @@ -135,7 +142,7 @@ class DoctorRatingDetails extends StatelessWidget { ), Container( margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), - child: Text("${bookAppointmentsVM.doctorDetailsList[3].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600)), + child: Text("${bookAppointmentsVM.doctorDetailsList[3].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600, fontFamily: "Poppins")), ), ], ), @@ -156,7 +163,7 @@ class DoctorRatingDetails extends StatelessWidget { ), Container( margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), - child: Text("${bookAppointmentsVM.doctorDetailsList[4].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600)), + child: Text("${bookAppointmentsVM.doctorDetailsList[4].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600, fontFamily: "Poppins")), ), ], ), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 3012876f..0c17434e 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -407,10 +407,10 @@ class _LandingPageState extends State { ? myAppointmentsVM.patientAppointmentsHistoryList.length : 3, layout: SwiperLayout.STACK, - loop: true, + loop: false, itemWidth: MediaQuery.of(context).size.width - 48.h, indicatorLayout: PageIndicatorLayout.COLOR, - axisDirection: AxisDirection.right, + axisDirection: getIt.get().isArabic() ? AxisDirection.left : AxisDirection.right, controller: _controller, itemHeight: 255.h + 20, // extra space for shadow diff --git a/lib/presentation/home/widgets/large_service_card.dart b/lib/presentation/home/widgets/large_service_card.dart index 4405a197..b89e58c5 100644 --- a/lib/presentation/home/widgets/large_service_card.dart +++ b/lib/presentation/home/widgets/large_service_card.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -92,12 +93,12 @@ class LargeServiceCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ serviceCardData.title.tr(context: context).toText14(isBold: true, color: AppColors.textColor), - serviceCardData.subtitle.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + serviceCardData.subtitle.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight, maxLine: 2), ], ), ), ], - ).paddingSymmetrical(16.w, 0.h).expanded, + ).paddingSymmetrical(8.w, 0.h).expanded, CustomButton( text: serviceCardData.isBold ? LocaleKeys.visitPharmacyOnline.tr(context: context) : LocaleKeys.bookNow.tr(context: context), onPressed: () { @@ -177,7 +178,9 @@ class FadedLargeServiceCard extends StatelessWidget { children: [ ClipRRect( borderRadius: BorderRadius.circular(24.r), - child: Image.asset(serviceCardData.largeCardIcon, fit: BoxFit.cover, width: 520.w, height: 250.h), + child: Transform.flip( + flipX: getIt.get().isArabic(), + child: Image.asset(serviceCardData.largeCardIcon, fit: BoxFit.cover, width: 520.w, height: 250.h)), ), Positioned( top: 0, @@ -216,11 +219,14 @@ class FadedLargeServiceCard extends StatelessWidget { ), child: Padding( padding: EdgeInsets.all(8.h), - child: Utils.buildSvgWithAssets( - icon: serviceCardData.icon, - iconColor: serviceCardData.iconColor, - fit: BoxFit.contain, - applyThemeColor: false + child: Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: serviceCardData.icon, + iconColor: serviceCardData.iconColor, + fit: BoxFit.contain, + applyThemeColor: false + ), ), ), ), diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index 59a4dd97..2136f0cb 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -110,6 +110,7 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { LoaderBottomSheet.hideLoader(); insuranceViewModel.setIsInsuranceDataToBeLoaded(true); insuranceViewModel.initInsuranceProvider(); + Navigator.pop(context); }, onError: (err) { insuranceViewModel.updatePatientInsuranceCard( patientID: appState.getAuthenticatedUser()!.patientId!, @@ -129,9 +130,9 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { isFullScreen: false, isCloseButtonVisible: false, ); - Future.delayed(Duration(milliseconds: 2000)).then((value) async { - Navigator.pop(context); - }); + // Future.delayed(Duration(milliseconds: 2000)).then((value) async { + // Navigator.pop(context); + // }); }, onError: (err) { LoaderBottomSheet.hideLoader(); diff --git a/lib/presentation/lab/lab_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart index 1b5db920..ac6f0f3b 100644 --- a/lib/presentation/lab/lab_order_by_test.dart +++ b/lib/presentation/lab/lab_order_by_test.dart @@ -59,7 +59,7 @@ class LabOrderByTest extends StatelessWidget { icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, - text: LocaleKeys.viewReport.tr(context: context), + text: LocaleKeys.viewResults.tr(context: context), onPressed: () { onTap(); }, diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index b804a751..1c839c10 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -283,9 +283,9 @@ class _MedicalFilePageState extends State { ), AppCustomChipWidget( icon: AppAssets.blood_icon, - labelText: LocaleKeys.bloodGroup.tr(namedArgs: {'bloodType': appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup}, context: context), + labelText: appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup, iconColor: AppColors.primaryRedColor, - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), padding: EdgeInsets.zero, ), Consumer(builder: (context, insuranceVM, child) { @@ -749,7 +749,26 @@ class _MedicalFilePageState extends State { ], ).paddingSymmetrical(0.w, 0.h), SizedBox(height: 24.h), - LocaleKeys.activeMedicationsAndPrescriptions.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.activeMedicationsAndPrescriptions.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), + Row( + children: [ + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + SizedBox(width: 2.w), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ).onPress(() { + // myAppointmentsViewModel.getPatientMyDoctors(); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionsListPage(), + ), + ); + }), + ], + ), SizedBox(height: 16.h), Consumer(builder: (context, prescriptionVM, child) { return prescriptionVM.isPrescriptionsOrdersLoading @@ -830,55 +849,55 @@ class _MedicalFilePageState extends State { separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), SizedBox(height: 16.h), - Divider(color: AppColors.dividerColor), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.allPrescriptions.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: PrescriptionsListPage(), - ), - ); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 12.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - height: 40.h, - icon: AppAssets.requests, - iconColor: AppColors.primaryRedColor, - iconSize: 16.w, - ), - ), - SizedBox(width: 6.w), - Expanded( - child: CustomButton( - text: LocaleKeys.allMedications.tr(context: context), - onPressed: () { Navigator.of(context).push( - CustomPageRoute( - page: ActiveMedicationPage(), - ), - );}, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 12.f, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - height: 40.h, - icon: AppAssets.all_medications_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), - ), - ], - ), + // Divider(color: AppColors.dividerColor), + // SizedBox(height: 16.h), + // Row( + // children: [ + // Expanded( + // child: CustomButton( + // text: LocaleKeys.allPrescriptions.tr(context: context), + // onPressed: () { + // Navigator.of(context).push( + // CustomPageRoute( + // page: PrescriptionsListPage(), + // ), + // ); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 12.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // height: 40.h, + // icon: AppAssets.requests, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.w, + // ), + // ), + // SizedBox(width: 6.w), + // Expanded( + // child: CustomButton( + // text: LocaleKeys.allMedications.tr(context: context), + // onPressed: () { Navigator.of(context).push( + // CustomPageRoute( + // page: ActiveMedicationPage(), + // ), + // );}, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 12.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.h, + // height: 40.h, + // icon: AppAssets.all_medications_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ), + // ), + // ], + // ), ], ), ), @@ -911,7 +930,7 @@ class _MedicalFilePageState extends State { Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], ).onPress(() { - myAppointmentsViewModel.getPatientMyDoctors(); + // myAppointmentsViewModel.getPatientMyDoctors(); Navigator.of(context).push( CustomPageRoute( page: MyDoctorsPage(), @@ -1210,6 +1229,26 @@ class _MedicalFilePageState extends State { // Requests Tab Data return Column( children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${LocaleKeys.sick.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}".toText16(isBold: true), + Row( + children: [ + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ), + ], + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: PatientSickleavesListPage(), + ), + ); + }), + SizedBox(height: 16.h), Consumer(builder: (context, medicalFileVM, child) { return medicalFileVM.isPatientSickLeaveListLoading ? PatientSickLeaveCard( @@ -1279,20 +1318,20 @@ class _MedicalFilePageState extends State { ), ); }), - MedicalFileCard( - label: LocaleKeys.sickLeaveReport.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.sick_leave_report_icon, - isLargeText: true, - iconSize: 36.h, - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: PatientSickleavesListPage(), - ), - ); - }), + // MedicalFileCard( + // label: LocaleKeys.sickLeaveReport.tr(context: context), + // textColor: AppColors.blackColor, + // backgroundColor: AppColors.whiteColor, + // svgIcon: AppAssets.sick_leave_report_icon, + // isLargeText: true, + // iconSize: 36.h, + // ).onPress(() { + // Navigator.of(context).push( + // CustomPageRoute( + // page: PatientSickleavesListPage(), + // ), + // ); + // }), ], ).paddingSymmetrical(0.w, 0.0), SizedBox(height: 24.h), diff --git a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart index a0c54e68..18bd2e69 100644 --- a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -40,18 +40,17 @@ class PatientSickLeaveCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "${LocaleKeys.sick.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}".toText16(isBold: true).toShimmer2(isShow: isLoading), - AppCustomChipWidget( - labelText: isLoading ? "" : getStatusText(context), - backgroundColor: getStatusColor().withOpacity(0.15), - textColor: getStatusColor(), - ).toShimmer2(isShow: isLoading, width: 100.h), - ], - ), - SizedBox(height: 16.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // AppCustomChipWidget( + // labelText: isLoading ? "" : getStatusText(context), + // backgroundColor: getStatusColor().withOpacity(0.15), + // textColor: getStatusColor(), + // ).toShimmer2(isShow: isLoading, width: 100.h), + // ], + // ), + // SizedBox(height: 16.h), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 826b1741..f304fcc3 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -102,7 +102,7 @@ class _FamilyCardsState extends State { : profile.status == FamilyFileEnum.active.toInt ? AppColors.lightGreenColor : AppColors.lightGrayBGColor, - chipText: profile.statusDescription ?? "N/A", + chipText: profile.statusDescription ?? " N/A", iconAsset: null, isShowBorder: false, borderRadius: 8.h, @@ -154,7 +154,7 @@ class _FamilyCardsState extends State { crossAxisCount: 2, crossAxisSpacing: 10.w, mainAxisSpacing: 10.h, - childAspectRatio: widget.isShowDetails ? 0.56.h : 0.64.h, + childAspectRatio: widget.isShowDetails ? 0.56.h : 0.74.h, ), padding: EdgeInsets.only(bottom: 20.h), itemBuilder: (context, index) { @@ -164,7 +164,7 @@ class _FamilyCardsState extends State { final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID); return Container( padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), child: Opacity( opacity: isActive || profile.status == FamilyFileEnum.pending.toInt || !canSwitch ? 0.4 : 1.0, // Fade all content if active child: Stack( @@ -187,7 +187,7 @@ class _FamilyCardsState extends State { CustomChipWidget( chipType: ChipTypeEnum.alert, backgroundColor: AppColors.lightGrayBGColor, - chipText: "Relation:${profile.relationship ?? " N/A"}", + chipText: "Relation: ${profile.relationship ?? " N/A"}", iconAsset: AppAssets.heart, isShowBorder: false, borderRadius: 8.h, diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index e975d6f4..98002ca9 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -24,6 +24,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; +import 'package:hmg_patient_app_new/presentation/profile_settings/widgets/update_email_widget.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/app_language_change.dart'; @@ -115,7 +116,7 @@ class ProfileSettingsState extends State { // extra space for shadow itemWidth: SizeUtils.width - 30.w, indicatorLayout: PageIndicatorLayout.COLOR, - axisDirection: AxisDirection.right, + axisDirection: getIt.get().isArabic() ? AxisDirection.left : AxisDirection.right, controller: _controller, pagination: SwiperPagination( alignment: Alignment.bottomCenter, @@ -234,24 +235,24 @@ class ProfileSettingsState extends State { ], ), ), - Container( - margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), - padding: EdgeInsets.only(top: 4.h, bottom: 4.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), - child: Column( - children: [ - actionItem(AppAssets.language_change, LocaleKeys.language.tr(context: context), () { - showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.language.tr(context: context), child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); - }, trailingLabel: Utils.appState.isArabic() ? "العربية" : "English"), - 1.divider, - actionItem(AppAssets.bell, LocaleKeys.notificationsSettings.tr(context: context), () { - openAppSettings(); - }), - // 1.divider, - // actionItem(AppAssets.touch_face_id, LocaleKeys.touchIDFaceIDServices.tr(context: context), () {}, switchValue: true), - ], - ), - ), + // Container( + // margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), + // padding: EdgeInsets.only(top: 4.h, bottom: 4.h), + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + // child: Column( + // children: [ + // actionItem(AppAssets.language_change, LocaleKeys.language.tr(context: context), () { + // showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.language.tr(context: context), child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); + // }, trailingLabel: Utils.appState.isArabic() ? "العربية" : "English"), + // 1.divider, + // actionItem(AppAssets.bell, LocaleKeys.notificationsSettings.tr(context: context), () { + // openAppSettings(); + // }), + // // 1.divider, + // // actionItem(AppAssets.touch_face_id, LocaleKeys.touchIDFaceIDServices.tr(context: context), () {}, switchValue: true), + // ], + // ), + // ), LocaleKeys.personalInformation.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), @@ -259,7 +260,15 @@ class ProfileSettingsState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Column( children: [ - actionItem(AppAssets.email_transparent, LocaleKeys.updateEmailAddress.tr(context: context), () {}), + actionItem(AppAssets.email_transparent, LocaleKeys.updateEmailAddress.tr(context: context), () { + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.updateEmailAddress.tr(context: context), + child: UpdateEmailDialog(), + callBackFunc: () {}, + isFullScreen: false, + ); + }), // 1.divider, // actionItem(AppAssets.smart_phone_fill, "Phone Number".needTranslation, () {}), // 1.divider, @@ -385,10 +394,24 @@ class FamilyCardWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ "${profile.patientName}".toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: true), - AppCustomChipWidget( - icon: AppAssets.file_icon, - labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${profile.responseId}", - iconSize: 12.w, + Wrap( + direction: Axis.horizontal, + spacing: 4.w, + runSpacing: 6.w, + children: [ + AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), + icon: AppAssets.file_icon, + labelText: "${LocaleKeys.fileno.tr(context: context)}: ${profile.responseId}", + iconSize: 12.w, + ), + isActive ? AppCustomChipWidget( + icon: AppAssets.checkmark_icon, + labelText: LocaleKeys.verified.tr(context: context), + iconColor: AppColors.successColor, + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), + ) : SizedBox.shrink(), + ], ), ], ).expanded, @@ -406,13 +429,14 @@ class FamilyCardWidget extends StatelessWidget { AppCustomChipWidget( labelText: LocaleKeys.ageYearsOld.tr(namedArgs: {'age': profile.age.toString(), 'yearsOld': LocaleKeys.yearsOld.tr(context: context)}), ), - isActive && appState.getAuthenticatedUser()!.bloodGroup != null - ? AppCustomChipWidget( - icon: AppAssets.blood_icon, + // isActive && appState.getAuthenticatedUser()!.bloodGroup != null + // ? + isActive ? AppCustomChipWidget( + icon: AppAssets.blood_icon, labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 8.w), - labelText: "Blood: ${appState.getAuthenticatedUser()!.bloodGroup ?? ""}", - iconColor: AppColors.primaryRedColor) - : SizedBox(), + labelText: appState.getAuthenticatedUser()!.bloodGroup ?? "N/A", + iconColor: AppColors.primaryRedColor) + : SizedBox(), Consumer(builder: (context, insuranceVM, child) { return isActive ? AppCustomChipWidget( diff --git a/lib/presentation/profile_settings/widgets/update_email_widget.dart b/lib/presentation/profile_settings/widgets/update_email_widget.dart new file mode 100644 index 00000000..ad9b9d5a --- /dev/null +++ b/lib/presentation/profile_settings/widgets/update_email_widget.dart @@ -0,0 +1,106 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:provider/provider.dart'; + +class UpdateEmailDialog extends StatefulWidget { + UpdateEmailDialog({super.key}); + + @override + State createState() => _UpdateEmailDialogState(); +} + +class _UpdateEmailDialogState extends State { + late FocusNode _textFieldFocusNode; + late TextEditingController? textController; + ProfileSettingsViewModel? profileSettingsViewModel; + + @override + void initState() { + _textFieldFocusNode = FocusNode(); + textController = TextEditingController(); + textController!.text = getIt.get().getAuthenticatedUser()!.emailAddress ?? ""; + super.initState(); + } + + @override + void dispose() { + _textFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + profileSettingsViewModel = Provider.of(context); + return GestureDetector( + onTap: () { + _textFieldFocusNode.unfocus(); + FocusScope.of(context).unfocus(); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Enter the new email address to be updated in your HMG File: ".toText16(textAlign: TextAlign.start, weight: FontWeight.w500), + SizedBox(height: 12.h), + TextInputWidget( + labelText: LocaleKeys.email.tr(), + hintText: "demo@gmail.com", + controller: textController, + focusNode: _textFieldFocusNode, + autoFocus: true, + padding: EdgeInsets.all(8.h), + keyboardType: TextInputType.emailAddress, + isEnable: true, + isReadOnly: false, + prefix: null, + isBorderAllowed: false, + isAllowLeadingIcon: true, + fontSize: 14.f, + isCountryDropDown: false, + leadingIcon: AppAssets.email, + fontFamily: "Poppins", + ), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.submit.tr(context: context), + onPressed: () { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.updatingEmailAddress.tr(context: context)); + profileSettingsViewModel!.updatePatientInfo( + patientInfo: {"EmailAddress": textController!.text}, + onSuccess: (response) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), + callBackFunc: () async { + Navigator.of(context).pop(); + }, isFullScreen: false); + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + // Show error message + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(error)), + ); + }, + ); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: const Color(0xFFffffff), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index f1e8413d..b245c014 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -100,7 +100,7 @@ class _UserInfoSelectionPageState extends State { children: [ title.toText14(weight: FontWeight.w500), subTitle - .toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + .toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500, isEnglishOnly: true) .toShimmer2(isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading), ], ), diff --git a/lib/presentation/symptoms_checker/widgets/condition_card.dart b/lib/presentation/symptoms_checker/widgets/condition_card.dart index e48f41ea..d753b780 100644 --- a/lib/presentation/symptoms_checker/widgets/condition_card.dart +++ b/lib/presentation/symptoms_checker/widgets/condition_card.dart @@ -168,7 +168,7 @@ class ConditionCard extends StatelessWidget { crossAxisAlignment: WrapCrossAlignment.center, children: [ for (int i = 0; i < symptoms.length; i++) ...[ - "● ${symptoms[i]}".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "● ${symptoms[i]}".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, isEnglishOnly: true), if (i != symptoms.length - 1) Padding( padding: EdgeInsets.symmetric(horizontal: 2.w), @@ -228,7 +228,7 @@ class ConditionCard extends StatelessWidget { backgroundColor: AppColors.scaffoldBgColor, titleWidget: Row( children: [ - "$percentage%".toText12(fontWeight: FontWeight.bold, color: getChipColorBySeverityEnum(severityEnum)), + "$percentage%".toText12(fontWeight: FontWeight.bold, color: getChipColorBySeverityEnum(severityEnum), isEnglishOnly: true), ], ).paddingSymmetrical(0, 4.h), ), diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 4a315c6e..b933e530 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -259,7 +259,7 @@ class _ScrollAnimatedTitleState extends State { super.dispose(); } - double t = 0; + double t = 1.0; void _onScroll() { final double offset = widget.controller.offset; diff --git a/pubspec.yaml b/pubspec.yaml index bca92d54..4484fa77 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,8 +2,8 @@ name: hmg_patient_app_new description: "New HMG Patient App" publish_to: 'none' # Remove this line if you wish to publish to pub.dev -#version: 0.0.13+10 -version: 0.0.1+13 +version: 0.0.15+12 +#version: 0.0.1+14 environment: sdk: ">=3.6.0 <4.0.0"