diff --git a/assets/images/png/baby_girl_img.png b/assets/images/png/baby_girl_img.png new file mode 100644 index 0000000..55cee71 Binary files /dev/null and b/assets/images/png/baby_girl_img.png differ diff --git a/assets/images/png/baby_img.png b/assets/images/png/baby_img.png new file mode 100644 index 0000000..a4f3f86 Binary files /dev/null and b/assets/images/png/baby_img.png differ diff --git a/assets/images/png/female_img.png b/assets/images/png/female_img.png index 3307034..7d3354a 100644 Binary files a/assets/images/png/female_img.png and b/assets/images/png/female_img.png differ diff --git a/assets/images/png/male_img.png b/assets/images/png/male_img.png index 21932cd..f849800 100644 Binary files a/assets/images/png/male_img.png and b/assets/images/png/male_img.png differ diff --git a/assets/images/svg/heart.svg b/assets/images/svg/heart.svg new file mode 100644 index 0000000..6b0939a --- /dev/null +++ b/assets/images/svg/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 19111f7..32ab221 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -126,7 +126,7 @@ "gregorianDate": "التاريخ الميلادي", "verifyLoginWith": "يرجى اختيار واحدة من الخيارات التالية للتحقق", "registerUser": "تسجيل", - "verifyWithFingerprint": "بصمة الإصبع", + "verifyWithFingerprint":"البيومترية", "verifyWithFaceid": "معرف الوجه", "verifyWithSms": "رسالة قصيرة", "verifyWithWhatsapp": "واتساب", @@ -849,6 +849,7 @@ "pleaseEnterEmail": "يرجى إدخال البريد الإلكتروني", "pleaseEnterAValidEmailFormat": "يرجى إدخال تنسيق بريد إلكتروني صالح", "selectCountry": "اختر الدولة", - "forLoginVerification": "للتحقق من تسجيل الدخول" + "forLoginVerification": "للتحقق من تسجيل الدخول", + "searchHospital": "بحث في المستشفى" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 27609c8..15d74a9 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -126,7 +126,7 @@ "gregorianDate": "Gregorian Date", "verifyLoginWith": "Please choose one of the following options to verify", "registerUser": "Register", - "verifyWithFingerprint": "Fingerprint", + "verifyWithFingerprint": "Biometric", "verifyWithFaceid": "Face ID", "verifyWithSms": "SMS", "verifyWithWhatsapp": "Whatsapp", @@ -561,12 +561,12 @@ "selectTamaraPlan": "Select Tamara Payment Plan", "changeMethod": "Change Method", "reviewOrder": "Review Order", - "active": "ACTIVE", - "inactive": "INACTIVE", - "balance": "BALANCE", + "active": "Active", + "inactive": "InActive", + "balance": "Balance", "gained": "GAINED", "consumed": "Consumed", - "transferred": "TRANSFERRED", + "transferred": "Transferred", "riyal": "RIYAL", "membersince": "MEMBER SINCE", "identification": "رقم الهوية", @@ -845,5 +845,6 @@ "pleaseEnterAValidEmailFormat": "Please enter a valid email format", "selectCountry": "Select Country", "forLoginVerification": "for login verification", - "lastLoginBy": "Last login by" + "lastLoginBy": "Last login by", + "searchHospital": "Search Hospital" } \ No newline at end of file diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index f802818..5886dfa 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -720,10 +720,14 @@ const SAVE_SETTING = 'Services/Patients.svc/REST/UpdatePateintInfo'; const DEACTIVATE_ACCOUNT = 'Services/Patients.svc/REST/PatientAppleActivation_InsertUpdate'; +//family Files + +const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus'; + class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -803,6 +807,8 @@ class ApiConsts { static final String getPrivileges = 'Services/Patients.svc/REST/Service_Privilege'; static final String registerUser = 'Services/Authentication.svc/REST/PatientRegistration'; + static final String addFamilyFile = 'Services/Patients.svc/REST/ShareFamilyFileService'; + // static values for Api static final double appVersionID = 18.7; static final int appChannelId = 3; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 4acd26f..83fb842 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -143,12 +143,15 @@ class AppAssets { static const String closeBottomNav = '$svgBasePath/close_bottom_nav.svg'; static const String feedback = '$svgBasePath/feedback.svg'; static const String news = '$svgBasePath/news.svg'; + static const String heart = '$svgBasePath/heart.svg'; // PNGS // static const String hmg_logo = '$pngBasePath/hmg_logo.png'; static const String livecare_service = '$pngBasePath/livecare_service.png'; static const String male_img = '$pngBasePath/male_img.png'; static const String femaleImg = '$pngBasePath/female_img.png'; + static const String babyGirlImg = '$pngBasePath/baby_girl_img.png'; + static const String babyBoyImg = '$pngBasePath/baby_img.png'; static const String apple_pay = '$pngBasePath/Apple_Pay.png'; static const String mada = '$pngBasePath/Mada.png'; static const String Mastercard = '$pngBasePath/Mastercard.png'; diff --git a/lib/core/common_models/data_points.dart b/lib/core/common_models/data_points.dart new file mode 100644 index 0000000..af7c473 --- /dev/null +++ b/lib/core/common_models/data_points.dart @@ -0,0 +1,14 @@ + + +///class used to provide value for the [DynamicResultChart] to plot the values +class DataPoint { + ///values that is displayed on the graph and dot is plotted on this + final double value; + ///label shown on the bottom of the graph + String label; + + DataPoint( + {required this.value, + required this.label, + }); +} diff --git a/lib/core/common_models/threshold.dart b/lib/core/common_models/threshold.dart new file mode 100644 index 0000000..c8e897c --- /dev/null +++ b/lib/core/common_models/threshold.dart @@ -0,0 +1,21 @@ +import 'dart:ui' show Color; + +class ThresholdRange { + final String label; + final double value; + final Color color; + final Color lineColor; + final String? actualValue; + + ThresholdRange( + {required this.label, + required this.value, + required this.color, + required this.lineColor, + this.actualValue}); + + @override + String toString() { + return 'ThresholdRange(label: $label, value: $value, color: ${color.value.toRadixString(16)}, lineColor: ${lineColor.value.toRadixString(16)})'; + } +} \ No newline at end of file diff --git a/lib/core/enums.dart b/lib/core/enums.dart index d4eafff..bb6c6fe 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -8,22 +8,9 @@ import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; -enum AuthMethodTypesEnum { - sms, - whatsApp, - fingerPrint, - faceID, - moreOptions, -} +enum AuthMethodTypesEnum { sms, whatsApp, fingerPrint, faceID, moreOptions } -enum ViewStateEnum { - hide, - idle, - busy, - error, - busyLocal, - errorLocal, -} +enum ViewStateEnum { hide, idle, busy, error, busyLocal, errorLocal } enum CountryEnum { saudiArabia, unitedArabEmirates } @@ -35,7 +22,7 @@ enum GenderTypeEnum { male, female } enum MaritalStatusTypeEnum { single, married, divorced, widowed } -enum ChipTypeEnum { success, error, alert, info, warning } +enum ChipTypeEnum { success, error, alert, info, warning, lightBg, primaryRed } enum OTPTypeEnum { sms, whatsapp, faceIDFingerprint } diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart index bd35dcb..db8bef4 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -8,16 +8,9 @@ import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; -import 'package:huawei_location/huawei_location.dart' as HmsLocation - show - FusedLocationProviderClient, - Location, - LocationSettingsRequest, - LocationRequest; -import 'package:location/location.dart' - show Location, PermissionStatus, LocationData; -import 'package:permission_handler/permission_handler.dart' - show Permission, PermissionListActions, PermissionStatusGetters; +import 'package:huawei_location/huawei_location.dart' as HmsLocation show FusedLocationProviderClient, Location, LocationSettingsRequest, LocationRequest; +import 'package:location/location.dart' show Location, PermissionStatus, LocationData; +import 'package:permission_handler/permission_handler.dart' show Permission, PermissionListActions, PermissionStatusGetters; class LocationUtils { NavigationService navigationService; @@ -39,8 +32,7 @@ class LocationUtils { isGMSDevice = GmsCheck().checkGmsAvailability(); } - void getLocation( - {Function(LatLng)? onSuccess, VoidCallback? onFailure}) async { + void getLocation({Function(LatLng)? onSuccess, VoidCallback? onFailure}) async { if (Platform.isIOS) { getCurrentLocation(onFailure: onFailure, onSuccess: onSuccess); return; @@ -54,8 +46,7 @@ class LocationUtils { getHMSLocation(onFailure: onFailure, onSuccess: onSuccess); } - void getCurrentLocation( - {Function(LatLng)? onSuccess, VoidCallback? onFailure}) async { + void getCurrentLocation({Function(LatLng)? onSuccess, VoidCallback? onFailure}) async { var location = Location(); bool isLocationEnabled = await location.serviceEnabled(); @@ -72,8 +63,7 @@ class LocationUtils { LocationPermission permissionGranted = await Geolocator.checkPermission(); if (permissionGranted == LocationPermission.denied) { permissionGranted = await Geolocator.requestPermission(); - if (permissionGranted != LocationPermission.whileInUse && - permissionGranted != LocationPermission.always) { + if (permissionGranted != LocationPermission.whileInUse && permissionGranted != LocationPermission.always) { appState.resetLocation(); onFailure?.call(); return; @@ -82,13 +72,11 @@ class LocationUtils { Position? currentLocation = await Geolocator.getLastKnownPosition(); - if(currentLocation?.latitude == null || currentLocation?.longitude == null){ - currentLocation = await Geolocator.getCurrentPosition( - desiredAccuracy: LocationAccuracy.low); + if (currentLocation?.latitude == null || currentLocation?.longitude == null) { + currentLocation = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.low); } - LatLng locationData = LatLng( - currentLocation?.latitude ?? 0.0, currentLocation?.longitude ?? 0.0); + LatLng locationData = LatLng(currentLocation?.latitude ?? 0.0, currentLocation?.longitude ?? 0.0); saveLatLngToAppState(locationData); onSuccess?.call(locationData); } @@ -197,22 +185,17 @@ class LocationUtils { appState.userLong = locationData.longitude; } - void getHMSLocation( - {VoidCallback? onFailure, Function(LatLng p1)? onSuccess}) async { + void getHMSLocation({VoidCallback? onFailure, Function(LatLng p1)? onSuccess}) async { try { var location = Location(); - HmsLocation.FusedLocationProviderClient locationService = - HmsLocation.FusedLocationProviderClient()..initFusedLocationService(); + HmsLocation.FusedLocationProviderClient locationService = HmsLocation.FusedLocationProviderClient()..initFusedLocationService(); bool isLocationEnabled = await Geolocator.isLocationServiceEnabled(); //if the location service is not enabled, ask the user to enable it if (!isLocationEnabled) { - HmsLocation.LocationRequest locationRequest = - HmsLocation.LocationRequest() - ..priority = HmsLocation.LocationRequest.PRIORITY_HIGH_ACCURACY; + HmsLocation.LocationRequest locationRequest = HmsLocation.LocationRequest()..priority = HmsLocation.LocationRequest.PRIORITY_HIGH_ACCURACY; - HmsLocation.LocationSettingsRequest request = - HmsLocation.LocationSettingsRequest( + HmsLocation.LocationSettingsRequest request = HmsLocation.LocationSettingsRequest( alwaysShow: true, needBle: false, requests: [locationRequest], @@ -223,8 +206,7 @@ class LocationUtils { LocationPermission permissionGranted = await Geolocator.checkPermission(); if (permissionGranted == LocationPermission.denied) { permissionGranted = await Geolocator.requestPermission(); - if (permissionGranted != LocationPermission.whileInUse && - permissionGranted != LocationPermission.always) { + if (permissionGranted != LocationPermission.whileInUse && permissionGranted != LocationPermission.always) { appState.resetLocation(); onFailure?.call(); return; @@ -246,8 +228,7 @@ class LocationUtils { onFailure?.call(); return; } - var locationData = - LatLng(data.latitude ?? 0.0, data.longitude ?? 0.0); + var locationData = LatLng(data.latitude ?? 0.0, data.longitude ?? 0.0); saveLatLngToAppState(locationData); onSuccess?.call(locationData); }); diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 00bfc94..8b993f1 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -246,4 +246,20 @@ class RequestUtils { if (!isDubai) "HealthId": appState.getNHICUserData.healthId, }; } + + static dynamic getAddFamilyRequest({required String nationalIDorFile, required String mobileNo, required String countryCode, required int loginType}) { + var request = {}; + if (loginType == 1) { + request["sharedPatientID"] = 0; + request["sharedPatientIdentificationID"] = nationalIDorFile; + } else if (loginType == 2) { + request["sharedPatientID"] = int.parse(nationalIDorFile); + request["sharedPatientIdentificationID"] = ''; + } + request["searchType"] = loginType; + request["sharedPatientMobileNumber"] = mobileNo; + request["zipCode"] = countryCode; + request["isRegister"] = false; + request["patientStatus"] = 2; + } } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index e0eeb23..698075d 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -606,6 +606,27 @@ class Utils { ); } + static Widget buildImgWithAssets({ + required String icon, + Color? iconColor, + bool isDisabled = false, + double width = 24, + double height = 24, + BoxFit fit = BoxFit.cover, + }) { + return Image.asset(icon, width: width, height: height, fit: fit); + } + + /// Widget to build an SVG from network + static Widget buildImgWithNetwork({required String url, required Color iconColor, bool isDisabled = false, double width = 24, double height = 24, BoxFit fit = BoxFit.cover}) { + return Image.network( + url, + width: width, + height: height, + fit: fit, + ); + } + static Widget getPaymentMethods() { return Row( mainAxisSize: MainAxisSize.max, diff --git a/lib/core/utils/validation_utils.dart b/lib/core/utils/validation_utils.dart index e335c6b..4f02ad9 100644 --- a/lib/core/utils/validation_utils.dart +++ b/lib/core/utils/validation_utils.dart @@ -140,4 +140,34 @@ class ValidationUtils { return true; } + + static bool isValidatedIdAndPhoneWithCountryValidation({String? nationalId, String? phoneNumber, required Function() onOkPress, CountryEnum? selectedCountry}) { + bool isCorrectID = true; + if (nationalId == null || nationalId.isEmpty) { + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAnationalID.tr(), onOkPressed: onOkPress); + isCorrectID = false; + } + + if (nationalId != null && nationalId.isNotEmpty && selectedCountry != null) { + if (selectedCountry == CountryEnum.saudiArabia) { + if (!validateIqama(nationalId)) { + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidIqamaID.tr(), onOkPressed: onOkPress); + return false; + } + } + + if (selectedCountry == CountryEnum.unitedArabEmirates) { + if (!validateUaeNationalId(nationalId)) { + _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidNationalID.tr(), onOkPressed: onOkPress); + return false; + } + } + + if (phoneNumber == null || phoneNumber.isEmpty) { + _dialogService.showExceptionBottomSheet(message: LocaleKeys.enterValidPhoneNumber.tr(), onOkPressed: onOkPress); + return false; + } + } + return isCorrectID; + } } diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index 26a1442..424aa88 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -22,7 +22,7 @@ extension WidgetExtensions on Widget { Widget paddingSymmetrical(double horizontal, double vertical) => Padding(padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical), child: this); Widget paddingOnly({double left = 0.0, double right = 0.0, double top = 0.0, double bottom = 0.0}) => - Padding(padding: EdgeInsets.only(left: left, right: right, top: top, bottom: bottom), child: this); + Padding(padding: EdgeInsetsDirectional.only(start: left, end: right, top: top, bottom: bottom), child: this); Widget toExpanded({int flex = 1}) => Expanded(flex: flex, child: this); @@ -233,6 +233,10 @@ extension ChipTypeEnumExtension on ChipTypeEnum { return AppColors.infoColor; // Replace with your actual color case ChipTypeEnum.warning: return AppColors.warningColor; // Replace with your actual color + case ChipTypeEnum.lightBg: + return AppColors.chipPrimaryRedBorderColor; // Replace with your actual color + case ChipTypeEnum.primaryRed: + return AppColors.chipSecondaryLightRedColor; // Replace with your actual color } } @@ -248,6 +252,10 @@ extension ChipTypeEnumExtension on ChipTypeEnum { return AppColors.infoLightColor; // Replace with your actual color case ChipTypeEnum.warning: return AppColors.warningLightColor; // Replace with your actual color + case ChipTypeEnum.lightBg: + return AppColors.chipSecondaryLightRedColor; // Replace with your actual color + case ChipTypeEnum.primaryRed: + return AppColors.chipPrimaryRedBorderColor; } } } diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index b9750d8..6488dd8 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -414,11 +414,6 @@ class BookAppointmentsViewModel extends ChangeNotifier { } Future getRegionMappedProjectList() async { - //todo handle the case in the location is switch on - // if(hospitalList != null && hospitalList!.registeredDoctorMap != null && hospitalList!.registeredDoctorMap!.isNotEmpty){ - // filteredHospitalList = hospitalList; - // return; - // } isRegionListLoading = true; notifyListeners(); final result = await bookAppointmentsRepo.getProjectList(); @@ -432,7 +427,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { var projectList = apiResponse.data!; hospitalList = await DoctorMapper.getMappedHospitals(projectList, - isArabic: false, + isArabic: _appState.isArabic(), lat: _appState.userLat, lng: _appState.userLong, ); diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 2e481f4..29b90f8 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -17,6 +17,8 @@ class LabViewModel extends ChangeNotifier { List get labSuggestions => _labSuggestionsList; + Set uniqueTests = {}; + LabViewModel({required this.labRepo, required this.errorHandlerService}); initLabProvider() { @@ -32,8 +34,8 @@ class LabViewModel extends ChangeNotifier { final result = await labRepo.getPatientLabOrders(); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), - (apiResponse) { + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { @@ -43,6 +45,7 @@ class LabViewModel extends ChangeNotifier { isLabOrdersLoading = false; isLabResultsLoading = false; filterSuggestions(); + getUniqueTestDescription(); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -75,4 +78,15 @@ class LabViewModel extends ChangeNotifier { } notifyListeners(); } + + getUniqueTestDescription() { + + uniqueTests = { + for (var item in patientLabOrders) + if (item.testDetails != null) + ...?item.testDetails?.map((test) => + TestDetails(description: test.description.toString(), testCode: test.testCode.toString(), testID: test.testID, createdOn: item.createdOn)) + }; + uniqueTests.forEach(print); + } } diff --git a/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart index 11bf573..265a19e 100644 --- a/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart +++ b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart @@ -226,13 +226,14 @@ class TestDetails { String? description; String? testCode; String? testID; - - TestDetails({this.description, this.testCode, this.testID}); + String? createdOn; + TestDetails({this.description, this.testCode, this.testID, this.createdOn}); TestDetails.fromJson(Map json) { description = json['Description']; testCode = json['TestCode']; testID = json['TestID']; + createdOn = json['CreatedOn']; } Map toJson() { @@ -240,6 +241,7 @@ class TestDetails { data['Description'] = this.description; data['TestCode'] = this.testCode; data['TestID'] = this.testID; + data['CreatedOn'] = this.createdOn; return data; } } diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index bbb4fcd..6b83f33 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine import 'package:hmg_patient_app_new/services/logger_service.dart'; import '../authentication/models/resp_models/authenticated_user_resp_model.dart'; +import 'models/family_file_response_model.dart'; abstract class MedicalFileRepo { Future>>> getPatientVaccinesList(); @@ -22,6 +23,10 @@ abstract class MedicalFileRepo { Future>>> getPatientMedicalReportsList(); Future>> getPatientMedicalReportPDF(PatientMedicalReportResponseModel patientMedicalReportResponseModel, AuthenticatedUser authenticatedUser); + + Future>>> getPatientFamilyFiles(); + + Future>>> addFamilyFile({required dynamic request}); } class MedicalFileRepoImp implements MedicalFileRepo { @@ -267,4 +272,80 @@ class MedicalFileRepoImp implements MedicalFileRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientFamilyFiles() async { + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + FAMILY_FILES, + body: {"Status": 3}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['GetAllSharedRecordsByStatusList']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + final familyLists = list.map((item) => FamilyFileResponseModelLists.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: familyLists, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> addFamilyFile({dynamic request}) async { + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.addFamilyFile, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + print(response); + // final list = response['GetAllSharedRecordsByStatusList']; + // + // final familyLists = list.map((item) => FamilyFileResponseModelLists.fromJson(item as Map)).toList().cast(); + // + // apiResponse = GenericApiModel>( + // messageStatus: messageStatus, + // statusCode: statusCode, + // errorMessage: null, + // data: familyLists, + // ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 73167bb..2e528de 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -1,9 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class MedicalFileViewModel extends ChangeNotifier { @@ -25,10 +30,14 @@ class MedicalFileViewModel extends ChangeNotifier { List patientMedicalReportReadyList = []; List patientMedicalReportCancelledList = []; + List patientFamilyFiles = []; + String patientSickLeavePDFBase64 = ""; String patientMedicalReportPDFBase64 = ""; int selectedMedicalReportsTabIndex = 0; + static final DialogService _dialogService = getIt.get(); + AppState _appState = getIt(); MedicalFileViewModel({required this.medicalFileRepo, required this.errorHandlerService}); @@ -217,4 +226,76 @@ class MedicalFileViewModel extends ChangeNotifier { }, ); } + + Future getFamilyFiles({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await medicalFileRepo.getPatientFamilyFiles(); + + result.fold( + (failure) async => await errorHandlerService.handleError( + failure: failure, + onOkPressed: () { + onError!(failure.message); + }, + ), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientFamilyFiles = apiResponse.data!; + patientFamilyFiles.insert( + 0, + FamilyFileResponseModelLists( + patientId: _appState.getAuthenticatedUser()!.patientId, + patientName: '${_appState.getAuthenticatedUser()!.firstName!} ${_appState.getAuthenticatedUser()!.lastName!}', + isActive: true, + gender: _appState.getAuthenticatedUser()!.gender!, + responseId: _appState.getAuthenticatedUser()!.patientId), + ); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future switchFamilyFiles({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await medicalFileRepo.getPatientFamilyFiles(); + + result.fold( + (failure) async => await errorHandlerService.handleError( + failure: failure, + onOkPressed: () { + onError!(failure.message); + }, + ), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientFamilyFiles = apiResponse.data!; + patientFamilyFiles.insert( + 0, + FamilyFileResponseModelLists( + patientId: _appState.getAuthenticatedUser()!.patientId, + patientName: '${_appState.getAuthenticatedUser()!.firstName!} ${_appState.getAuthenticatedUser()!.lastName!}', + isActive: true, + gender: _appState.getAuthenticatedUser()!.gender!, + responseId: _appState.getAuthenticatedUser()!.patientId), + ); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future addFamilyFile() async { + final resultEither = await medicalFileRepo.addFamilyFile(request: {}); + resultEither.fold((failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) async {}); + } + } diff --git a/lib/features/medical_file/models/family_file_response_model.dart b/lib/features/medical_file/models/family_file_response_model.dart new file mode 100644 index 0000000..82fe3d8 --- /dev/null +++ b/lib/features/medical_file/models/family_file_response_model.dart @@ -0,0 +1,105 @@ +import 'dart:convert'; + +class FamilyFileResponseModelLists { + int? id; + int? patientId; + int? responseId; + dynamic relationshipId; + dynamic relationship; + dynamic relationshipN; + int? regionId; + int? familyRegionId; + int? status; + dynamic isActive; + String? editedOn; + String? createdOn; + int? age; + String? emaiLAddress; + int? gender; + String? genderDescription; + String? genderImage; + String? mobileNumber; + int? patientDataVerified; + String? patientIdenficationNumber; + String? patientName; + String? statusDescription; + + FamilyFileResponseModelLists({ + this.id, + this.patientId, + this.responseId, + this.relationshipId, + this.relationship, + this.relationshipN, + this.regionId, + this.familyRegionId, + this.status, + this.isActive, + this.editedOn, + this.createdOn, + this.age, + this.emaiLAddress, + this.gender, + this.genderDescription, + this.genderImage, + this.mobileNumber, + this.patientDataVerified, + this.patientIdenficationNumber, + this.patientName, + this.statusDescription, + }); + + factory FamilyFileResponseModelLists.fromRawJson(String str) => FamilyFileResponseModelLists.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory FamilyFileResponseModelLists.fromJson(Map json) => FamilyFileResponseModelLists( + id: json["ID"], + patientId: json["PatientID"], + responseId: json["ResponseID"], + relationshipId: json["RelationshipID"], + relationship: json["Relationship"], + relationshipN: json["RelationshipN"], + regionId: json["RegionID"], + familyRegionId: json["FamilyRegionID"], + status: json["Status"], + isActive: json["IsActive"], + editedOn: json["EditedOn"], + createdOn: json["CreatedOn"], + age: json["Age"], + emaiLAddress: json["EmaiLAddress"], + gender: json["Gender"], + genderDescription: json["GenderDescription"], + genderImage: json["GenderImage"], + mobileNumber: json["MobileNumber"], + patientDataVerified: json["PatientDataVerified"], + patientIdenficationNumber: json["PatientIdenficationNumber"], + patientName: json["PatientName"], + statusDescription: json["StatusDescription"], + ); + + Map toJson() => { + "ID": id, + "PatientID": patientId, + "ResponseID": responseId, + "RelationshipID": relationshipId, + "Relationship": relationship, + "RelationshipN": relationshipN, + "RegionID": regionId, + "FamilyRegionID": familyRegionId, + "Status": status, + "IsActive": isActive, + "EditedOn": editedOn, + "CreatedOn": createdOn, + "Age": age, + "EmaiLAddress": emaiLAddress, + "Gender": gender, + "GenderDescription": genderDescription, + "GenderImage": genderImage, + "MobileNumber": mobileNumber, + "PatientDataVerified": patientDataVerified, + "PatientIdenficationNumber": patientIdenficationNumber, + "PatientName": patientName, + "StatusDescription": statusDescription, + }; +} diff --git a/lib/features/my_appointments/appointment_via_region_viewmodel.dart b/lib/features/my_appointments/appointment_via_region_viewmodel.dart index 1525280..4829f17 100644 --- a/lib/features/my_appointments/appointment_via_region_viewmodel.dart +++ b/lib/features/my_appointments/appointment_via_region_viewmodel.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart' show ChangeNotifier; +import 'package:hmg_patient_app_new/core/app_state.dart' show AppState; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -20,8 +21,9 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { final NavigationService navigationService; AppointmentViaRegionState bottomSheetState = AppointmentViaRegionState.REGION_SELECTION; + final AppState appState; - AppointmentViaRegionViewmodel({required this.navigationService}); + AppointmentViaRegionViewmodel({required this.navigationService,required this.appState}); void setSelectedRegionId(String? regionId) { selectedRegionId = regionId; @@ -69,4 +71,6 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { void setHospitalModel(PatientDoctorAppointmentList? hospital) { selectedHospital = hospital; } + + bool get isArabic => appState.isArabic(); } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index a452959..3d12e5b 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -848,5 +848,6 @@ abstract class LocaleKeys { static const pleaseEnterAValidEmailFormat = 'pleaseEnterAValidEmailFormat'; static const selectCountry = 'selectCountry'; static const forLoginVerification = 'forLoginVerification'; + static const searchHospital = 'searchHospital'; } diff --git a/lib/main.dart b/lib/main.dart index 6abde55..bdd1f29 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -147,8 +147,8 @@ void main() async { ), ), ChangeNotifierProvider( - create: (_) => - AppointmentViaRegionViewmodel(navigationService: getIt())) + create: (_) => AppointmentViaRegionViewmodel( + navigationService: getIt(), appState: getIt())) ], child: MyApp()), ), ); diff --git a/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart b/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart index e1ff677..cacc9d3 100644 --- a/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart +++ b/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart @@ -38,7 +38,7 @@ class FacilityTypeSelectionWidget extends StatelessWidget { ), ), Text( - LocaleKeys.selectFacilitiesSubTitle, + LocaleKeys.selectFacilitiesSubTitle.tr(), style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, @@ -55,9 +55,11 @@ class FacilityTypeSelectionWidget extends StatelessWidget { }), ).onPress( () { - regionalViewModel.setFacility(FacilitySelection.HMG.name); - regionalViewModel.setBottomSheetState( - AppointmentViaRegionState.HOSPITAL_SELECTION); + if(bookAppointmentViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmgSize != 0) { + regionalViewModel.setFacility(FacilitySelection.HMG.name); + regionalViewModel.setBottomSheetState( + AppointmentViaRegionState.HOSPITAL_SELECTION); + } }, ), SizedBox(height: 16.h), @@ -69,9 +71,11 @@ class FacilityTypeSelectionWidget extends StatelessWidget { "${bookAppointmentViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmcSize ?? 0}" })).onPress( () { - regionalViewModel.setFacility(FacilitySelection.HMC.name); - regionalViewModel.setBottomSheetState( - AppointmentViaRegionState.HOSPITAL_SELECTION); + if(bookAppointmentViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmcSize!= 0 ) { + regionalViewModel.setFacility(FacilitySelection.HMC.name); + regionalViewModel.setBottomSheetState( + AppointmentViaRegionState.HOSPITAL_SELECTION); + } }, ), ], diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart index 8159abd..43ce9c6 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart @@ -50,7 +50,7 @@ class HospitalBottomSheetBody extends StatelessWidget { SizedBox(height: 16.h), TextInputWidget( labelText: LocaleKeys.search.tr(), - hintText: "Search Hospital".tr(), + hintText: LocaleKeys.searchHospital.tr(), controller: searchText, onChange: (value) { appointmentsViewModel.filterHospitalListByString(value, regionalViewModel.selectedRegionId , regionalViewModel.selectedFacilityType == diff --git a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart index 64263de..ab9cd9b 100644 --- a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart +++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart @@ -77,7 +77,7 @@ class RegionListItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Align( - alignment: Alignment.centerLeft, + alignment: AlignmentDirectional.centerStart, child: Text( title, style: TextStyle( diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index bb659c4..140b593 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -200,7 +200,11 @@ class _BookAppointmentPageState extends State { void openRegionListBottomSheet(BuildContext context) { regionalViewModel.flush(); // AppointmentViaRegionViewmodel? viewmodel = null; - showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, + showCommonBottomSheetWithoutHeight(context, + title: "", + titleWidget: Consumer( + builder: (_, data, __) => getTitle(data)), + isDismissible: false, child: Consumer(builder: (_, data, __) { return getRegionalSelectionWidget(data); }), callBackFunc: () {}); @@ -234,9 +238,19 @@ class _BookAppointmentPageState extends State { if (data.selectedRegionId == null) { return LocaleKeys.selectRegion.tr().toText20(weight: FontWeight.w600); } else { - return Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, iconColor: Color(0xff2B353E)).onPress(() { - data.handleBackPress(); - }); + return + Transform.flip( + flipX: data.isArabic ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, + iconColor: Color(0xff2B353E), + + fit: BoxFit.contain, + ), + ).onPress(() { + data.handleBackPress(); + }); + } } } diff --git a/lib/presentation/lab/lab_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart new file mode 100644 index 0000000..e5c6929 --- /dev/null +++ b/lib/presentation/lab/lab_order_by_test.dart @@ -0,0 +1,84 @@ +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/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.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/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; + +class LabOrderByTest extends StatelessWidget { + final VoidCallback onTap; + final int index; + final TestDetails? tests; + final bool isLoading; + final bool isExpanded; + + const LabOrderByTest({super.key, required this.onTap, this.tests, required this.index, this.isLoading = false, this.isExpanded = false}); + + @override + build(BuildContext context) { + return 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: InkWell( + onTap: () { + if (!isLoading) { + onTap(); + } + }, + child: Container( + key: ValueKey(index), + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ...labOrder!.testDetails!.map((detail) { + Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: '${tests!.description}'.toText14(weight: FontWeight.w500), + ), + + SizedBox(height: 12.h), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + richText: '${"Last Tested:".needTranslation} ${ DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false)}'.toText12(isBold: true), + // chipType: ChipTypeEnum.lightBg, + backgroundColor: AppColors.greyLightColor, + textColor: AppColors.textColor, + // borderRadius: 5, + + ), + CustomButton( + icon: AppAssets.view_report_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + text: LocaleKeys.viewReport.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.bold, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ), + ], + ), + ))); + } +} diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index a3eb5e9..6e910b8 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -11,6 +11,7 @@ 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/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -32,7 +33,7 @@ class _LabOrdersPageState extends State { List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; - + int activeIndex = 0; @override void initState() { scheduleMicrotask(() { @@ -84,7 +85,10 @@ class _LabOrdersPageState extends State { // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { - // myAppointmentsViewModel.onTabChange(index); + activeIndex = index; + setState(() { + + }); }, ), SizedBox(height: 16.h), @@ -95,39 +99,73 @@ class _LabOrdersPageState extends State { isSelected: true, ) : SizedBox(), - ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.length, - itemBuilder: (context, index) { - final isExpanded = expandedIndex == index; - return model.isLabOrdersLoading - ? LabResultItemView( + activeIndex == 0 + ? ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.length, + itemBuilder: (context, index) { + final isExpanded = expandedIndex == index; + return model.isLabOrdersLoading + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: LabResultItemView( + onTap: () { + setState(() { + expandedIndex = isExpanded ? null : index; + }); + }, + labOrder: model.patientLabOrders[index], + index: index, + isExpanded: isExpanded)), + ), + ); + }, + ) + : ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: model.isLabOrdersLoading ? 5 :model.uniqueTests.toList().length, + itemBuilder: (context, index) { + final isExpanded = expandedIndex == index; + return model.isLabOrdersLoading + ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, - ) - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: LabResultItemView( - onTap: () { - setState(() { - expandedIndex = isExpanded ? null : index; - }); - }, - labOrder: model.patientLabOrders[index], - index: index, - isExpanded: isExpanded)), - ), - ); - }, - ), + ) : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: LabOrderByTest( + onTap: () { + setState(() { + expandedIndex = isExpanded ? null : index; + }); + }, + tests: model.uniqueTests.toList()[index], + index: index, + isExpanded: isExpanded)), + ), + ); + + }, + ) ], ); }, diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 43f0537..18b6085 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -4,9 +4,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.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/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/request_utils.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'; @@ -15,6 +17,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; @@ -33,7 +36,10 @@ import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page. import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart'; +import 'package:hmg_patient_app_new/presentation/my_family/my_Family.dart'; +import 'package:hmg_patient_app_new/presentation/my_family/widget/my_family_sheet.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.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/chip/app_custom_chip_widget.dart'; @@ -72,6 +78,7 @@ class _MedicalFilePageState extends State { insuranceViewModel.initInsuranceProvider(); medicalFileViewModel.setIsPatientSickLeaveListLoading(true); medicalFileViewModel.getPatientSickLeaveList(); + medicalFileViewModel.getFamilyFiles(); medicalFileViewModel.onTabChanged(0); } }); @@ -85,6 +92,7 @@ class _MedicalFilePageState extends State { medicalFileViewModel = Provider.of(context, listen: false); bookAppointmentsViewModel = Provider.of(context, listen: false); appState = getIt.get(); + NavigationService navigationService = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( @@ -111,10 +119,7 @@ class _MedicalFilePageState extends State { SizedBox(height: 16.h), Container( width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), child: Padding( padding: EdgeInsets.all(16.h), child: Column( @@ -123,11 +128,7 @@ class _MedicalFilePageState extends State { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset( - appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, - width: 56.h, - height: 56.h, - ), + Image.asset(appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, width: 56.h, height: 56.h), SizedBox(width: 8.h), Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -768,4 +769,9 @@ class _MedicalFilePageState extends State { return Container(); } } + + getMember() { + // AuthanticationViewModel authanticationViewModel = getIt.get(); + // RequestUtils.getAddFamilyRequest(nationalIDorFile: nationalIDorFile, mobileNo: mobileNo, countryCode: countryCode, loginType: loginType); + } } diff --git a/lib/presentation/my_family/my_Family.dart b/lib/presentation/my_family/my_Family.dart new file mode 100644 index 0000000..6e4ef91 --- /dev/null +++ b/lib/presentation/my_family/my_Family.dart @@ -0,0 +1,197 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/core/utils/validation_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/features/medical_file/models/family_file_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/my_family/widget/family_cards.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.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/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; + +class FamilyMedicalScreen extends StatefulWidget { + final List profiles; + final Function(FamilyFileResponseModelLists) onSelect; + + const FamilyMedicalScreen({ + Key? key, + required this.profiles, + required this.onSelect, + }) : super(key: key); + + @override + State createState() => _FamilyMedicalScreenState(); +} + +class _FamilyMedicalScreenState extends State { + List tabs = [CustomTabBarModel("", LocaleKeys.medicalFile.tr()), CustomTabBarModel("", LocaleKeys.request.tr())]; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + appBar: CustomAppBar( + onBackPressed: () { + Navigator.of(context).pop(); + }, + onLanguageChanged: (lang) {}, + hideLogoAndLang: true, + ), + body: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.myMedicalFile.tr().toText26(color: AppColors.textColor, weight: FontWeight.w600, letterSpacing: -2), + SizedBox(height: 25.h), + CustomTabBar( + tabs: tabs, + onTabChange: (int index) {}, + ), + SizedBox(height: 25.h), + FamilyCards(profiles: widget.profiles, onSelect: widget.onSelect, isShowDetails: true), + SizedBox(height: 20.h), + ], + ), + ).paddingSymmetrical(20, 0), + bottomSheet: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + ), + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 20.h), + child: CustomButton( + text: "Add a new family member", + onPressed: () { + showModelSheet(); + }, + icon: AppAssets.add_icon, + height: 56.h, + fontWeight: FontWeight.w600, + )), + ); + } + + void showModelSheet() { + AuthenticationViewModel authVm = getIt.get(); + return showCommonBottomSheetWithoutHeight(context, + title: "Add Family Member", + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + "Please fill the below field to add a new family member to your profile".toText16(color: AppColors.textColor, weight: FontWeight.w500), + SizedBox(height: 20.h), + Container( + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)), + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + children: [ + CustomCountryDropdown( + countryList: CountryEnum.values, + onCountryChange: authVm.onCountryChange, + ).paddingOnly(top: 8.h, bottom: 16.h), + Divider( + height: 1.h, + color: AppColors.spacerLineColor, + ), + TextInputWidget( + labelText: LocaleKeys.nationalIdNumber.tr(), + hintText: "xxxxxxxxx", + controller: authVm.nationalIdController, + // focusNode: _nationalIdFocusNode, + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + keyboardType: TextInputType.number, + padding: EdgeInsets.symmetric(vertical: 8.h), + leadingIcon: AppAssets.student_card, + ).paddingOnly(top: 8.h, bottom: 8.h), + Divider( + height: 1.h, + color: AppColors.spacerLineColor, + ), + TextInputWidget( + labelText: LocaleKeys.phoneNumber.tr(), + hintText: "574345434", + controller: authVm.phoneNumberController, + isEnable: true, + prefix: authVm.selectedCountrySignup.countryCode, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + keyboardType: TextInputType.number, + padding: EdgeInsets.symmetric(vertical: 8.h), + leadingIcon: AppAssets.smart_phone, + ).paddingOnly(top: 8.h, bottom: 4), + + //TextInputWidget( + // labelText: widget.isForEmail ? LocaleKeys.email.tr() : LocaleKeys.phoneNumber.tr(), + // hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx", + // controller: widget.textController!, + // focusNode: _textFieldFocusNode, + // autoFocus: widget.autoFocus, + // padding: EdgeInsets.all(8.h), + // keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number, + // onChange: (value) { + // if (widget.onChange != null) { + // widget.onChange!(value); + // } + // }, + // onCountryChange: (value) { + // if (widget.onCountryChange != null) { + // widget.onCountryChange!(value); + // } + // }, + // isEnable: true, + // isReadOnly: widget.isFromSavedLogin, + // prefix: widget.isForEmail ? null : widget.countryCode, + // isBorderAllowed: false, + // isAllowLeadingIcon: true, + // fontSize: 13.h, + // isCountryDropDown: widget.isEnableCountryDropdown, + // leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone, + // ) + ], + ), + ), + SizedBox(height: 20.h), + CustomButton( + text: "Verify the member", + onPressed: () { + FocusScope.of(context).unfocus(); + if (ValidationUtils.isValidatedIdAndPhoneWithCountryValidation( + nationalId: authVm.nationalIdController.text, + selectedCountry: authVm.selectedCountrySignup, + phoneNumber: authVm.phoneNumberController.text, + onOkPress: () { + Navigator.of(context).pop(); + }, + )) {} + }, + icon: AppAssets.add_icon, + height: 56.h, + fontWeight: FontWeight.w600), + SizedBox(height: 20.h), + ], + ), + callBackFunc: () {}); + } +} diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart new file mode 100644 index 0000000..480f91f --- /dev/null +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -0,0 +1,116 @@ +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/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/family_file_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:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; + +class FamilyCards extends StatefulWidget { + final List profiles; + final Function(FamilyFileResponseModelLists) onSelect; + final bool isShowDetails; + final bool isBottomSheet; + + const FamilyCards({super.key, required this.profiles, required this.onSelect, this.isShowDetails = false, this.isBottomSheet = false}); + + @override + State createState() => _FamilyCardsState(); +} + +class _FamilyCardsState extends State { + AppState appState = getIt(); + + @override + Widget build(BuildContext context) { + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.profiles.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10.h, + mainAxisSpacing: 10.h, + childAspectRatio: widget.isShowDetails ? 0.56.h : 0.74.h, + ), + itemBuilder: (context, index) { + final profile = widget.profiles[index]; + final isActive = (profile.responseId == appState + .getAuthenticatedUser() + ?.patientId); + return Container( + padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), + child: Opacity( + opacity: isActive ? 0.4 : 1.0, // Fade all content if active + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 5.h), + Utils.buildImgWithAssets( + icon: profile.gender == 1 ? ((profile.age ?? 0) < 7 ? AppAssets.babyBoyImg : AppAssets.male_img) : (profile.age! < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg), + width: 80.h, + height: 78.h), + SizedBox(height: 8.h), + (profile.patientName ?? "Unknown").toText16(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), + SizedBox(height: 4.h), + CustomChipWidget( + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "Relation: ${profile.relationship ?? "N/A"}", + iconAsset: AppAssets.heart, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + widget.isShowDetails ? SizedBox(height: 4.h) : SizedBox(), + widget.isShowDetails + ? CustomChipWidget( + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "Age: ${profile.age ?? "N/A"} Years", + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor, + ) + : SizedBox(), + widget.isShowDetails ? SizedBox(height: 8.h) : SizedBox(), + Spacer(), + if (isActive) + CustomButton( + height: 40.h, + onPressed: () {}, + text: LocaleKeys.active.tr(), + backgroundColor: Colors.grey.shade200, + borderColor: Colors.grey.shade200, + textColor: AppColors.greyTextColor, + fontSize: 13.h, + ).paddingOnly(top: 0, bottom: 0) + else + CustomButton( + height: 40.h, + onPressed: () => widget.onSelect(profile), + text: LocaleKeys.select.tr(), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 13.h, + icon: widget.isBottomSheet ? null : AppAssets.heart, + iconColor: AppColors.primaryRedColor, + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), + ).paddingOnly(top: 0, bottom: 0), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/presentation/my_family/widget/my_family_sheet.dart b/lib/presentation/my_family/widget/my_family_sheet.dart new file mode 100644 index 0000000..a119906 --- /dev/null +++ b/lib/presentation/my_family/widget/my_family_sheet.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; +import 'package:hmg_patient_app_new/presentation/my_family/my_Family.dart'; +import 'package:hmg_patient_app_new/presentation/my_family/widget/family_cards.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; + +class MyFamilySheet { + static void show(BuildContext context, List familyLists, Function(FamilyFileResponseModelLists) onSelect) { + return showCommonBottomSheetWithoutHeight( + context, + titleWidget: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 'Please select a profile'.toText21(isBold: true), + 'switch from the below list of medical file'.toText16(weight: FontWeight.w100, color: AppColors.greyTextColor), + ], + ), + child: FamilyCards( + profiles: familyLists, + onSelect: (profile) { + Navigator.of(context).pop(); // Close the bottom sheet + onSelect(profile); // Call the onSelect callback + }, + isBottomSheet: true), + callBackFunc: () {}, + ); + } +} diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 0c7da58..06079dc 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -36,13 +36,15 @@ class AppColors { static const Color warningColorYellow = Color(0xFFF4A308); static const Color blackBgColor = Color(0xFF2E3039); static const blackColor = textColor; - static const inputLabelTextColor = Color(0xff898A8D); - static const greyTextColor = Color(0xFF8F9AA3); + static const Color inputLabelTextColor = Color(0xff898A8D); + static const Color greyTextColor = Color(0xFF8F9AA3); + static const Color lightGrayBGColor = Color(0x142E3039); static const lightGreenColor = Color(0xFF0ccedde); static const textGreenColor = Color(0xFF18C273); static const Color ratingColorYellow = Color(0xFFFFAF15); + static const Color spacerLineColor = Color(0x2E30391A); //Chips static const Color successColor = Color(0xff18C273); @@ -51,6 +53,8 @@ class AppColors { static const Color infoColor = Color(0xFF0B85F7); static const Color warningColor = Color(0xFFFFCC00); static const Color greyColor = Color(0xFFEFEFF0); + static const Color chipPrimaryRedBorderColor = Color(0xFFED1C2B); + static const Color chipSecondaryLightRedColor = Color(0xFFFEE9EA); static const Color successLightColor = Color(0xFF18C273); static const Color errorLightColor = Color(0xFFED1C2B); @@ -62,4 +66,7 @@ static const Color greyLightColor = Color(0xFFEFEFF0); static const Color bottomNAVBorder = Color(0xFFEEEEEE); static const Color quickLoginColor = Color(0xFF666666); + +static const Color tooltipTextColor = Color(0xFF414D55); +static const Color graphGridColor = Color(0x4D18C273); } diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index b29f6e6..a6e1817 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -72,9 +72,9 @@ class AppCustomChipWidget extends StatelessWidget { // padding: EdgeInsets.all(0.0), padding: padding, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - labelPadding: EdgeInsets.only( - left: -4.h, - right: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), + labelPadding: EdgeInsetsDirectional.only( + start: -4.h, + end: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), backgroundColor: backgroundColor, shape: shape, deleteIcon: deleteIcon?.isNotEmpty == true @@ -96,9 +96,9 @@ class AppCustomChipWidget extends StatelessWidget { padding: EdgeInsets.all(0.0), backgroundColor: backgroundColor, shape: shape, - labelPadding: EdgeInsets.only( - left: 8.h, - right: deleteIcon?.isNotEmpty == true ? -2.h : 8.h), + labelPadding: EdgeInsetsDirectional.only( + start: 8.h, + end: deleteIcon?.isNotEmpty == true ? -2.h : 8.h), deleteIcon: deleteIcon?.isNotEmpty == true ? Utils.buildSvgWithAssets( icon: deleteIcon!, diff --git a/lib/widgets/chip/custom_chip_widget.dart b/lib/widgets/chip/custom_chip_widget.dart index 6c2f1e3..1d0b5ef 100644 --- a/lib/widgets/chip/custom_chip_widget.dart +++ b/lib/widgets/chip/custom_chip_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -12,6 +13,10 @@ class CustomChipWidget extends StatelessWidget { final bool isSelected; final double borderRadius; final EdgeInsetsGeometry padding; + final Color? backgroundColor; + final Color? textColor; + final Color? borderColor; + final bool isShowBorder; const CustomChipWidget({ super.key, @@ -22,6 +27,10 @@ class CustomChipWidget extends StatelessWidget { this.isSelected = false, this.borderRadius = 12, this.padding = const EdgeInsets.all(8), + this.backgroundColor, + this.textColor, + this.borderColor, + this.isShowBorder = false, }); @override @@ -32,11 +41,13 @@ class CustomChipWidget extends StatelessWidget { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(borderRadius), - color: isSelected ? chipType.color : chipType.backgroundColor, - border: Border.all( - color: chipType.color, - width: isSelected ? 0 : 1, - ), + color: isSelected ? chipType.color : backgroundColor ?? chipType.backgroundColor, + border: isShowBorder + ? Border.all( + color: chipType.color, + width: isSelected ? 0 : 1, + ) + : null, ), child: InkWell( onTap: hasOnTap ? onTap : null, @@ -51,18 +62,9 @@ class CustomChipWidget extends StatelessWidget { children: [ if (iconAsset != null) ...[ Utils.buildSvgWithAssets(icon: iconAsset!), - const SizedBox(width: 6), + SizedBox(width: 4.h), ], - Text( - chipText.toUpperCase(), - style: context.dynamicTextStyle( - fontWeight: FontWeight.w500, - fontSize: 14, - color: isSelected ? Colors.white : chipType.color, - letterSpacing: 0.1, - isLanguageSwitcher: true, - ), - ), + chipText.toText10(isBold: true, color: isSelected ? Colors.white : textColor ?? chipType.color, maxlines: 1, weight: FontWeight.w500, letterSpacing: -0.5), ], ), ), diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart new file mode 100644 index 0000000..d66d281 --- /dev/null +++ b/lib/widgets/graph/custom_graph.dart @@ -0,0 +1,287 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +/// +/// CustomGraph(dataPoints: sampleData, scrollDirection: Axis.horizontal,height: 200,maxY: 100, maxX:2.5, +/// leftLabelFormatter: (value){ +/// Widget buildLabel(String label) { +/// return Padding( +/// padding: const EdgeInsets.only(right: 8), +/// child: Text( +/// label, +/// style: TextStyle( +/// fontSize: 8.fSize, color: AppColors.textColor, +/// fontFamily: +/// FontUtils.getFontFamilyForLanguage(false) +/// ), +/// textAlign: TextAlign.right, +/// ), +/// ); +/// } +/// switch (value.toInt()) { +/// +/// case 20: +/// return buildLabel("Critical Low"); +/// case 40: +/// return buildLabel("Low"); +/// case 60: +/// return buildLabel("Normal"); +/// case 80: +/// return buildLabel("High"); +/// case 100: +/// return buildLabel("Critical High"); +/// } +/// return const SizedBox.shrink(); +/// }, +/// +/// ), +class CustomGraph extends StatelessWidget { + final List dataPoints; + final double? width; + final double height; + final double? maxY; + final double? maxX; + final Color spotColor; + final Color graphColor; + final Color graphShadowColor; + final Color graphGridColor; + final Color bottomLabelColor; + final double? bottomLabelSize; + final FontWeight? bottomLabelFontWeight; + + ///creates the left label and provide it to the chart as it will be used by other part of the application so the label will be different for every chart + final Widget Function(double value) leftLabelFormatter; + + final Axis scrollDirection; + final bool showBottomTitleDates; + final bool isFullScreeGraph; + + const CustomGraph({ + super.key, + required this.dataPoints, + required this.leftLabelFormatter, + this.width, + required this.scrollDirection, + required this.height, + this.maxY, + this.maxX, + this.showBottomTitleDates = true, + this.isFullScreeGraph = false, + this.spotColor = AppColors.bgGreenColor, + this.graphColor = AppColors.bgGreenColor, + this.graphShadowColor = AppColors.graphGridColor, + this.graphGridColor = AppColors.graphGridColor, + this.bottomLabelColor = AppColors.textColor, + this.bottomLabelFontWeight = FontWeight.w500, + this.bottomLabelSize, + }); + + @override + Widget build(BuildContext context) { + // var maxY = 0.0; + double interval = 20; + if ((maxY ?? 0) > 10 && (maxY ?? 0) <= 20) { + interval = 2; + } else if ((maxY ?? 0) > 5 && (maxY ?? 0) <= 10) { + interval = 1; + } else if ((maxY ?? 0) >= 0 && (maxY ?? 0) <= 5) { + interval = .4; + } + return Material( + color: Colors.white, + child: SizedBox( + width: width, + height: height, + child: Padding( + padding: const EdgeInsets.only(top: 8.0, bottom: 8), + child: LineChart( + LineChartData( + minY: 0, + maxY: + ((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(), + // minX: dataPoints.first.labelValue - 1, + maxX: maxX, + minX: -0.2, + lineTouchData: LineTouchData( + getTouchLineEnd: (_, __) => 0, + getTouchedSpotIndicator: (barData, indicators) { + // Only show custom marker for touched spot + return indicators.map((int index) { + return TouchedSpotIndicatorData( + FlLine(color: Colors.transparent), + FlDotData( + show: true, + getDotPainter: (spot, percent, barData, idx) { + return FlDotCirclePainter( + radius: 8, + color: spotColor, + strokeWidth: 2, + strokeColor: Colors.white, + ); + }, + ), + ); + }).toList(); + }, + enabled: true, + touchTooltipData: LineTouchTooltipData( + getTooltipColor: (_) => Colors.white, + getTooltipItems: (touchedSpots) { + if (touchedSpots.isEmpty) return []; + // Only show tooltip for the first touched spot, hide others + return touchedSpots.map((spot) { + if (spot == touchedSpots.first) { + final dataPoint = dataPoints[spot.x.toInt()]; + + return LineTooltipItem( + // '${dataPoint.label} ${spot.y.toStringAsFixed(2)}', + '${dataPoint.value} ', + TextStyle( + color: Colors.black, + fontSize: 12.fSize, + fontWeight: FontWeight.w500), + ); + } + return null; // hides the rest + }).toList(); + }, + ), + ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 77, + interval: .1, // Let fl_chart handle it + getTitlesWidget: (value, _) { + return leftLabelFormatter(value); + }, + ), + ), + bottomTitles: AxisTitles( + axisNameSize: 60, + sideTitles: SideTitles( + showTitles: showBottomTitleDates, + reservedSize: 50, + getTitlesWidget: (value, _) { + if ((value.toDouble() >= 0) && + (value.toDouble() < (maxX ?? dataPoints.length))) { + var label = dataPoints[value.toInt()].label; + + return buildBottomLabel(label); + } + return const SizedBox.shrink(); + }, + interval: 1, // ensures 1:1 mapping with spots + ), + ), + topTitles: AxisTitles(), + rightTitles: AxisTitles(), + ), + borderData: FlBorderData( + show: true, + border: const Border( + bottom: BorderSide.none, + left: BorderSide(color: Colors.grey, width: .5), + right: BorderSide.none, + top: BorderSide.none, + ), + ), + lineBarsData: _buildColoredLineSegments(dataPoints), + gridData: FlGridData( + show: true, + drawVerticalLine: false, + horizontalInterval: 20, + checkToShowHorizontalLine: (value) => + value >= 0 && value <= 100, + getDrawingHorizontalLine: (value) { + return FlLine( + color: AppColors.graphGridColor, + strokeWidth: 1, + dashArray: [5, 5], + ); + }, + ), + ), + ), + ), + )); + } + + List _buildColoredLineSegments(List dataPoints) { + final List allSpots = dataPoints.asMap().entries.map((entry) { + return FlSpot(entry.key.toDouble(), entry.value.value); + }).toList(); + + var data = [ + LineChartBarData( + spots: allSpots, + isCurved: true, + isStrokeCapRound: true, + isStrokeJoinRound: true, + barWidth: 4, + gradient: LinearGradient( + colors: [graphColor, graphColor], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + dotData: FlDotData( + show: false, + ), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + graphShadowColor, + Colors.transparent, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + ) + ]; + + return data; + } + + // Widget buildLabel(String label) { + // return Padding( + // padding: const EdgeInsets.only(right: 8), + // child: Text( + // label, + // style: TextStyle( + // fontSize: leftLabelSize ?? 8.fSize, color: leftLabelColor), + // textAlign: TextAlign.right, + // ), + // ); + // } + + Widget buildBottomLabel(String label) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + label, + style: TextStyle( + fontSize: bottomLabelSize ?? 8.fSize, color: bottomLabelColor), + ), + ); + } +} + +final List sampleData = [ + DataPoint( + value: 20, + label: 'Jan 2024', + ), + DataPoint( + value: 36, + label: 'Feb 2024', + ), + DataPoint( + value: 80, + label: 'This result', + ), +]; diff --git a/lib/widgets/my_family/my_Family.dart b/lib/widgets/my_family/my_Family.dart deleted file mode 100644 index 912ae0b..0000000 --- a/lib/widgets/my_family/my_Family.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/material.dart'; - -class ProfileSelector extends StatelessWidget { - final List> profiles; - final Function(Map) onSelect; - - const ProfileSelector({ - Key? key, - required this.profiles, - required this.onSelect, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: profiles.map((profile) { - return ListTile( - leading: CircleAvatar( - radius: 22, - backgroundImage: profile["GenderImage"] != null && - profile["GenderImage"].toString().isNotEmpty - ? NetworkImage(profile["GenderImage"]) - : AssetImage( - profile["Gender"] == 1 - ? "assets/images/male.png" - : "assets/images/female.png") - as ImageProvider, - ), - title: Text( - profile["PatientName"] ?? "Unknown", - style: const TextStyle(fontWeight: FontWeight.w600), - ), - subtitle: Text( - profile["Relationship"] ?? "Self", - style: const TextStyle(color: Colors.grey), - ), - trailing: const Icon(Icons.arrow_forward_ios, size: 16), - onTap: () => onSelect(profile), - ); - }).toList(), - ); - } -} diff --git a/lib/widgets/my_family/my_family_sheet.dart b/lib/widgets/my_family/my_family_sheet.dart deleted file mode 100644 index 2e0ae15..0000000 --- a/lib/widgets/my_family/my_family_sheet.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; -import '../common_bottom_sheet.dart'; -import 'my_Family.dart'; - class MyFamilySheet { - static void show(BuildContext context, List> profiles, Function(Map) onSelect) { - showCommonBottomSheetWithoutHeight( - context, - title: 'Select Profile', - child: ProfileSelector(profiles: profiles, onSelect: (profile) { - Navigator.of(context).pop(); // Close the bottom sheet - onSelect(profile); // Call the onSelect callback - }), callBackFunc: () {}, - ); - } - } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index ea41d43..0466933 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,7 +55,7 @@ dependencies: uuid: ^4.5.1 health: ^13.1.3 # health: 12.0.1 - fl_chart: ^1.0.0 + fl_chart: ^1.1.1 geolocator: ^14.0.2 dropdown_search: ^6.0.2 google_maps_flutter: ^2.12.3