diff --git a/.gitignore b/.gitignore index 3ada50c..79c113f 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,3 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release -/android/ diff --git a/assets/images/svg/cancel_circle.svg b/assets/images/svg/cancel_circle.svg new file mode 100644 index 0000000..3ceda6f --- /dev/null +++ b/assets/images/svg/cancel_circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/close_bottom_sheet_icon.svg b/assets/images/svg/close_bottom_sheet_icon.svg new file mode 100644 index 0000000..599ce85 --- /dev/null +++ b/assets/images/svg/close_bottom_sheet_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/insurance_history_icon.svg b/assets/images/svg/insurance_history_icon.svg new file mode 100644 index 0000000..4ae2a1c --- /dev/null +++ b/assets/images/svg/insurance_history_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/prescription_reminder_icon.svg b/assets/images/svg/prescription_reminder_icon.svg new file mode 100644 index 0000000..3a854c3 --- /dev/null +++ b/assets/images/svg/prescription_reminder_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/update_insurance_card.svg b/assets/images/svg/update_insurance_card.svg new file mode 100644 index 0000000..9b21aab --- /dev/null +++ b/assets/images/svg/update_insurance_card.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 2c7ac9b..ede5e64 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -782,6 +782,9 @@ "resultsPending": "النتائج معلقة", "resultsAvailable": "النتائج متاحة", "viewReport": "عرض التقرير", + "checkAvailability": "التحقق من التوفر", + "readInstructions": "قراءة التعليمات", + "searchLabReport" : "ابحث عن تقرير المختبر", "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.", "receiveOtpToast": "أين تود تلقي رمز التحقق OTP؟", "enterPhoneNumber": "أدخل رقم الهاتف", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index d01d9f6..8ccd2c4 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -783,6 +783,9 @@ "resultsPending": "Results Pending", "resultsAvailable": "Results Available", "viewReport": "View Report", + "checkAvailability": "Check Availability", + "readInstructions": "Read Instructions", + "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", diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 5fa6f5e..6eaaff6 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -731,6 +731,14 @@ class ApiConsts { static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD + static String SELECT_DEVICE_IMEI = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; + + static num VERSION_ID = 18.9; + + static final String selectDeviceImei = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; + static final String sendActivationCode = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType'; + + static setBackendURLs() { switch (appEnvironmentType) { case AppEnvironmentTypeEnum.prod: diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index bd3e541..7f1b6d1 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -67,14 +67,19 @@ class AppAssets { static const String rating_icon = '$svgBasePath/rating_icon.svg'; static const String doctor_calendar_icon = '$svgBasePath/doctor_calendar_icon.svg'; static const String prescription_remarks_icon = '$svgBasePath/prescription_remarks_icon.svg'; + static const String prescription_reminder_icon = '$svgBasePath/prescription_reminder_icon.svg'; + static const String insurance_history_icon = '$svgBasePath/insurance_history_icon.svg'; + static const String cancel_circle_icon = '$svgBasePath/cancel_circle.svg'; + static const String update_insurance_card_icon = '$svgBasePath/update_insurance_card.svg'; + static const String close_bottom_sheet_icon = '$svgBasePath/close_bottom_sheet_icon.svg'; //bottom navigation// - static const String homeBottom = '$svgBasePath/home_bottom.svg'; static const String bookAppoBottom = '$svgBasePath/book_appo_bottom.svg'; static const String myFilesBottom = '$svgBasePath/my_files_bottom.svg'; static const String toDoBottom = '$svgBasePath/todo_bottom.svg'; static const String servicesBottom = '$svgBasePath/services_bottom.svg'; + static const String closeBottomNav = '$svgBasePath/close_bottom_nav.svg'; // PNGS // static const String hmg_logo = '$pngBasePath/hmg_logo.png'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 77088af..a900742 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -7,6 +7,8 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_repo. import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; @@ -73,6 +75,7 @@ class AppDependencies { getIt.registerLazySingleton(() => LabRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => RadiologyRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => PrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => InsuranceRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -98,6 +101,13 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => InsuranceViewModel( + insuranceRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + getIt.registerLazySingleton( () => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/core/utils/calendar_utils.dart b/lib/core/utils/calendar_utils.dart index 39c7a14..8786f7f 100644 --- a/lib/core/utils/calendar_utils.dart +++ b/lib/core/utils/calendar_utils.dart @@ -4,8 +4,6 @@ import 'dart:io'; import 'dart:ui'; import 'package:device_calendar/device_calendar.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:manage_calendar_events/manage_calendar_events.dart' as ios; import 'package:timezone/data/latest.dart' as tzl; @@ -137,14 +135,12 @@ class CalendarUtils { print("catchError " + e.toString()); }).whenComplete(() { print("whenComplete Calender ID " + eventId!); - // Utils.showToast(LocaleKeys.appoReminderSuccess.tr()); }); } else { await _myPlugin.createEvent(calendarId: writableCalendars.id!, event: iosCalEvent).catchError((e) { print("catchError " + e.toString()); }).whenComplete(() { print("whenComplete Calender ID iOS " + eventId!); - // Utils.showToast(LocaleKeys.appoReminderSuccess.tr()); }); } } diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart index 0b37082..6ad1835 100644 --- a/lib/core/utils/size_utils.dart +++ b/lib/core/utils/size_utils.dart @@ -11,6 +11,10 @@ extension ResponsiveExtension on num { double get h => ((this * _width) / FIGMA_DESIGN_WIDTH); double get fSize => ((this * _width) / FIGMA_DESIGN_WIDTH); + static double get screenHeight => SizeUtils.height; + + /// Full screen width + static double get screenWidth => SizeUtils.width; } extension FormatExtension on double { diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 4d813f0..352415b 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -60,15 +60,16 @@ extension EmailValidator on String { ), ); - Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => Text( + Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? fontWeight, bool isCenter = false, double? height, int maxLine = 0}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, style: TextStyle( fontSize: 12.fSize, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal, + fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4, + height: height, decorationColor: isUnderLine ? AppColors.blackColor : null, decoration: isUnderLine ? TextDecoration.underline : null, ), @@ -206,49 +207,45 @@ extension EmailValidator on String { Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( this, maxLines: maxlines, - style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.fSize, letterSpacing: -0.4, fontWeight: weight ?? (isBold ? FontWeight.w600 : FontWeight.normal)), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.fSize, letterSpacing: -0.4, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: 1, color: color ?? AppColors.blackColor, fontSize: 22.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.w600 : FontWeight.normal), + style: TextStyle(height: 1, color: color ?? AppColors.blackColor, fontSize: 22.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText24({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.w600 : FontWeight.normal), + style: TextStyle(height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); - Widget toText26({Color? color, bool isBold = false, bool isCenter = false, double height = 23/26}) => Text( + Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: height, color: color ?? AppColors.blackColor, fontSize: 26.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.w600 : FontWeight.normal), + style: TextStyle(height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); - Widget toText28({Color? color, bool isBold = false, bool isCenter = false, TextScaler? textScaler}) => Text( - this, - textAlign: isCenter ? TextAlign.center : null, - textScaler: textScaler, - style: TextStyle(height: 40 / 28, color: color ?? AppColors.blackColor, fontSize: 28.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.w600 : FontWeight.normal), - ); - Widget toText32({Color? color, bool isBold = false, bool isCenter = false}) => Text( - this, - textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.w600 : FontWeight.normal), - ); + Widget toText28({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text( + this, + textAlign: isCenter ? TextAlign.center : null, + style: TextStyle(height: height ?? 23 / 28, color: color ?? AppColors.blackColor, fontSize: 28.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + ); - Widget toText36({Color? color, bool isBold = false, bool isCenter = false}) => Text( + + + Widget toText32({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: 47 / 36, color: color ?? AppColors.blackColor, fontSize: 36.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.w600 : FontWeight.normal), + style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText44({Color? color, bool isBold = false}) => Text( this, - style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.w600 : FontWeight.normal), + style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); 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 36fd187..82e85ee 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,12 +1,16 @@ import 'dart:developer'; +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; 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/select_device_by_imei.dart'; @@ -36,8 +40,13 @@ class AuthenticationViewModel extends ChangeNotifier { _appState = appState, _authenticationRepo = authenticationRepo; - final TextEditingController nationalIdController = TextEditingController(); - final TextEditingController phoneNumberController = TextEditingController(); + final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController(); + CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; + MaritalStatusTypeEnum? maritalStatus; + GenderTypeEnum? genderType; + bool isTermsAccepted = false; + List? countriesList; + NationalityCountries? pickedCountryByUAEUser; bool isDubai = false; bool authenticated = false; @@ -78,6 +87,52 @@ class AuthenticationViewModel extends ChangeNotifier { } } + void clearDefaults() { + nationalIdController.clear(); + phoneNumberController.clear(); + dobController.clear(); + maritalStatus = null; + genderType = null; + isTermsAccepted = false; + selectedCountrySignup = CountryEnum.saudiArabia; + pickedCountryByUAEUser = null; + } + + void onCountryChange(CountryEnum country) { + selectedCountrySignup = country; + notifyListeners(); + } + + void loadCountriesData({required BuildContext context}) async { + final String response = await DefaultAssetBundle.of(context).loadString('assets/json/countriesList.json'); + final List data = json.decode(response); + countriesList = data.map((e) => NationalityCountries.fromJson(e)).toList(); + } + + void onMaritalStatusChange(String? status) { + maritalStatus = MaritalStatusTypeExtension.fromType(status)!; + notifyListeners(); + } + + void onGenderChange(String? status) { + genderType = GenderTypeExtension.fromType(status)!; + notifyListeners(); + } + + void onUAEUserCountrySelection(String? value) { + pickedCountryByUAEUser = countriesList!.firstWhere((element) => element.name == value); + notifyListeners(); + } + + void onPhoneNumberChange(String? phoneNumber) { + phoneNumberController.text = phoneNumber!; + } + + void onTermAccepted() { + isTermsAccepted = !isTermsAccepted; + notifyListeners(); + } + Future selectDeviceImei({required Function(dynamic data) onSuccess, Function(String)? onError}) async { // LoadingUtils.showFullScreenLoading(); String firebaseToken = _appState.deviceToken == "" diff --git a/lib/features/insurance/insurance_repo.dart b/lib/features/insurance/insurance_repo.dart new file mode 100644 index 0000000..a5c624f --- /dev/null +++ b/lib/features/insurance/insurance_repo.dart @@ -0,0 +1,77 @@ +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/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class InsuranceRepo { + Future>>> getPatientInsuranceDetails({required String patientId}); +} + +class InsuranceRepoImp implements InsuranceRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + InsuranceRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getPatientInsuranceDetails({required String patientId}) async { + final mapDevice = { + "isDentalAllowedBackend": false, + "VersionID": 50.0, + "Channel": 3, + "LanguageID": 2, + "IPAdress": "10.20.10.20", + "generalid": "Cs2020@2016\$2958", + "Latitude": 0.0, + "Longitude": 0.0, + "DeviceTypeID": 1, + "PatientType": 1, + "PatientTypeID": 1, + "TokenID": "@dm!n", + "PatientID": "3628599", + "PatientOutSA": "0", + "SessionID": "03478TYC02N80874CTYN04883475!?" + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PAtIENTS_INSURANCE, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus}) { + try { + final list = response['List_PatientInsuranceCard']; + if (list == null || list.isEmpty) { + throw Exception("insurance list is empty"); + } + + final labOrders = list.map((item) => PatientInsuranceDetailsResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: labOrders, + ); + } 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())); + } + + throw UnimplementedError(); + } +} diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart new file mode 100644 index 0000000..fac09da --- /dev/null +++ b/lib/features/insurance/insurance_view_model.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class InsuranceViewModel extends ChangeNotifier { + bool isInsuranceLoading = false; + bool isInsuranceHistoryLoading = false; + + InsuranceRepo insuranceRepo; + ErrorHandlerService errorHandlerService; + + List patientInsuranceList = []; + + InsuranceViewModel({required this.insuranceRepo, required this.errorHandlerService}); + + initInsuranceProvider() { + patientInsuranceList.clear(); + isInsuranceLoading = true; + isInsuranceHistoryLoading = true; + getPatientInsuranceDetails(); + notifyListeners(); + } + + setIsInsuranceHistoryLoading(bool val) { + isInsuranceHistoryLoading = val; + notifyListeners(); + } + + Future getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await insuranceRepo.getPatientInsuranceDetails(patientId: "1231755"); + + 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) { + patientInsuranceList = apiResponse.data!; + isInsuranceLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/insurance/models/resp_models/patient_insurance_details_response_model.dart b/lib/features/insurance/models/resp_models/patient_insurance_details_response_model.dart new file mode 100644 index 0000000..b734abc --- /dev/null +++ b/lib/features/insurance/models/resp_models/patient_insurance_details_response_model.dart @@ -0,0 +1,96 @@ +class PatientInsuranceDetailsResponseModel { + String? setupID; + int? projectID; + bool? isActive; + int? patientID; + int? companyID; + int? subCategoryID; + dynamic companyType; + String? patientCardID; + String? cardValidTo; + int? patientCreditLimit; + String? subPolicyNo; + String? companyName; + String? companyNameN; + String? subCategoryDesc; + dynamic subCategoryDescN; + bool? isElectronicClaim; + String? subCategoryValidTo; + dynamic groupID; + String? groupName; + dynamic groupNameN; + String? insurancePolicyNo; + + PatientInsuranceDetailsResponseModel( + {this.setupID, + this.projectID, + this.isActive, + this.patientID, + this.companyID, + this.subCategoryID, + this.companyType, + this.patientCardID, + this.cardValidTo, + this.patientCreditLimit, + this.subPolicyNo, + this.companyName, + this.companyNameN, + this.subCategoryDesc, + this.subCategoryDescN, + this.isElectronicClaim, + this.subCategoryValidTo, + this.groupID, + this.groupName, + this.groupNameN, + this.insurancePolicyNo}); + + PatientInsuranceDetailsResponseModel.fromJson(Map json) { + setupID = json['SetupID']; + projectID = json['ProjectID']; + isActive = json['IsActive']; + patientID = json['PatientID']; + companyID = json['CompanyID']; + subCategoryID = json['SubCategoryID']; + companyType = json['CompanyType']; + patientCardID = json['PatientCardID']; + cardValidTo = json['CardValidTo']; + patientCreditLimit = json['PatientCreditLimit']; + subPolicyNo = json['SubPolicyNo']; + companyName = json['CompanyName']; + companyNameN = json['CompanyNameN']; + subCategoryDesc = json['SubCategoryDesc']; + subCategoryDescN = json['SubCategoryDescN']; + isElectronicClaim = json['IsElectronicClaim']; + subCategoryValidTo = json['SubCategoryValidTo']; + groupID = json['GroupID']; + groupName = json['GroupName']; + groupNameN = json['GroupNameN']; + insurancePolicyNo = json['InsurancePolicyNo']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['IsActive'] = this.isActive; + data['PatientID'] = this.patientID; + data['CompanyID'] = this.companyID; + data['SubCategoryID'] = this.subCategoryID; + data['CompanyType'] = this.companyType; + data['PatientCardID'] = this.patientCardID; + data['CardValidTo'] = this.cardValidTo; + data['PatientCreditLimit'] = this.patientCreditLimit; + data['SubPolicyNo'] = this.subPolicyNo; + data['CompanyName'] = this.companyName; + data['CompanyNameN'] = this.companyNameN; + data['SubCategoryDesc'] = this.subCategoryDesc; + data['SubCategoryDescN'] = this.subCategoryDescN; + data['IsElectronicClaim'] = this.isElectronicClaim; + data['SubCategoryValidTo'] = this.subCategoryValidTo; + data['GroupID'] = this.groupID; + data['GroupName'] = this.groupName; + data['GroupNameN'] = this.groupNameN; + data['InsurancePolicyNo'] = this.insurancePolicyNo; + return data; + } +} diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index acd87eb..94ddd87 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -12,6 +12,10 @@ class LabViewModel extends ChangeNotifier { List patientLabOrders = []; + late List _labSuggestionsList = []; + + List get labSuggestions => _labSuggestionsList; + LabViewModel({required this.labRepo, required this.errorHandlerService}); initLabProvider() { @@ -34,6 +38,7 @@ class LabViewModel extends ChangeNotifier { patientLabOrders = apiResponse.data!; isLabOrdersLoading = false; isLabResultsLoading = false; + filterSuggestions(); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -42,4 +47,15 @@ class LabViewModel extends ChangeNotifier { }, ); } + + filterSuggestions(){ + final List labels = patientLabOrders + .expand((order) => order.testDetails!) // flatten testDetails + .map((detail) => detail.description) // pick description + .whereType() // remove nulls if any + .toList(); + _labSuggestionsList = labels.toSet().toList(); // remove duplicates by converting to a set and back to a list + + notifyListeners(); + } } diff --git a/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart b/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart index 3ca8396..b35cfef 100644 --- a/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart +++ b/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart @@ -32,6 +32,7 @@ class PrescriptionDetailResponseModel { String? sKU; num? scaleOffset; String? startDate; + bool? hasReminder; PrescriptionDetailResponseModel( {this.address, @@ -66,7 +67,8 @@ class PrescriptionDetailResponseModel { this.route, this.sKU, this.scaleOffset, - this.startDate}); + this.startDate, + this.hasReminder = false}); PrescriptionDetailResponseModel.fromJson(Map json) { address = json['Address']; @@ -102,6 +104,7 @@ class PrescriptionDetailResponseModel { sKU = json['SKU']; scaleOffset = json['ScaleOffset']; startDate = json['StartDate']; + hasReminder = false; } Map toJson() { diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index 23a818e..ff93f84 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -43,6 +43,14 @@ class PrescriptionsViewModel extends ChangeNotifier { notifyListeners(); } + setPrescriptionItemReminder(bool value, PrescriptionDetailResponseModel item) { + int index = prescriptionDetailsList.indexOf(item); + if (index != -1) { + prescriptionDetailsList[index].hasReminder = value; + notifyListeners(); + } + } + setIsSortByClinic(bool value) { isSortByClinic = value; if (isSortByClinic) { diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 0210e4c..7831616 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -802,5 +802,8 @@ abstract class LocaleKeys { static const loginByOTP = 'loginByOTP'; static const guest = 'guest'; static const switchAccount = 'switchAccount'; + static const checkAvailability = 'checkAvailability'; + static const readInstructions = 'readInstructions'; + static const searchLabReport = 'searchLabReport'; } diff --git a/lib/main.dart b/lib/main.dart index 29c8943..53825c8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ 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/utils.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; @@ -84,6 +85,12 @@ void main() async { errorHandlerService: getIt(), ), ), + ChangeNotifierProvider( + create: (_) => InsuranceViewModel( + insuranceRepo: getIt(), + errorHandlerService: getIt(), + ), + ), ChangeNotifierProvider( create: (_) => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index c3948a1..20a6507 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -160,12 +160,12 @@ class _LoginScreen extends State { padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: SingleChildScrollView( child: GenericBottomSheet( - countryCode: "966", + countryCode: authVM.selectedCountrySignup.countryCode, initialPhoneNumber: "", textController: phoneNumberController, isEnableCountryDropdown: true, - onCountryChange: (value) {}, - onChange: (String? value) {}, + onCountryChange: authVM.onCountryChange, + onChange: authVM.onPhoneNumberChange, buttons: [ Padding( padding: EdgeInsets.only(bottom: 10.h), diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index a97c7ad..448b7af 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -9,6 +9,7 @@ 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/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/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; @@ -16,6 +17,8 @@ import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dar import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart' show CustomButton; import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/otp/otp.dart'; +import 'package:provider/provider.dart'; class RegisterNew extends StatefulWidget { @override @@ -23,8 +26,6 @@ class RegisterNew extends StatefulWidget { } class _RegisterNew extends State { - bool isTermsAccepted = true; - @override void initState() { super.initState(); @@ -38,6 +39,8 @@ class _RegisterNew extends State { @override Widget build(BuildContext context) { AppState appState = getIt.get(); + AuthenticationViewModel authVm = context.read(); + return Scaffold( backgroundColor: AppColors.bgScaffoldColor, appBar: CustomAppBar( @@ -79,30 +82,31 @@ class _RegisterNew extends State { children: [ CustomCountryDropdown( countryList: CountryEnum.values, - onCountryChange: (CountryEnum? value) {}, + onCountryChange: authVm.onCountryChange, isRtl: Directionality.of(context) == TextDirection.LTR, ).withVerticalPadding(8.h), Divider(height: 1.h), TextInputWidget( - labelText: LocaleKeys.nationalIdNumber.tr(), - hintText: "xxxxxxxxx", - controller: TextEditingController(), - isEnable: true, - prefix: null, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - autoFocus: true, - padding: EdgeInsets.symmetric(vertical: 8.h), - leadingIcon: AppAssets.student_card, - onChange: (value) { - print(value); - }).withVerticalPadding(8), + labelText: LocaleKeys.nationalIdNumber.tr(), + hintText: "xxxxxxxxx", + controller: authVm.nationalIdController, + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + padding: EdgeInsets.symmetric(vertical: 8.h), + leadingIcon: AppAssets.student_card, + // onChange: (value) { + // print(value); + // } + ).withVerticalPadding(8), Divider(height: 1), TextInputWidget( labelText: LocaleKeys.dob.tr(), hintText: "11 July, 1994", - controller: TextEditingController(), + controller: authVm.dobController, isEnable: true, prefix: null, isAllowRadius: true, @@ -110,7 +114,7 @@ class _RegisterNew extends State { isAllowLeadingIcon: true, padding: EdgeInsets.symmetric(vertical: 8.h), leadingIcon: AppAssets.birthday_cake, - onChange: (value) {}, + selectionType: SelectionTypeEnum.calendar, ).withVerticalPadding(8), ], ), @@ -118,22 +122,25 @@ class _RegisterNew extends State { ), SizedBox(height: 25.h), GestureDetector( - onTap: () {}, + onTap: authVm.onTermAccepted, child: Row( children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 200), - height: 24.h, - width: 24.h, - decoration: BoxDecoration( - color: isTermsAccepted ? const Color(0xFFE92227) : Colors.transparent, - borderRadius: BorderRadius.circular(6), - border: Border.all( - color: isTermsAccepted ? const Color(0xFFE92227) : Colors.grey, - width: 2.h, - ), - ), - child: isTermsAccepted ? Icon(Icons.check, size: 16.fSize, color: Colors.white) : null, + Selector( + selector: (_, viewModel) => viewModel.isTermsAccepted, + shouldRebuild: (previous, next) => previous != next, + builder: (context, isTermsAccepted, child) { + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + height: 24.h, + width: 24.h, + decoration: BoxDecoration( + color: isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: isTermsAccepted ? AppColors.primaryRedBorderColor : AppColors.greyColor, width: 2.h), + ), + child: isTermsAccepted ? Icon(Icons.check, size: 16.fSize, color: Colors.white) : null, + ); + }, ), SizedBox(width: 12.h), Expanded( @@ -150,7 +157,7 @@ class _RegisterNew extends State { text: "Register", icon: AppAssets.note_edit, onPressed: () { - showRegisterModel(context: context); + showRegisterModel(context: context, authVM: authVm); }, ), SizedBox(height: 14), @@ -193,7 +200,7 @@ class _RegisterNew extends State { )); } - void showRegisterModel({required BuildContext context, TextEditingController? textController}) { + void showRegisterModel({required BuildContext context, required AuthenticationViewModel authVM}) { showModalBottomSheet( context: context, isScrollControlled: true, @@ -204,18 +211,22 @@ class _RegisterNew extends State { padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), child: SingleChildScrollView( child: GenericBottomSheet( - countryCode: "966", + countryCode: authVM.selectedCountrySignup.countryCode, initialPhoneNumber: "", + textController: authVM.phoneNumberController, isEnableCountryDropdown: true, - textController: TextEditingController(), - onChange: (String? value) {}, + onCountryChange: authVM.onCountryChange, + onChange: authVM.onPhoneNumberChange, buttons: [ Padding( padding: const EdgeInsets.only(bottom: 10), child: CustomButton( text: LocaleKeys.sendOTPSMS.tr(), onPressed: () { - // TODO: MOVE TO OTP SCREEN + Navigator.of(context).push(MaterialPageRoute( + builder: (BuildContext context) => OTPVerificationPage( + phoneNumber: '12234567', + ))); // if (mobileNo.isEmpty) { // context.showBottomSheet( diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index 295dd8a..483ce5c 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.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/common_models/nationality_country_model.dart'; @@ -11,6 +12,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/context_extensions.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/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; @@ -18,6 +20,7 @@ import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dar import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; class RegisterNewStep2 extends StatefulWidget { var nHICData; @@ -31,30 +34,34 @@ class RegisterNewStep2 extends StatefulWidget { class _RegisterNew extends State { bool isFromDubai = true; - List countriesList = []; - AppState? appState; - GenderTypeEnum? selectedGenderType; - MaritalStatusTypeEnum? selectedMaritalStatusType; - CountryEnum? selectedCountry; + AuthenticationViewModel? authVM; @override void initState() { super.initState(); + authVM = context.read(); + authVM!.loadCountriesData(context: context); // isFromDubai = widget.payload.zipCode!.contains("971") || widget.payload.zipCode!.contains("+971"); - loadCountriesList(); } - loadCountriesList() async { - appState = getIt.get(); - final String response = await DefaultAssetBundle.of(context).loadString('assets/json/countriesList.json'); - final List data = json.decode(response); - countriesList = data.map((e) => NationalityCountries.fromJson(e)).toList(); + @override + void dispose() { + super.dispose(); + authVM!.clearDefaults(); } + @override Widget build(BuildContext context) { + AppState appState = getIt.get(); return Scaffold( - - appBar: CustomAppBar(onBackPressed: () {}, onLanguageChanged: (lang) {}, hideLogoAndLang: true,), + appBar: CustomAppBar( + onBackPressed: () { + Navigator.of(context).pop(); + authVM!.clearDefaults(); + }, + onLanguageChanged: (lang) {}, + hideLogoAndLang: true, + ), body: SingleChildScrollView( reverse: false, padding: EdgeInsets.only(left: 24.h, right: 24.h, top: 24.h), @@ -69,147 +76,180 @@ class _RegisterNew extends State { child: Column( children: [ TextInputWidget( - labelText: isFromDubai ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), - hintText: isFromDubai ? "name" ?? "" : (widget.nHICData!.firstNameEn!.toUpperCase() + " " + widget.nHICData!.lastNameEn!.toUpperCase()), - controller: null, - isEnable: true, - prefix: null, - isAllowRadius: false, - isBorderAllowed: false, - keyboardType: TextInputType.text, - isAllowLeadingIcon: true, - isReadOnly: isFromDubai ? false : true, - leadingIcon: AppAssets.user_circle).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), - TextInputWidget( - labelText: LocaleKeys.nationalIdNumber.tr(), - hintText: isFromDubai ? "widget.payload.nationalID!" : (widget.nHICData!.idNumber ?? ""), - controller: null, - isEnable: true, - prefix: null, - isAllowRadius: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - isReadOnly: true, - leadingIcon: AppAssets.student_card).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), - isFromDubai - ? DropdownWidget( - labelText: LocaleKeys.gender.tr(), - hintText: LocaleKeys.malE.tr(), - isEnable: true, - dropdownItems: GenderTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), - selectedValue: appState!.isArabic() ? selectedGenderType!.typeAr : selectedGenderType?.type, - // selectionType: SelectionType.dropdown, - onChange: (val) { - if (val != null) {} - }, - isBorderAllowed: false, - hasSelectionCustomIcon: true, - isAllowRadius: false, - padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), - selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.user_full, - ).withVerticalPadding(8) - : TextInputWidget( - labelText: LocaleKeys.gender.tr(), - hintText: (widget.nHICData!.gender ?? ""), + labelText: isFromDubai ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), + hintText: isFromDubai ? "name" ?? "" : (widget.nHICData!.firstNameEn!.toUpperCase() + " " + widget.nHICData!.lastNameEn!.toUpperCase()), controller: null, isEnable: true, prefix: null, isAllowRadius: false, isBorderAllowed: false, + keyboardType: TextInputType.text, isAllowLeadingIcon: true, isReadOnly: isFromDubai ? false : true, - leadingIcon: AppAssets.user_full, - - onChange: (value) {}).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), - isFromDubai - ? DropdownWidget( - labelText: LocaleKeys.maritalStatus.tr(), - hintText: LocaleKeys.married.tr(), - isEnable: true, - dropdownItems: MaritalStatusTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), - selectedValue: appState!.isArabic() ? selectedMaritalStatusType!.typeAr : selectedMaritalStatusType?.type, - onChange: (val) {}, - isBorderAllowed: false, - hasSelectionCustomIcon: true, - isAllowRadius: false, - padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), - selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.smart_phone, - ).withVerticalPadding(8) - : TextInputWidget( - labelText: LocaleKeys.maritalStatus.tr(), - hintText: appState!.isArabic() - ? (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.typeAr) - : (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.type), + leadingIcon: AppAssets.user_circle) + .paddingSymmetrical(0.h, 16.h), + Divider(height: 1, color: AppColors.greyColor), + TextInputWidget( + labelText: LocaleKeys.nationalIdNumber.tr(), + hintText: isFromDubai ? "widget.payload.nationalID!" : (widget.nHICData!.idNumber ?? ""), + controller: null, isEnable: true, prefix: null, isAllowRadius: false, isBorderAllowed: false, isAllowLeadingIcon: true, isReadOnly: true, - leadingIcon: AppAssets.smart_phone, - onChange: (value) {}).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), + leadingIcon: AppAssets.student_card) + .paddingSymmetrical(0.h, 16.h), + Divider(height: 1, color: AppColors.greyColor), isFromDubai - ? DropdownWidget( - labelText: LocaleKeys.country.tr(), - hintText: LocaleKeys.uae.tr(), - isEnable: true, - dropdownItems: countriesList.map((e) => appState!.isArabic() ? e.nameN ?? "" : e.name ?? "").toList(), - selectedValue: appState!.isArabic() ? selectedCountry!.nameArabic ?? "" : selectedCountry?.name ?? "", - onChange: (val) {}, - isBorderAllowed: false, - hasSelectionCustomIcon: true, - isAllowRadius: false, - padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), - selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.globe, - ).withVerticalPadding(8) + ? Selector( + selector: (_, authViewModel) => authViewModel.genderType, + shouldRebuild: (previous, next) => previous != next, + builder: (context, genderType, child) { + final authVM = context.read(); + return DropdownWidget( + labelText: LocaleKeys.gender.tr(), + hintText: LocaleKeys.malE.tr(), + isEnable: true, + dropdownItems: GenderTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), + selectedValue: genderType != null ? (appState!.isArabic() ? genderType!.typeAr : genderType!.type) : "", + onChange: authVM.onGenderChange, + isBorderAllowed: false, + hasSelectionCustomIcon: true, + isAllowRadius: false, + padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), + selectionCustomIcon: AppAssets.arrow_down, + leadingIcon: AppAssets.user_full, + ).withVerticalPadding(8); + }) : TextInputWidget( - labelText: LocaleKeys.nationality.tr(), - hintText: appState!.isArabic() - ? (countriesList.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") - : (countriesList.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), + labelText: LocaleKeys.gender.tr(), + hintText: (widget.nHICData!.gender ?? ""), + controller: null, + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: isFromDubai ? false : true, + leadingIcon: AppAssets.user_full, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider(height: 1, color: AppColors.greyColor), + isFromDubai + ? Selector( + selector: (_, authViewModel) => authViewModel.maritalStatus, + shouldRebuild: (previous, next) => previous != next, + builder: (context, maritalStatus, child) { + final authVM = context.read(); // For onChange + return DropdownWidget( + labelText: LocaleKeys.maritalStatus.tr(), + hintText: LocaleKeys.married.tr(), + isEnable: true, + dropdownItems: MaritalStatusTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), + selectedValue: maritalStatus != null ? (appState!.isArabic() ? maritalStatus.typeAr : maritalStatus.type) : "", + onChange: authVM.onMaritalStatusChange, + isBorderAllowed: false, + hasSelectionCustomIcon: true, + isAllowRadius: false, + padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), + selectionCustomIcon: AppAssets.arrow_down, + leadingIcon: AppAssets.smart_phone, + ).withVerticalPadding(8); + }, + ) + : TextInputWidget( + labelText: LocaleKeys.maritalStatus.tr(), + hintText: appState!.isArabic() + ? (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.typeAr) + : (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.type), + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.smart_phone, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider(height: 1, color: AppColors.greyColor), + isFromDubai + ? Selector? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( + selector: (context, authViewModel) { + final appState = getIt.get(); + return (countriesList: authViewModel.countriesList, selectedCountry: authViewModel.pickedCountryByUAEUser, isArabic: appState.isArabic()); + }, + shouldRebuild: (previous, next) => previous.countriesList != next.countriesList || previous.selectedCountry != next.selectedCountry || previous.isArabic != next.isArabic, + builder: (context, data, child) { + final authVM = context.read(); + return DropdownWidget( + labelText: LocaleKeys.country.tr(), + hintText: LocaleKeys.uae.tr(), + isEnable: true, + dropdownItems: (data.countriesList ?? []).map((e) => data.isArabic ? e.nameN ?? "" : e.name ?? "").toList(), + selectedValue: data.selectedCountry != null + ? data.isArabic + ? data.selectedCountry!.nameN ?? "" + : data.selectedCountry!.name ?? "" + : "", + onChange: authVM.onUAEUserCountrySelection, + isBorderAllowed: false, + hasSelectionCustomIcon: true, + isAllowRadius: false, + padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), + selectionCustomIcon: AppAssets.arrow_down, + leadingIcon: AppAssets.globe, + ).withVerticalPadding(8); + }, + ) + : TextInputWidget( + labelText: LocaleKeys.nationality.tr(), + hintText: appState.isArabic() + ? (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") + : (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.globe, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider( + height: 1, + color: AppColors.greyColor, + ), + TextInputWidget( + labelText: LocaleKeys.mobileNumber.tr(), + hintText: ("widget.payload.mobileNo" ?? ""), + controller: authVM!.phoneNumberController, isEnable: true, prefix: null, isAllowRadius: false, isBorderAllowed: false, isAllowLeadingIcon: true, isReadOnly: true, - leadingIcon: AppAssets.globe, - onChange: (value) {}).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), - TextInputWidget( - labelText: LocaleKeys.mobileNumber.tr(), - hintText: ("widget.payload.mobileNo" ?? ""), - controller: null, - isEnable: true, - prefix: null, - isAllowRadius: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - isReadOnly: true, - leadingIcon: AppAssets.call, - onChange: (value) {}).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), + leadingIcon: AppAssets.call, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider( + height: 1, + color: AppColors.greyColor, + ), TextInputWidget( - labelText: LocaleKeys.dob.tr(), - hintText: isFromDubai ? "widget.payload.dob!" : (widget.nHICData!.dateOfBirth ?? ""), - controller: null, - isEnable: true, - prefix: null, - isBorderAllowed: false, - isAllowLeadingIcon: true, - isReadOnly: true, - // : SelectionType.calendar, - // selectedValue: widget.payload.dob != null ? Utils.formatDateToDisplay(widget.payload.dob.toString()) : null, - // selectionCustomIcon: AppAssets.calendar, - leadingIcon: AppAssets.birthday_cake, - onChange: (value) {}).paddingSymmetrical(0.h,16.h), + labelText: LocaleKeys.dob.tr(), + hintText: isFromDubai ? "widget.payload.dob!" : (widget.nHICData!.dateOfBirth ?? ""), + controller: authVM!.dobController, + isEnable: true, + prefix: null, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.birthday_cake, + selectionType: SelectionTypeEnum.calendar, + ).paddingSymmetrical(0.h, 16.h), ], ), ), @@ -219,12 +259,12 @@ class _RegisterNew extends State { children: [ Expanded( child: CustomButton( - text: LocaleKeys.cancel, + text: LocaleKeys.cancel.tr(), icon: AppAssets.cancel, onPressed: () { Navigator.of(context).pop(); + authVM!.clearDefaults(); }, - // fontFamily: context.fontFamily, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index febde89..cf430e5 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -30,6 +30,7 @@ class LandingPage extends StatefulWidget { } class _LandingPageState extends State { + @override Widget build(BuildContext context) { AppState appState = getIt.get(); diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart new file mode 100644 index 0000000..152bbd2 --- /dev/null +++ b/lib/presentation/home/navigation_screen.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart'; + +class LandingNavigation extends StatefulWidget { + const LandingNavigation({super.key}); + + @override + State createState() => _LandingNavigationState(); +} + +class _LandingNavigationState extends State { + int _currentIndex = 0; + final PageController _pageController = PageController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: PageView( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + children: [ + const LandingPage(), + MedicalFilePage(), + const LandingPage(), + const LandingPage(), + const LandingPage(), + ], + ), + bottomNavigationBar: BottomNavigation( + currentIndex: _currentIndex, + onTap: (index) { + setState(() => _currentIndex = index); + _pageController.animateToPage(index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); + }, + ), + ); + } +} diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 297ed2b..f988e18 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.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/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; @@ -78,6 +79,11 @@ class SmallServiceCard extends StatelessWidget { ); break; case "insurance_update": + Navigator.of(context).push( + FadePage( + page: InsuranceHomePage(), + ), + ); break; default: // Handle unknown service diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart new file mode 100644 index 0000000..9f9271a --- /dev/null +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -0,0 +1,90 @@ +import 'dart:async'; + +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/utils/date_util.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/features/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; +import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.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/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; + +import 'widgets/insurance_history.dart'; + +class InsuranceHomePage extends StatefulWidget { + const InsuranceHomePage({super.key}); + + @override + State createState() => _InsuranceHomePageState(); +} + +class _InsuranceHomePageState extends State { + late InsuranceViewModel insuranceViewModel; + + @override + void initState() { + scheduleMicrotask(() { + insuranceViewModel.initInsuranceProvider(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: AppBar( + title: LocaleKeys.insurance.tr(context: context).toText18(), + backgroundColor: AppColors.bgScaffoldColor, + ), + body: SingleChildScrollView( + child: Consumer(builder: (context, insuranceVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}".toText24(isBold: true), + CustomButton( + icon: AppAssets.insurance_history_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 21.h, + text: LocaleKeys.history.tr(context: context), + onPressed: () { + insuranceVM.setIsInsuranceHistoryLoading(true); + showCommonBottomSheet(context, + child: InsuranceHistory(), callBackFunc: () {}, title: "", height: ResponsiveExtension.screenHeight * 0.5, isCloseButtonVisible: false, isFullScreen: false); + }, + backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + borderColor: AppColors.primaryRedColor.withOpacity(0.0), + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w600, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 24.h), + insuranceVM.isInsuranceLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0) + : PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), + ], + ); + }), + ), + ); + } +} diff --git a/lib/presentation/insurance/widgets/insurance_history.dart b/lib/presentation/insurance/widgets/insurance_history.dart new file mode 100644 index 0000000..0708c7c --- /dev/null +++ b/lib/presentation/insurance/widgets/insurance_history.dart @@ -0,0 +1,39 @@ +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/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/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; + +class InsuranceHistory extends StatelessWidget { + InsuranceHistory({super.key}); + + late InsuranceViewModel insuranceViewModel; + + @override + Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); + return Consumer(builder: (context, insuranceVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.history.tr(context: context).toText24(isBold: true), + Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon).onPress(() { + Navigator.of(context).pop(); + }), + ], + ).paddingSymmetrical(24.h, 24.h), + insuranceVM.isInsuranceHistoryLoading ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 24.h) : Container() + ], + ); + }); + } +} diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart new file mode 100644 index 0000000..2701233 --- /dev/null +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -0,0 +1,133 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.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/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_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:provider/provider.dart'; + +class PatientInsuranceCard extends StatelessWidget { + PatientInsuranceCard({super.key, required this.insuranceCardDetailsModel, required this.isInsuranceExpired}); + + PatientInsuranceDetailsResponseModel insuranceCardDetailsModel; + bool isInsuranceExpired = false; + + late InsuranceViewModel insuranceViewModel; + + @override + Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); + return Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Haroon Amjad".toText18(isBold: true), + "Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".toText12(isBold: true, color: AppColors.lightGrayColor), + ], + ), + CustomButton( + icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, + iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, + iconSize: 13.h, + text: isInsuranceExpired ? "Insurance Expired" : "Insurance Active", + onPressed: () {}, + backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.15) : AppColors.successColor.withOpacity(0.15), + borderColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.01) : AppColors.successColor.withOpacity(0.01), + textColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + SizedBox(height: 12.h), + insuranceCardDetailsModel.groupName!.toText12(isBold: true), + insuranceCardDetailsModel.companyName!.toText12(isBold: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, + children: [ + Row( + children: [ + CustomButton( + icon: AppAssets.doctor_calendar_icon, + iconColor: AppColors.blackColor, + iconSize: 13.h, + text: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + children: [ + CustomButton( + text: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], + ), + SizedBox(height: 10.h), + isInsuranceExpired + ? CustomButton( + icon: AppAssets.update_insurance_card_icon, + iconColor: AppColors.successColor, + iconSize: 15.h, + text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", + onPressed: () {}, + backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + borderColor: AppColors.bgGreenColor.withOpacity(0.0), + textColor: AppColors.bgGreenColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ) + : Container(), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h); + } +} diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 8a0ce26..1581ac3 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -9,10 +9,13 @@ 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/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.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/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -25,7 +28,7 @@ class LabOrdersPage extends StatefulWidget { class _LabOrdersPageState extends State { late LabViewModel labProvider; - + List?> labSuggestions = []; int? expandedIndex; @override @@ -57,7 +60,17 @@ class _LabOrdersPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.labResults.tr(context: context).toText24(isBold: true), - Utils.buildSvgWithAssets(icon: AppAssets.search_icon), + Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(() { + if(model.isLabOrdersLoading){ + return; + }else { + + showCommonBottomSheet(context, child: SearchLabResultsContent(labSuggestionsList: model.labSuggestions), + callBackFunc: () {}, + title: LocaleKeys.searchLabReport.tr(), + height: ResponsiveExtension.screenHeight, + isCloseButtonVisible: true); + } }), ], ), SizedBox(height: 16.h), @@ -262,4 +275,13 @@ class _LabOrdersPageState extends State { return ""; } } + getLabSuggestions(LabViewModel model) { + if(model.patientLabOrders.isEmpty){ + return []; + } + return model.patientLabOrders.map((m) => m.testDetails).toList(); + + + } + } diff --git a/lib/presentation/lab/search_lab_report.dart b/lib/presentation/lab/search_lab_report.dart new file mode 100644 index 0000000..d513b35 --- /dev/null +++ b/lib/presentation/lab/search_lab_report.dart @@ -0,0 +1,155 @@ +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_export.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.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/input_widget.dart'; +import 'package:sizer/sizer.dart'; + +class SearchLabResultsContent extends StatefulWidget { + final List labSuggestionsList; + + const SearchLabResultsContent({super.key, required this.labSuggestionsList}); + + @override + State createState() => _SearchLabResultsContentState(); +} + +class _SearchLabResultsContentState extends State { + TextEditingController searchEditingController = TextEditingController(); + List filteredSuggestions = []; + + @override + void initState() { + super.initState(); + filteredSuggestions = List.from(widget.labSuggestionsList); + + // Listen for changes in the search field + searchEditingController.addListener(() { + filterSuggestions(); + }); + } + + @override + void dispose() { + searchEditingController.dispose(); + super.dispose(); + } + + void filterSuggestions() { + final query = searchEditingController.text.toLowerCase(); + + if (query.isEmpty) { + setState(() { + filteredSuggestions = List.from(widget.labSuggestionsList); + }); + } else { + setState(() { + filteredSuggestions = widget.labSuggestionsList + .where((suggestion) => suggestion.toLowerCase().contains(query)) + .toList(); + }); + } + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextInputWidget( + labelText: "Search lab results", + hintText: "Type test name", + controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: true, + isBorderAllowed: false, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + SizedBox(height: ResponsiveExtension(20).h), + if (filteredSuggestions.isNotEmpty) ...[ + "Suggestions".toText16(isBold: true), + const SizedBox(height: 12), + ], + ], + ), + ), + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Wrap( + alignment: WrapAlignment.start, + spacing: 10, + runSpacing: 10, + children: filteredSuggestions + .map((label) => SuggestionChip( + label: label, + onTap: () { + searchEditingController.text = label; + }, + )) + .toList(), + ), + ), + ), + Container( + color: Colors.white, + padding: EdgeInsets.all(ResponsiveExtension(20).h), + child: CustomButton( + text: LocaleKeys.search.tr(), + icon: AppAssets.search_icon, + iconColor: Colors.white, + onPressed: () => Navigator.pop(context, searchEditingController.text), + ), + ), + ], + ); + } +} + +class SuggestionChip extends StatelessWidget { + final String label; + final bool isSelected; + final VoidCallback? onTap; + + const SuggestionChip({ + super.key, + required this.label, + this.isSelected = false, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppColors.greyColor, + width: 1, + ), + ), + child: label.toText12( + color: isSelected ? Colors.white : Colors.black87, + fontWeight: FontWeight.w500, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 9b2265c..4bb5cdb 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -1,20 +1,28 @@ 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/utils/date_util.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/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.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/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; class MedicalFilePage extends StatelessWidget { - const MedicalFilePage({super.key}); + MedicalFilePage({super.key}); + + late InsuranceViewModel insuranceViewModel; @override Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, appBar: AppBar( @@ -161,101 +169,13 @@ class MedicalFilePage extends StatelessWidget { ), SizedBox(height: 16.h), //Insurance Tab Data - Container( - // height: 150.h, - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Haroon Amjad".toText18(isBold: true), - "Policy: 223123345".toText12(isBold: true, color: AppColors.lightGrayColor), - ], - ), - CustomButton( - icon: AppAssets.cross_circle, - iconColor: AppColors.primaryRedColor, - iconSize: 13.h, - text: "Insurance Expired", - onPressed: () {}, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - SizedBox(height: 12.h), - "NCCI".toText12(isBold: true), - "NC_Dr Sulaiman Al Habib Medical Group".toText12(isBold: true), - SizedBox(height: 8.h), - Row( - children: [ - CustomButton( - icon: AppAssets.cross_circle, - iconColor: AppColors.primaryRedColor, - iconSize: 13.h, - text: "Expiry: 18 Mar, 2025", - onPressed: () {}, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - SizedBox(width: 5.h), - CustomButton( - text: "Patient Card ID: 3628599", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.normal, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - SizedBox(height: 10.h), - CustomButton( - icon: AppAssets.cross_circle, - iconColor: AppColors.primaryRedColor, - iconSize: 13.h, - text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", - onPressed: () {}, - backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), - borderColor: AppColors.bgGreenColor.withOpacity(0.0), - textColor: AppColors.bgGreenColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ), - ), - ), + Consumer(builder: (context, insuranceVM, child) { + return insuranceVM.isInsuranceLoading + ? const MoviesShimmerWidget() + : PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isBefore(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))); + }), SizedBox(height: 10.h), GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 0231c67..65dc550 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -29,6 +29,8 @@ class PrescriptionDetailPage extends StatefulWidget { class _PrescriptionDetailPageState extends State { late PrescriptionsViewModel prescriptionsViewModel; + bool _isSwitched = false; // Initial state of the switch + @override void initState() { scheduleMicrotask(() { @@ -46,267 +48,353 @@ class _PrescriptionDetailPageState extends State { title: LocaleKeys.prescriptions.tr(context: context).toText18(), backgroundColor: AppColors.bgScaffoldColor, ), - body: SingleChildScrollView( - child: Consumer(builder: (context, prescriptionVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 24.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: true, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.network( - widget.prescriptionsResponseModel.doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded(child: widget.prescriptionsResponseModel.doctorName!.toText16(isBold: true)), - ], - ), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - icon: AppAssets.doctor_calendar_icon, - iconColor: AppColors.textColor, - iconSize: 13.h, - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.prescriptionsResponseModel.clinicDescription!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - iconSize: 13.h, - text: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.prescriptionsResponseModel.name!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - ], + body: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Consumer(builder: (context, prescriptionVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 16.h), - ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length, - itemBuilder: (context, index) { - return prescriptionVM.isPrescriptionsDetailsLoading - ? const MoviesShimmerWidget() - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.network( + widget.prescriptionsResponseModel.doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded(child: widget.prescriptionsResponseModel.doctorName!.toText16(isBold: true)), + ], + ), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, + children: [ + Row( + mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: EdgeInsets.all(16.h), - child: Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Image.network( - prescriptionVM.prescriptionDetailsList[index].imageSRCUrl!, - width: 60.h, - height: 60.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded( - child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2), - ), - ], - ), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", + CustomButton( + icon: AppAssets.doctor_calendar_icon, + iconColor: AppColors.textColor, + iconSize: 13.h, + text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: widget.prescriptionsResponseModel.clinicDescription!, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + icon: AppAssets.rating_icon, + iconColor: AppColors.ratingColorYellow, + iconSize: 13.h, + text: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: widget.prescriptionsResponseModel.name!, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], + ), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length, + itemBuilder: (context, index) { + return prescriptionVM.isPrescriptionsDetailsLoading + ? const MoviesShimmerWidget() + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.network( + prescriptionVM.prescriptionDetailsList[index].imageThumbUrl!, + width: 60.h, + height: 60.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded( + child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2), + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 8.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), + SizedBox(width: 9.h), + Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 14.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + SizedBox(height: 14.h), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), + "Notify me before the consumption time".toText10(color: AppColors.textColorLight), + ], + ), + SizedBox(width: 12.h), + Switch( + activeColor: AppColors.successColor, + value: prescriptionVM.prescriptionDetailsList[index].hasReminder!, + onChanged: (newValue) { + setState(() { + prescriptionVM.setPrescriptionItemReminder(newValue, prescriptionVM.prescriptionDetailsList[index]); + }); + }, + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 14.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.checkAvailability.tr(context: context), onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, + backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + borderColor: AppColors.primaryRedColor.withOpacity(0.0), + textColor: AppColors.primaryRedColor, + fontSize: 13, fontWeight: FontWeight.w500, - borderRadius: 8, + borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, + height: 40.h, ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}", + ), + SizedBox(width: 16.h), + Expanded( + child: CustomButton( + text: LocaleKeys.readInstructions.tr(context: context), onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 13, fontWeight: FontWeight.w500, - borderRadius: 8, + borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, + height: 40.h, ), - ], - ), - ], - ), - SizedBox(height: 8.h), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), - SizedBox(width: 9.h), - Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), - ], - ) - ], - ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ], + ) + ], ), - ) - ], + ), + ), ), - ), - ), - ), - ); - }, - ).paddingSymmetrical(24.h, 0.h), - ], - ); - }), + ); + }, + ).paddingSymmetrical(24.h, 0.h), + ], + ); + }), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), + onPressed: () {}, + backgroundColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.greyF7Color, + borderColor: AppColors.successColor.withOpacity(0.01), + textColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.prescription_refill_icon, + iconColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], ), ); } diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 4aa1a1b..9574f28 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -196,7 +196,7 @@ class _PrescriptionsListPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -209,7 +209,7 @@ class _PrescriptionsListPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 292b2ee..b852cd4 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; // import 'package:hmg_patient_app_new/presentation/authantication/login.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -37,7 +38,7 @@ class _SplashScreenState extends State { LocalNotification.init(onNotificationClick: (payload) {}); Navigator.of(context).pushReplacement( FadePage( - page: LandingPage(), + page: LandingNavigation(), // page: LoginScreen(), ), ); diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 166783d..5cb9ced 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -28,6 +28,7 @@ class AppColors { static const Color bgRedLightColor = Color(0xFFFEE9EA); static const Color bgGreenColor = Color(0xFF18C273); static const Color textColor = Color(0xFF2E3039); + static const Color textColorLight = Color(0xFF5E5E5E); static const Color borderOnlyColor = Color(0xFF2E3039); static const Color dividerColor = Color(0xFFD2D2D2); static const Color warningColorYellow = Color(0xFFF4A308); diff --git a/lib/widgets/bottom_navigation/bottom_navigation.dart b/lib/widgets/bottom_navigation/bottom_navigation.dart index a052bc6..7f45464 100644 --- a/lib/widgets/bottom_navigation/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation/bottom_navigation.dart @@ -1,59 +1,103 @@ 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/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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class BottomNavigation extends StatelessWidget { - const BottomNavigation({super.key}); + final int currentIndex; + final ValueChanged onTap; + + const BottomNavigation({ + super.key, + required this.currentIndex, + required this.onTap, + }); @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: const BoxDecoration( - color: Colors.white, - border: Border( - top: BorderSide(color: AppColors.bottomNAVBorder, width: 0.5), - ), - + final items = [ + BottomNavItem(icon: AppAssets.homeBottom, label: LocaleKeys.home.tr()), + BottomNavItem(icon: AppAssets.myFilesBottom, label: LocaleKeys.myFiles.tr()), + BottomNavItem( + icon: AppAssets.bookAppoBottom, + label: LocaleKeys.appointment.tr(), + iconSize: 27, + isSpecial: true, ), + BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr()), + BottomNavItem(icon: AppAssets.servicesBottom, label: LocaleKeys.services2.tr()), + ]; + + return Container( + decoration: _containerDecoration, + padding: _containerPadding, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildNavItem(AppAssets.homeBottom, LocaleKeys.home.tr()), - _buildNavItem(AppAssets.myFilesBottom, LocaleKeys.myFiles.tr()), - _buildNavItem(AppAssets.bookAppoBottom, LocaleKeys.appointment.tr(), iconSize: 32), - _buildNavItem(AppAssets.toDoBottom, LocaleKeys.todoList.tr()), - _buildNavItem(AppAssets.servicesBottom, LocaleKeys.services2.tr()), - ], + children: List.generate( + items.length, + (index) => _buildNavItem(items[index], index), + ), ), ); } - Widget _buildNavItem(String iconName, String label,{ double iconSize = 24}) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.all(10), - child: Utils.buildSvgWithAssets( - icon: iconName, - height: iconSize, - width: iconSize - ), - ), - // const SizedBox(height: 4), - Text( - label, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Colors.black87, + Widget _buildNavItem(BottomNavItem item, int index) { + final bool isSelected = currentIndex == index; + + return GestureDetector( + onTap: () => onTap(index), + behavior: HitTestBehavior.opaque, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Center( + child: Utils.buildSvgWithAssets( + icon: item.icon, + height: item.iconSize.h, + width: item.iconSize.h, + // iconColor: isSelected ? Colors.black87 : Colors.black87, + ), + ), + const SizedBox(height: 10), + item.label.toText12( + fontWeight:FontWeight.w500, + // color: Colors.black87, + // textAlign: TextAlign.center, ), - ), - ], + SizedBox(height: item.isSpecial ? 5:0 ) + ], + + ), ); } } + +class BottomNavItem { + final String icon; + final String label; + final double iconSize; + final bool isSpecial; + + const BottomNavItem({ + required this.icon, + required this.label, + this.iconSize = 21, + this.isSpecial = false, + }); +} + +// Constants +const EdgeInsets _containerPadding = EdgeInsets.all(15); +const BoxDecoration _containerDecoration = BoxDecoration( + color: Colors.white, + border: Border( + top: BorderSide( + color: AppColors.bottomNAVBorder, + width: 0.5, + ), + ), +); diff --git a/lib/widgets/bottomsheet/generic_bottom_sheet.dart b/lib/widgets/bottomsheet/generic_bottom_sheet.dart index 6ca2014..74062ef 100644 --- a/lib/widgets/bottomsheet/generic_bottom_sheet.dart +++ b/lib/widgets/bottomsheet/generic_bottom_sheet.dart @@ -118,13 +118,16 @@ class _GenericBottomSheetState extends State { padding: EdgeInsets.all(8.h), keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number, onChange: (value) { - widget.textController!.text = value!; if (widget.onChange != null) { widget.onChange!(value); } }, + onCountryChange: (value) { + if (widget.onCountryChange != null) { + widget.onCountryChange!(value); + } + }, isEnable: true, - // focusNode: widget.myFocusNode, isReadOnly: widget.isFromSavedLogin, prefix: widget.isForEmail ? null : widget.countryCode, isBorderAllowed: false, diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart new file mode 100644 index 0000000..f6039eb --- /dev/null +++ b/lib/widgets/common_bottom_sheet.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.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/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +void showCommonBottomSheet(BuildContext context, + {required Widget child, required VoidCallback callBackFunc, String? title, required double height, bool isCloseButtonVisible = true, bool isFullScreen = true}) { + showModalBottomSheet( + sheetAnimationStyle: AnimationStyle( + duration: Duration(milliseconds: 500), // Custom animation duration + reverseDuration: Duration(milliseconds: 300), // Custom reverse animation duration + ), + context: context, + isScrollControlled: true, + showDragHandle: false, + backgroundColor: AppColors.scaffoldBgColor, + builder: (BuildContext context) { + return Container( + height: height, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.scaffoldBgColor, borderRadius: 24.h), + child: ButtonSheetContent( + title: title!, + isCloseButtonVisible: isCloseButtonVisible, + isFullScreen: isFullScreen, + child: child, + ), + ); + }).then((value) { + callBackFunc(); + }); +} + +class ButtonSheetContent extends StatelessWidget { + final Widget child; + final String title; + final bool isCloseButtonVisible; + final bool isFullScreen; + + const ButtonSheetContent({super.key, required this.child, required this.isCloseButtonVisible, required this.title, required this.isFullScreen}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // SizedBox( + // height: 20.h, + // ), + // Center( + // child: Container( + // margin: const EdgeInsets.only(top: 18, bottom: 12), + // height: 4, + // width: 40.h, + // decoration: BoxDecoration( + // color: Colors.grey[400], + // borderRadius: BorderRadius.circular(2), + // ), + // ), + // ), + + // Close button + isCloseButtonVisible && isFullScreen + ? Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Utils.buildSvgWithAssets(icon: AppAssets.closeBottomNav, width: 32, height: 32).onPress(() { + Navigator.of(context).pop(); + }), + ) + : SizedBox(), + + isFullScreen + ? Column( + children: [ + SizedBox(height: 20.h), + Padding(padding: EdgeInsets.symmetric(horizontal: 16.h), child: title.toText24(isBold: true)), + SizedBox(height: 16.h), + ], + ) + : SizedBox(), + + Expanded(child: child) + ], + ); + } +} diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index e34ba0c..0f520c6 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -1,11 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.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/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; +import '../core/dependencies.dart'; + // TODO: Import AppColors if bgRedColor is defined there // import 'package:hmg_patient_app_new/core/ui_utils/app_colors.dart'; @@ -28,11 +33,13 @@ class TextInputWidget extends StatelessWidget { final bool isCountryDropDown; final bool hasError; final String? errorMessage; + Function(CountryEnum)? onCountryChange; + SelectionTypeEnum? selectionType; // final List countryList; // final Function(Country)? onCountryChange; - const TextInputWidget({ + TextInputWidget({ Key? key, required this.labelText, required this.hintText, @@ -52,6 +59,8 @@ class TextInputWidget extends StatelessWidget { this.isCountryDropDown = false, this.hasError = false, this.errorMessage, + this.onCountryChange, + this.selectionType, // this.countryList = const [], // this.onCountryChange, }) : super(key: key); @@ -74,30 +83,28 @@ class TextInputWidget extends StatelessWidget { child: Row( textDirection: Directionality.of(context), children: [ - if (isAllowLeadingIcon && leadingIcon != null && !isCountryDropDown) - _buildLeadingIcon(context), - isCountryDropDown - ? CustomCountryDropdown( - countryList: CountryEnum.values, - onCountryChange: (CountryEnum? value) { - - }, - isRtl: Directionality.of(context) == TextDirection.rtl, - isFromBottomSheet: isCountryDropDown, - isEnableTextField: true, - onPhoneNumberChanged:(val){}, - textField: _buildTextField(context), - ) - : Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildLabelText(), - _buildTextField(context), - ], - ), + if (isAllowLeadingIcon && leadingIcon != null && !isCountryDropDown) _buildLeadingIcon(context), + isCountryDropDown + ? CustomCountryDropdown( + countryList: CountryEnum.values, + onCountryChange: onCountryChange, + isRtl: Directionality.of(context) == TextDirection.rtl, + isFromBottomSheet: isCountryDropDown, + isEnableTextField: true, + onPhoneNumberChanged: onChange, + // textField: _buildTextField(context), + ) + : Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildLabelText(), + _buildTextField(context), + ], ), + ), + if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context), ], ), ), @@ -129,6 +136,38 @@ class TextInputWidget extends StatelessWidget { child: Utils.buildSvgWithAssets(icon: leadingIcon!)); } + Widget _buildTrailingIcon(BuildContext context) { + final AppState appState = getIt.get(); + return Container( + height: 40.h, + width: 40.h, + margin: EdgeInsets.zero, + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h, color: AppColors.whiteColor), + child: GestureDetector( + onTap: () async { + bool isGregorian = true; + final picked = await showHijriGregBottomSheet(context, + switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h), + language: appState.getLanguageCode()!, + initialDate: DateTime.now(), + 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) { + isGregorian = value; + }); + if (picked != null && onChange != null) { + // if (onCalendarTypeChanged != null) { + // onCalendarTypeChanged.call(isGregorian); + // } + onChange!(picked.toIso8601String()); + } + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.calendar), + ), + ); + } + Widget _buildLabelText() { return Text( labelText, @@ -155,23 +194,11 @@ class TextInputWidget extends StatelessWidget { onChanged: onChange, focusNode: focusNode, autofocus: autoFocus, - style: TextStyle( - fontSize: 14.fSize, - height: 21 / 14, - fontWeight: FontWeight.w500, - color: AppColors.textColor, - letterSpacing: -0.2, - ), + style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -0.2), decoration: InputDecoration( isDense: true, hintText: hintText, - hintStyle: TextStyle( - fontSize: 14.fSize, - height: 21 / 16, - fontWeight: FontWeight.w500, - color: Color(0xff898A8D), - letterSpacing: -0.2, - ), + hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.2), prefixIconConstraints: BoxConstraints(minWidth: 45.h), prefixIcon: prefix == null ? null diff --git a/pubspec.lock b/pubspec.lock index a6bf33b..feed2d0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.12.0" audio_session: dependency: transitive description: @@ -1599,10 +1599,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "14.3.1" web: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index a2637f5..54ae213 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -82,7 +82,6 @@ flutter: - assets/ - assets/fonts/ - assets/langs/ - - assets/json/ - assets/images/ - assets/images/svg/ - assets/images/png/