From f9bfc131a8d344ee7b9aebb11c0d496ca07ad5a7 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Mon, 22 Sep 2025 09:55:38 +0300 Subject: [PATCH 01/12] family screen & widgets --- lib/core/api_consts.dart | 3 ++ lib/core/utils/request_utils.dart | 11 ++++ .../authentication/authentication_repo.dart | 21 +++++--- .../authentication_view_model.dart | 39 +++++++++++--- lib/presentation/my_family/my_Family.dart | 54 ++++++------------- lib/services/dialog_service.dart | 5 +- lib/widgets/common_bottom_sheet.dart | 3 ++ 7 files changed, 81 insertions(+), 55 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 5886dfa..9b013a5 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -808,6 +808,9 @@ class ApiConsts { static final String registerUser = 'Services/Authentication.svc/REST/PatientRegistration'; static final String addFamilyFile = 'Services/Patients.svc/REST/ShareFamilyFileService'; + static final String sendFamilyFileActivation = 'Services/Authentication.svc/REST/SendActivationCodeForFamilyFile'; + static final String checkActivationCodeForFamily = 'Services/Authentication.svc/REST/CheckActivationCodeForFamilyFile'; + // static values for Api static final double appVersionID = 18.7; diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 8b993f1..87f88f5 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -125,6 +125,8 @@ class RequestUtils { required bool isForRegister, required bool isFileNo, dynamic payload, + required bool isExcludedUser, + int? responseID, }) { AppState _appState = getIt.get(); var request = SendActivationRequest(); @@ -156,6 +158,15 @@ class RequestUtils { request.isRegister = false; } request.deviceTypeID = request.searchType; + + if (isExcludedUser) { + //INFO: Only for Excluded User Family Member Addition + request.isPatientExcluded = isExcludedUser; + request.responseID = responseID; + request.status = 2; + request.familyRegionID = zipCode == CountryEnum.saudiArabia.countryCode ? 1 : 2; + } + return request; } diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 2eaeb80..89a9404 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -17,12 +17,9 @@ abstract class AuthenticationRepo { Future>> checkPatientAuthentication({required dynamic checkPatientAuthenticationReq}); - Future>> sendActivationCodeRepo({required dynamic sendActivationCodeReq, String? languageID, bool isRegister = false}); + Future>> sendActivationCodeRepo({required dynamic sendActivationCodeReq, String? languageID, bool isRegister = false, bool isExcludedUser = false}); - Future>> checkActivationCodeRepo( - {required dynamic newRequest, // could be CheckActivationCodeReq or CheckActivationCodeRegisterReq - required String? activationCode, - required bool isRegister}); + Future>> checkActivationCodeRepo({required dynamic newRequest, required String? activationCode, required bool isRegister, bool isExcludedUser = false}); Future>> checkIfUserAgreed({required dynamic commonAuthanticatedRequest}); @@ -134,6 +131,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { required dynamic sendActivationCodeReq, String? languageID, bool isRegister = false, + bool isExcludedUser = false, }) async { int isOutKsa = (sendActivationCodeReq.zipCode == '966' || sendActivationCodeReq.zipCode == '+966') ? 0 : 1; sendActivationCodeReq.patientOutSA = isOutKsa; @@ -144,7 +142,11 @@ class AuthenticationRepoImp implements AuthenticationRepo { Failure? failure; await apiClient.post( - isRegister ? ApiConsts.sendActivationCodeRegister : ApiConsts.sendActivationCode, + isExcludedUser + ? ApiConsts.sendFamilyFileActivation + : isRegister + ? ApiConsts.sendActivationCodeRegister + : ApiConsts.sendActivationCode, body: sendActivationCodeReq.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; @@ -176,6 +178,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { required dynamic newRequest, // could be CheckActivationCodeReq or CheckActivationCodeRegisterReq required String? activationCode, required bool isRegister, + bool isExcludedUser = false, }) async { if (isRegister) { newRequest["activationCode"] = activationCode ?? "0000"; @@ -189,7 +192,11 @@ class AuthenticationRepoImp implements AuthenticationRepo { newRequest.isRegister = false; } - final endpoint = isRegister ? ApiConsts.checkActivationCodeRegister : ApiConsts.checkActivationCode; + final endpoint = isExcludedUser + ? ApiConsts.checkActivationCodeForFamily + : isRegister + ? ApiConsts.checkActivationCodeRegister + : ApiConsts.checkActivationCode; try { GenericApiModel? apiResponse; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 11374b7..4590a59 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -344,7 +344,14 @@ class AuthenticationViewModel extends ChangeNotifier { } Future sendActivationCode( - {required OTPTypeEnum otpTypeEnum, required String nationalIdOrFileNumber, required String phoneNumber, required bool isForRegister, dynamic payload, bool isComingFromResendOTP = false}) async { + {required OTPTypeEnum otpTypeEnum, + required String nationalIdOrFileNumber, + required String phoneNumber, + required bool isForRegister, + dynamic payload, + bool isComingFromResendOTP = false, + bool isExcludedUser = false, + int? responseID}) async { var request = RequestUtils.getCommonRequestSendActivationCode( otpTypeEnum: otpTypeEnum, mobileNumber: phoneNumber, @@ -360,6 +367,8 @@ class AuthenticationViewModel extends ChangeNotifier { ? false : true, payload: payload, + isExcludedUser: isExcludedUser, + responseID: responseID, ); // TODO: GET APP SMS SIGNATURE HERE @@ -369,7 +378,8 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setUserRegistrationPayload = RegistrationDataModelPayload.fromJson(payload); } - final resultEither = await _authenticationRepo.sendActivationCodeRepo(sendActivationCodeReq: request, isRegister: checkIsUserComingForRegister(request: payload), languageID: 'er'); + final resultEither = + await _authenticationRepo.sendActivationCodeRepo(sendActivationCodeReq: request, isRegister: checkIsUserComingForRegister(request: payload), languageID: 'er', isExcludedUser: isExcludedUser); resultEither.fold( (failure) async => await _errorHandlerService.handleError(failure: failure), @@ -384,7 +394,9 @@ class AuthenticationViewModel extends ChangeNotifier { } else { if (apiResponse.data != null && apiResponse.data['isSMSSent'] == true) { LoaderBottomSheet.hideLoader(); - if (!isComingFromResendOTP) navigateToOTPScreen(otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber, isComingFromRegister: checkIsUserComingForRegister(request: payload), payload: payload); + if (!isComingFromResendOTP) + navigateToOTPScreen( + otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber, isComingFromRegister: checkIsUserComingForRegister(request: payload), payload: payload, isExcludedUser: isExcludedUser); } else { // TODO: Handle isSMSSent false // navigateToOTPScreen(otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber); @@ -403,7 +415,13 @@ class AuthenticationViewModel extends ChangeNotifier { } Future checkActivationCode( - {required String? activationCode, required OTPTypeEnum otpTypeEnum, required Function(String? message) onWrongActivationCode, Function()? onResendActivation}) async { + {required String? activationCode, + required OTPTypeEnum otpTypeEnum, + required Function(String? message) onWrongActivationCode, + Function()? onResendActivation, + bool isExcludedUser = false, + dynamic requestID, + dynamic responseID}) async { bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1); final request = RequestUtils.getCommonRequestWelcome( @@ -429,6 +447,13 @@ class AuthenticationViewModel extends ChangeNotifier { //TODO: Error Here IN Zip Code. loginType: loginTypeEnum.toInt) .toJson(); + + if (isExcludedUser) { + request['PatientShareRequestID'] = requestID; + request['ResponseID'] = responseID; + request['Status'] = 3; + } + LoaderBottomSheet.showLoader(); if (isForRegister) { if (_appState.getUserRegistrationPayload.patientOutSa == 0) request['DOB'] = _appState.getUserRegistrationPayload.dob; @@ -464,7 +489,8 @@ class AuthenticationViewModel extends ChangeNotifier { } }); } else { - final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: CheckActivationCodeRegisterReq.fromJson(request), activationCode: activationCode, isRegister: false); + final resultEither = await _authenticationRepo.checkActivationCodeRepo( + newRequest: CheckActivationCodeRegisterReq.fromJson(request), activationCode: activationCode, isRegister: false, isExcludedUser: isExcludedUser); resultEither.fold( (failure) async => await _errorHandlerService.handleError( @@ -573,12 +599,13 @@ class AuthenticationViewModel extends ChangeNotifier { _navigationService.pushAndReplace(AppRoutes.landingScreen); } - Future navigateToOTPScreen({required OTPTypeEnum otpTypeEnum, required String phoneNumber, required bool isComingFromRegister, dynamic payload}) async { + Future navigateToOTPScreen({required OTPTypeEnum otpTypeEnum, required String phoneNumber, required bool isComingFromRegister, dynamic payload, bool isExcludedUser = false}) async { _navigationService.pushToOtpScreen( phoneNumber: phoneNumber, checkActivationCode: (int activationCode) async { await checkActivationCode( activationCode: activationCode.toString(), + isExcludedUser: isExcludedUser, otpTypeEnum: otpTypeEnum, onWrongActivationCode: (String? value) { onWrongActivationCode(message: value); diff --git a/lib/presentation/my_family/my_Family.dart b/lib/presentation/my_family/my_Family.dart index 6e4ef91..888f364 100644 --- a/lib/presentation/my_family/my_Family.dart +++ b/lib/presentation/my_family/my_Family.dart @@ -89,9 +89,11 @@ class _FamilyMedicalScreenState extends State { AuthenticationViewModel authVm = getIt.get(); return showCommonBottomSheetWithoutHeight(context, title: "Add Family Member", + useSafeArea: true, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, 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), @@ -104,10 +106,7 @@ class _FamilyMedicalScreenState extends State { countryList: CountryEnum.values, onCountryChange: authVm.onCountryChange, ).paddingOnly(top: 8.h, bottom: 16.h), - Divider( - height: 1.h, - color: AppColors.spacerLineColor, - ), + Divider(height: 1.h, color: AppColors.spacerLineColor), TextInputWidget( labelText: LocaleKeys.nationalIdNumber.tr(), hintText: "xxxxxxxxx", @@ -123,13 +122,10 @@ class _FamilyMedicalScreenState extends State { padding: EdgeInsets.symmetric(vertical: 8.h), leadingIcon: AppAssets.student_card, ).paddingOnly(top: 8.h, bottom: 8.h), - Divider( - height: 1.h, - color: AppColors.spacerLineColor, - ), + Divider(height: 1.h, color: AppColors.spacerLineColor), TextInputWidget( labelText: LocaleKeys.phoneNumber.tr(), - hintText: "574345434", + hintText: "", controller: authVm.phoneNumberController, isEnable: true, prefix: authVm.selectedCountrySignup.countryCode, @@ -140,35 +136,7 @@ class _FamilyMedicalScreenState extends State { 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, - // ) + ).paddingOnly(top: 8.h, bottom: 4.h), ], ), ), @@ -184,7 +152,15 @@ class _FamilyMedicalScreenState extends State { onOkPress: () { Navigator.of(context).pop(); }, - )) {} + )) { + authVm.sendActivationCode( + otpTypeEnum: OTPTypeEnum.sms, + nationalIdOrFileNumber: authVm.nationalIdController.text, + phoneNumber: authVm.phoneNumberController.text, + isForRegister: false, + isExcludedUser: true, + responseID: 123); + } }, icon: AppAssets.add_icon, height: 56.h, diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 29aee3d..474a0ec 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -73,9 +73,8 @@ class DialogServiceImp implements DialogService { Future showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; - showCommonBottomSheetWithoutHeight(context, title: label ?? "", child: exceptionBottomSheetWidget(context: context, message: message, onOkPressed: onOkPressed, onCancelPressed: onCancelPressed), - callBackFunc: () { - }); + showCommonBottomSheetWithoutHeight(context, + title: label ?? "", child: exceptionBottomSheetWidget(context: context, message: message, onOkPressed: onOkPressed, onCancelPressed: onCancelPressed), callBackFunc: () {}); } @override diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index c50ffad..286386d 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -113,6 +113,8 @@ void showCommonBottomSheetWithoutHeight( bool isFullScreen = true, bool isDismissible = true, Widget? titleWidget, + bool useSafeArea = false, + }) { showModalBottomSheet( sheetAnimationStyle: AnimationStyle( @@ -124,6 +126,7 @@ void showCommonBottomSheetWithoutHeight( showDragHandle: false, isDismissible: isDismissible, backgroundColor: AppColors.bottomSheetBgColor, + useSafeArea: useSafeArea, builder: (BuildContext context) { return SafeArea( top: false, From 1125437a569d99d7d9e3fef26ffce5ea6ff9193d Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Mon, 22 Sep 2025 14:48:39 +0300 Subject: [PATCH 02/12] family screen & widgets --- lib/core/api_consts.dart | 2 +- lib/core/utils/request_utils.dart | 12 +- .../authentication_view_model.dart | 2 + .../medical_file/medical_file_view_model.dart | 16 +- lib/presentation/my_family/my_Family.dart | 17 +- lib/widgets/common_bottom_sheet.dart | 175 +++++++++++++----- 6 files changed, 163 insertions(+), 61 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 9b013a5..3640cdd 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -727,7 +727,7 @@ const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatu class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 87f88f5..4f66819 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -258,8 +258,16 @@ class RequestUtils { }; } - static dynamic getAddFamilyRequest({required String nationalIDorFile, required String mobileNo, required String countryCode, required int loginType}) { + static dynamic getAddFamilyRequest({required String nationalIDorFile, required String mobileNo, required String countryCode}) { var request = {}; + int? loginType = 0; // Default to National ID + + if (countryCode == CountryEnum.saudiArabia.countryCode || countryCode == '+966') { + loginType = (nationalIDorFile.length == 10) ? 1 : 2; + } else if (countryCode == CountryEnum.unitedArabEmirates.countryCode || countryCode == '+971') { + loginType = (nationalIDorFile.length == 15) ? 1 : 2; + } + if (loginType == 1) { request["sharedPatientID"] = 0; request["sharedPatientIdentificationID"] = nationalIDorFile; @@ -272,5 +280,7 @@ class RequestUtils { request["zipCode"] = countryCode; request["isRegister"] = false; request["patientStatus"] = 2; + request["isDentalAllowedBackend"] = false; + return request; } } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 4590a59..f6c2ec5 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/core/common_models/privilege/HMCProjectListM import 'package:hmg_patient_app_new/core/common_models/privilege/PrivilegeModel.dart'; import 'package:hmg_patient_app_new/core/common_models/privilege/ProjectDetailListModel.dart'; import 'package:hmg_patient_app_new/core/common_models/privilege/VidaPlusProjectListModel.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/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; @@ -26,6 +27,7 @@ import 'package:hmg_patient_app_new/features/authentication/models/request_model import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_activation_code_resp_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; import 'package:hmg_patient_app_new/presentation/authentication/saved_login_screen.dart'; diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 2e528de..a724a10 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -1,7 +1,10 @@ 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/enums.dart'; import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.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'; @@ -293,9 +296,14 @@ class MedicalFileViewModel extends ChangeNotifier { ); } - Future addFamilyFile() async { - final resultEither = await medicalFileRepo.addFamilyFile(request: {}); - resultEither.fold((failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) async {}); - } + Future addFamilyFile({required OTPTypeEnum otpTypeEnum, required bool isExcludedUser}) async { + AuthenticationViewModel authVM = getIt.get(); + final request = + await RequestUtils.getAddFamilyRequest(nationalIDorFile: authVM.nationalIdController.text, mobileNo: authVM.phoneNumberController.text, countryCode: authVM.selectedCountrySignup.countryCode); + final resultEither = await medicalFileRepo.addFamilyFile(request: request); + resultEither.fold((failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) async { + print(apiResponse); + }); + } } diff --git a/lib/presentation/my_family/my_Family.dart b/lib/presentation/my_family/my_Family.dart index 888f364..4eefe7a 100644 --- a/lib/presentation/my_family/my_Family.dart +++ b/lib/presentation/my_family/my_Family.dart @@ -10,6 +10,7 @@ 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/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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/my_family/widget/family_cards.dart'; @@ -38,6 +39,13 @@ class FamilyMedicalScreen extends StatefulWidget { class _FamilyMedicalScreenState extends State { List tabs = [CustomTabBarModel("", LocaleKeys.medicalFile.tr()), CustomTabBarModel("", LocaleKeys.request.tr())]; + MedicalFileViewModel? medicalVM; + + @override + void initState() { + super.initState(); + medicalVM = getIt.get(); + } @override Widget build(BuildContext context) { @@ -153,13 +161,8 @@ class _FamilyMedicalScreenState extends State { Navigator.of(context).pop(); }, )) { - authVm.sendActivationCode( - otpTypeEnum: OTPTypeEnum.sms, - nationalIdOrFileNumber: authVm.nationalIdController.text, - phoneNumber: authVm.phoneNumberController.text, - isForRegister: false, - isExcludedUser: true, - responseID: 123); + // authVm.addFamilyMember(otpTypeEnum: OTPTypeEnum.sms, isExcludedUser: true); + medicalVM?.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms, isExcludedUser: true); } }, icon: AppAssets.add_icon, diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 286386d..c6db474 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -105,57 +105,136 @@ class ButtonSheetContent extends StatelessWidget { } void showCommonBottomSheetWithoutHeight( - BuildContext context, { - required Widget child, - required VoidCallback callBackFunc, - String title = "", - bool isCloseButtonVisible = true, - bool isFullScreen = true, - bool isDismissible = true, - Widget? titleWidget, - bool useSafeArea = false, - -}) { + BuildContext context, { + required Widget child, + required VoidCallback callBackFunc, + String title = "", + bool isCloseButtonVisible = true, + bool isFullScreen = true, + bool isDismissible = true, + Widget? titleWidget, + bool useSafeArea = false, + }) { showModalBottomSheet( - sheetAnimationStyle: AnimationStyle( - duration: Duration(milliseconds: 500), // Custom animation duration - reverseDuration: Duration(milliseconds: 300), // Custom reverse animation duration - ), - context: context, - isScrollControlled: true, - showDragHandle: false, - isDismissible: isDismissible, - backgroundColor: AppColors.bottomSheetBgColor, - useSafeArea: useSafeArea, - builder: (BuildContext context) { - return SafeArea( - top: false, - left: false, - right: false, - child: isCloseButtonVisible - ? Container( - padding: EdgeInsets.only(left: 24, top: 24, right: 24, bottom: 12), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.bottomSheetBgColor, borderRadius: 24.h), - child: Column( - mainAxisSize: MainAxisSize.min, - spacing: 16.h, + sheetAnimationStyle: AnimationStyle( + duration: Duration(milliseconds: 500), + reverseDuration: Duration(milliseconds: 300), + ), + context: context, + isScrollControlled: true, + showDragHandle: false, + isDismissible: isDismissible, + backgroundColor: AppColors.bottomSheetBgColor, + useSafeArea: useSafeArea, + builder: (BuildContext context) { + return SafeArea( + top: false, + left: false, + right: false, + child: Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: SingleChildScrollView( + physics: ClampingScrollPhysics(), + child: isCloseButtonVisible + ? Container( + padding: EdgeInsets.only( + left: 24, + top: 24, + right: 24, + bottom: 12, + ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.bottomSheetBgColor, + borderRadius: 24.h, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - titleWidget ?? Expanded(child: title.toText20(weight: FontWeight.w600)), - Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() { - Navigator.of(context).pop(); - }), - ], - ), - child, + titleWidget ?? + Expanded( + child: title.toText20(weight: FontWeight.w600), + ), + Utils.buildSvgWithAssets( + icon: AppAssets.close_bottom_sheet_icon, + iconColor: Color(0xff2B353E), + ).onPress(() { + Navigator.of(context).pop(); + }), ], - )) - : child, - ); - }).then((value) { + ), + SizedBox(height: 16.h), + child, + ], + ), + ) + : child, + ), + ), + ); + }, + ).then((value) { callBackFunc(); }); } + +// void showCommonBottomSheetWithoutHeight( +// BuildContext context, { +// required Widget child, +// required VoidCallback callBackFunc, +// String title = "", +// bool isCloseButtonVisible = true, +// bool isFullScreen = true, +// bool isDismissible = true, +// Widget? titleWidget, +// bool useSafeArea = false, +// +// }) { +// showModalBottomSheet( +// sheetAnimationStyle: AnimationStyle( +// duration: Duration(milliseconds: 500), // Custom animation duration +// reverseDuration: Duration(milliseconds: 300), // Custom reverse animation duration +// ), +// context: context, +// isScrollControlled: true, +// showDragHandle: false, +// isDismissible: isDismissible, +// backgroundColor: AppColors.bottomSheetBgColor, +// useSafeArea: useSafeArea, +// builder: (BuildContext context) { +// return SafeArea( +// top: false, +// left: false, +// right: false, +// child: isCloseButtonVisible +// ? Container( +// padding: EdgeInsets.only(left: 24, top: 24, right: 24, bottom: 12), +// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.bottomSheetBgColor, borderRadius: 24.h), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// spacing: 16.h, +// children: [ +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// titleWidget ?? Expanded(child: title.toText20(weight: FontWeight.w600)), +// Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() { +// Navigator.of(context).pop(); +// }), +// ], +// ), +// child, +// ], +// )) +// : child, +// ); +// }).then((value) { +// callBackFunc(); +// }); +// } From 192d617350216d240506b98284e62751ebf473e2 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Wed, 24 Sep 2025 09:33:33 +0300 Subject: [PATCH 03/12] family screen & widgets --- lib/core/api_consts.dart | 2 +- lib/core/utils/request_utils.dart | 28 ++-- .../authentication/authentication_repo.dart | 6 +- .../authentication_view_model.dart | 40 +++--- .../common/models/family_file_request.dart | 57 ++++++++ .../medical_file/medical_file_repo.dart | 23 ++-- .../medical_file/medical_file_view_model.dart | 32 ++++- .../medical_file/medical_file_page.dart | 7 + lib/presentation/my_family/my_Family.dart | 4 +- lib/widgets/chip/app_custom_chip_widget.dart | 129 +++++++++--------- 10 files changed, 211 insertions(+), 117 deletions(-) create mode 100644 lib/features/common/models/family_file_request.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 3640cdd..9b013a5 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -727,7 +727,7 @@ const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatu 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 diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 4f66819..cd813ed 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/registration_payload_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/send_activation_request_model.dart'; import 'package:hmg_patient_app_new/features/common/models/commong_authanticated_req_model.dart'; +import 'package:hmg_patient_app_new/features/common/models/family_file_request.dart'; class RequestUtils { static dynamic getPatientAuthenticationRequest({ @@ -126,6 +127,7 @@ class RequestUtils { required bool isFileNo, dynamic payload, required bool isExcludedUser, + required bool isFormFamilyFile, int? responseID, }) { AppState _appState = getIt.get(); @@ -159,7 +161,7 @@ class RequestUtils { } request.deviceTypeID = request.searchType; - if (isExcludedUser) { + if (isFormFamilyFile) { //INFO: Only for Excluded User Family Member Addition request.isPatientExcluded = isExcludedUser; request.responseID = responseID; @@ -258,8 +260,8 @@ class RequestUtils { }; } - static dynamic getAddFamilyRequest({required String nationalIDorFile, required String mobileNo, required String countryCode}) { - var request = {}; + static Future getAddFamilyRequest({required String nationalIDorFile, required String mobileNo, required String countryCode}) async { + FamilyFileRequest request = FamilyFileRequest(); int? loginType = 0; // Default to National ID if (countryCode == CountryEnum.saudiArabia.countryCode || countryCode == '+966') { @@ -269,18 +271,18 @@ class RequestUtils { } if (loginType == 1) { - request["sharedPatientID"] = 0; - request["sharedPatientIdentificationID"] = nationalIDorFile; + request.sharedPatientId = 0; + request.sharedPatientIdentificationId = nationalIDorFile; } else if (loginType == 2) { - request["sharedPatientID"] = int.parse(nationalIDorFile); - request["sharedPatientIdentificationID"] = ''; + request.sharedPatientId = int.parse(nationalIDorFile); + request.sharedPatientIdentificationId = ''; } - request["searchType"] = loginType; - request["sharedPatientMobileNumber"] = mobileNo; - request["zipCode"] = countryCode; - request["isRegister"] = false; - request["patientStatus"] = 2; - request["isDentalAllowedBackend"] = false; + request.searchType = loginType; + request.sharedPatientMobileNumber = mobileNo; + request.zipCode = countryCode; + request.isRegister = false; + request.patientStatus = 2; + request.isDentalAllowedBackend = false; return request; } } diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 89a9404..8038c43 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -17,7 +17,7 @@ abstract class AuthenticationRepo { Future>> checkPatientAuthentication({required dynamic checkPatientAuthenticationReq}); - Future>> sendActivationCodeRepo({required dynamic sendActivationCodeReq, String? languageID, bool isRegister = false, bool isExcludedUser = false}); + Future>> sendActivationCodeRepo({required dynamic sendActivationCodeReq, String? languageID, bool isRegister = false, bool isFormFamilyFile = false}); Future>> checkActivationCodeRepo({required dynamic newRequest, required String? activationCode, required bool isRegister, bool isExcludedUser = false}); @@ -131,7 +131,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { required dynamic sendActivationCodeReq, String? languageID, bool isRegister = false, - bool isExcludedUser = false, + bool isFormFamilyFile = false, }) async { int isOutKsa = (sendActivationCodeReq.zipCode == '966' || sendActivationCodeReq.zipCode == '+966') ? 0 : 1; sendActivationCodeReq.patientOutSA = isOutKsa; @@ -142,7 +142,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { Failure? failure; await apiClient.post( - isExcludedUser + isFormFamilyFile ? ApiConsts.sendFamilyFileActivation : isRegister ? ApiConsts.sendActivationCodeRegister diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 7c1252c..558f02d 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -354,25 +354,26 @@ class AuthenticationViewModel extends ChangeNotifier { dynamic payload, bool isComingFromResendOTP = false, bool isExcludedUser = false, + bool isFormFamilyFile = false, int? responseID}) async { var request = RequestUtils.getCommonRequestSendActivationCode( - otpTypeEnum: otpTypeEnum, - mobileNumber: phoneNumber, - selectedLoginType: otpTypeEnum.toInt(), - zipCode: selectedCountrySignup.countryCode, - nationalId: int.parse(nationalIdOrFileNumber), - isFileNo: isForRegister ? isPatientHasFile(request: payload) : false, - patientId: 0, - isForRegister: isForRegister, - patientOutSA: isForRegister - ? isPatientOutsideSA(request: payload) - : selectedCountrySignup.countryCode == CountryEnum.saudiArabia - ? false - : true, - payload: payload, - isExcludedUser: isExcludedUser, - responseID: responseID, - ); + otpTypeEnum: otpTypeEnum, + mobileNumber: phoneNumber, + selectedLoginType: otpTypeEnum.toInt(), + zipCode: selectedCountrySignup.countryCode, + nationalId: int.parse(nationalIdOrFileNumber), + isFileNo: isForRegister ? isPatientHasFile(request: payload) : false, + patientId: 0, + isForRegister: isForRegister, + patientOutSA: isForRegister + ? isPatientOutsideSA(request: payload) + : selectedCountrySignup.countryCode == CountryEnum.saudiArabia + ? false + : true, + payload: payload, + isExcludedUser: isExcludedUser, + isFormFamilyFile: isFormFamilyFile, + responseID: responseID); // TODO: GET APP SMS SIGNATURE HERE request.sMSSignature = await getSignature(); @@ -382,7 +383,7 @@ class AuthenticationViewModel extends ChangeNotifier { } final resultEither = - await _authenticationRepo.sendActivationCodeRepo(sendActivationCodeReq: request, isRegister: checkIsUserComingForRegister(request: payload), languageID: 'er', isExcludedUser: isExcludedUser); + await _authenticationRepo.sendActivationCodeRepo(sendActivationCodeReq: request, isRegister: checkIsUserComingForRegister(request: payload), languageID: 'er', isFormFamilyFile: isFormFamilyFile); resultEither.fold( (failure) async => await _errorHandlerService.handleError(failure: failure), @@ -397,9 +398,10 @@ class AuthenticationViewModel extends ChangeNotifier { } else { if (apiResponse.data != null && apiResponse.data['isSMSSent'] == true) { LoaderBottomSheet.hideLoader(); - if (!isComingFromResendOTP) + if (!isComingFromResendOTP) { navigateToOTPScreen( otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber, isComingFromRegister: checkIsUserComingForRegister(request: payload), payload: payload, isExcludedUser: isExcludedUser); + } } else { // TODO: Handle isSMSSent false // navigateToOTPScreen(otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber); diff --git a/lib/features/common/models/family_file_request.dart b/lib/features/common/models/family_file_request.dart new file mode 100644 index 0000000..047b9af --- /dev/null +++ b/lib/features/common/models/family_file_request.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; + +class FamilyFileRequest { + int? sharedPatientId; + String? sharedPatientIdentificationId; + int? searchType; + String? sharedPatientMobileNumber; + String? zipCode; + bool? isRegister; + int? patientStatus; + bool? isDentalAllowedBackend; + bool? isPatientExcluded; + int? responseID; + + FamilyFileRequest({ + this.sharedPatientId, + this.sharedPatientIdentificationId, + this.searchType, + this.sharedPatientMobileNumber, + this.zipCode, + this.isRegister, + this.patientStatus, + this.isDentalAllowedBackend, + this.isPatientExcluded, + this.responseID, + }); + + factory FamilyFileRequest.fromRawJson(String str) => FamilyFileRequest.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory FamilyFileRequest.fromJson(Map json) => FamilyFileRequest( + sharedPatientId: json["sharedPatientID"], + sharedPatientIdentificationId: json["sharedPatientIdentificationID"], + searchType: json["searchType"], + sharedPatientMobileNumber: json["sharedPatientMobileNumber"], + zipCode: json["zipCode"], + isRegister: json["isRegister"], + patientStatus: json["patientStatus"], + isDentalAllowedBackend: json["isDentalAllowedBackend"], + isPatientExcluded: json["IsPatientExcluded"], + responseID: json["ReponseID"], + ); + + Map toJson() => { + "SharedPatientID": sharedPatientId, + "SharedPatientIdentificationID": sharedPatientIdentificationId, + "SearchType": searchType, + "SharedPatientMobileNumber": sharedPatientMobileNumber, + "zipCode": zipCode, + "isRegister": isRegister, + "PatientStatus": patientStatus, + "isDentalAllowedBackend": isDentalAllowedBackend, + "IsPatientExcluded": isPatientExcluded, + "ReponseID": responseID, + }; +} diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index 6b83f33..3042aba 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -26,7 +26,7 @@ abstract class MedicalFileRepo { Future>>> getPatientFamilyFiles(); - Future>>> addFamilyFile({required dynamic request}); + Future>> addFamilyFile({required dynamic request}); } class MedicalFileRepoImp implements MedicalFileRepo { @@ -313,9 +313,9 @@ class MedicalFileRepoImp implements MedicalFileRepo { } @override - Future>>> addFamilyFile({dynamic request}) async { + Future>> addFamilyFile({dynamic request}) async { try { - GenericApiModel>? apiResponse; + GenericApiModel? apiResponse; Failure? failure; await apiClient.post( ApiConsts.addFamilyFile, @@ -325,17 +325,12 @@ class MedicalFileRepoImp implements MedicalFileRepo { }, 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, - // ); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response["ShareFamilyFileObj"] ?? null, + ); } catch (e) { failure = DataParsingFailure(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 a724a10..1c3d353 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -6,6 +8,7 @@ import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; +import 'package:hmg_patient_app_new/features/common/models/family_file_request.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'; @@ -13,6 +16,7 @@ import 'package:hmg_patient_app_new/features/medical_file/models/patient_sicklea 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'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; class MedicalFileViewModel extends ChangeNotifier { int selectedTabIndex = 0; @@ -298,12 +302,34 @@ class MedicalFileViewModel extends ChangeNotifier { Future addFamilyFile({required OTPTypeEnum otpTypeEnum, required bool isExcludedUser}) async { AuthenticationViewModel authVM = getIt.get(); - final request = + NavigationService navigationService = getIt.get(); + FamilyFileRequest request = await RequestUtils.getAddFamilyRequest(nationalIDorFile: authVM.nationalIdController.text, mobileNo: authVM.phoneNumberController.text, countryCode: authVM.selectedCountrySignup.countryCode); - final resultEither = await medicalFileRepo.addFamilyFile(request: request); + final resultEither = await medicalFileRepo.addFamilyFile(request: request.toJson()); resultEither.fold((failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) async { - print(apiResponse); + if (apiResponse != null && apiResponse.data != null) { + request.isPatientExcluded = apiResponse.data["IsPatientExcluded"]; + request.responseID = apiResponse.data["ReponseID"]; + _dialogService.showExceptionBottomSheet( + message: apiResponse.data['Message'], + onOkPressed: () { + print("=================== On Press Ok =================="); + authVM.sendActivationCode( + otpTypeEnum: otpTypeEnum, + nationalIdOrFileNumber: request.sharedPatientIdentificationId!, + phoneNumber: request.sharedPatientMobileNumber!, + isForRegister: false, + isExcludedUser: apiResponse.data['IsPatientExcluded'], + responseID: apiResponse.data["ReponseID"], + isFormFamilyFile: true); + + // insertFamilyData(payload: apiResponse.data![0]['ShareFamilyFileObj'], isExcludedPatient: apiResponse.data![0]['ShareFamilyFileObj']['IsPatientExcluded']); + }, + onCancelPressed: () { + navigationService.pop(); + }); + } }); } } diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 9776e5b..3786382 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -143,6 +143,13 @@ class _MedicalFilePageState extends State { AppCustomChipWidget( icon: AppAssets.file_icon, labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}", + onChipTap: () { + navigationService.pushPage( + page: FamilyMedicalScreen( + profiles: medicalFileViewModel.patientFamilyFiles, + onSelect: (FamilyFileResponseModelLists p1) {}, + )); + }, ), AppCustomChipWidget( icon: AppAssets.checkmark_icon, diff --git a/lib/presentation/my_family/my_Family.dart b/lib/presentation/my_family/my_Family.dart index 4eefe7a..7ebd069 100644 --- a/lib/presentation/my_family/my_Family.dart +++ b/lib/presentation/my_family/my_Family.dart @@ -28,10 +28,10 @@ class FamilyMedicalScreen extends StatefulWidget { final Function(FamilyFileResponseModelLists) onSelect; const FamilyMedicalScreen({ - Key? key, + super.key, required this.profiles, required this.onSelect, - }) : super(key: key); + }); @override State createState() => _FamilyMedicalScreenState(); diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index b29f6e6..099062e 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -24,6 +24,7 @@ class AppCustomChipWidget extends StatelessWidget { this.deleteIconColor = AppColors.textColor, this.deleteIconHasColor = false, this.padding = EdgeInsets.zero, + this.onChipTap }); final String? labelText; @@ -40,74 +41,78 @@ class AppCustomChipWidget extends StatelessWidget { final bool deleteIconHasColor; final OutlinedBorder? shape; final EdgeInsets? padding; + final void Function()? onChipTap; @override Widget build(BuildContext context) { - return ChipTheme( - data: ChipThemeData( - padding: EdgeInsets.all(0.0), - shape: SmoothRectangleBorder( - side: BorderSide( - width: 0.0, - color: Colors.transparent, // Crucially, set color to transparent - style: BorderStyle.none, + return GestureDetector( + onTap: onChipTap, + child: ChipTheme( + data: ChipThemeData( + padding: EdgeInsets.all(0.0), + shape: SmoothRectangleBorder( + side: BorderSide( + width: 0.0, + color: Colors.transparent, // Crucially, set color to transparent + style: BorderStyle.none, + ), + borderRadius: BorderRadius.circular(10.0), // Apply a border radius of 16.0 ), - borderRadius: BorderRadius.circular(10.0), // Apply a border radius of 16.0 ), + child: icon.isNotEmpty + ? Chip( + avatar: icon.isNotEmpty + ? Utils.buildSvgWithAssets( + icon: icon, + width: iconSize.h, + height: iconSize.h, + iconColor: iconHasColor ? iconColor : null) + : SizedBox.shrink(), + label: richText ?? + labelText!.toText10( + weight: FontWeight.w500, + letterSpacing: -0.64, + color: textColor), + // padding: EdgeInsets.all(0.0), + padding: padding, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + labelPadding: EdgeInsets.only( + left: -4.h, + right: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), + backgroundColor: backgroundColor, + shape: shape, + deleteIcon: deleteIcon?.isNotEmpty == true + ? Utils.buildSvgWithAssets( + icon: deleteIcon!, + width: deleteIconSize!.width!.h, + height: deleteIconSize!.height.h, + iconColor: deleteIconHasColor ? deleteIconColor : null) + : null, + onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, + ) + : Chip( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + label: richText ?? + labelText!.toText10( + weight: FontWeight.w500, + letterSpacing: -0.64, + color: textColor), + padding: EdgeInsets.all(0.0), + backgroundColor: backgroundColor, + shape: shape, + labelPadding: EdgeInsets.only( + left: 8.h, + right: deleteIcon?.isNotEmpty == true ? -2.h : 8.h), + deleteIcon: deleteIcon?.isNotEmpty == true + ? Utils.buildSvgWithAssets( + icon: deleteIcon!, + width: deleteIconSize!.width.h, + height: deleteIconSize!.height.h, + iconColor: deleteIconHasColor ? deleteIconColor : null) + : null, + onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, + ), ), - child: icon.isNotEmpty - ? Chip( - avatar: icon.isNotEmpty - ? Utils.buildSvgWithAssets( - icon: icon, - width: iconSize.h, - height: iconSize.h, - iconColor: iconHasColor ? iconColor : null) - : SizedBox.shrink(), - label: richText ?? - labelText!.toText10( - weight: FontWeight.w500, - letterSpacing: -0.64, - color: textColor), - // padding: EdgeInsets.all(0.0), - padding: padding, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - labelPadding: EdgeInsets.only( - left: -4.h, - right: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), - backgroundColor: backgroundColor, - shape: shape, - deleteIcon: deleteIcon?.isNotEmpty == true - ? Utils.buildSvgWithAssets( - icon: deleteIcon!, - width: deleteIconSize!.width!.h, - height: deleteIconSize!.height.h, - iconColor: deleteIconHasColor ? deleteIconColor : null) - : null, - onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, - ) - : Chip( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - label: richText ?? - labelText!.toText10( - weight: FontWeight.w500, - letterSpacing: -0.64, - color: textColor), - padding: EdgeInsets.all(0.0), - backgroundColor: backgroundColor, - shape: shape, - labelPadding: EdgeInsets.only( - left: 8.h, - right: deleteIcon?.isNotEmpty == true ? -2.h : 8.h), - deleteIcon: deleteIcon?.isNotEmpty == true - ? Utils.buildSvgWithAssets( - icon: deleteIcon!, - width: deleteIconSize!.width.h, - height: deleteIconSize!.height.h, - iconColor: deleteIconHasColor ? deleteIconColor : null) - : null, - onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, - ), ); } } From 47ce5022d3f54eae135a280953d43e466a221ce8 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Wed, 24 Sep 2025 14:43:10 +0300 Subject: [PATCH 04/12] family screen & widgets --- .../authentication/authentication_repo.dart | 24 +++++- .../authentication_view_model.dart | 74 +++++++++++++------ .../widgets/otp_verification_screen.dart | 10 +-- .../medical_file/medical_file_view_model.dart | 5 +- lib/services/navigation_service.dart | 4 +- 5 files changed, 82 insertions(+), 35 deletions(-) diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 8038c43..a02ef70 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -3,6 +3,7 @@ import 'dart:async'; 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/app_state.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/common_models/privilege/PrivilegeModel.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; @@ -19,7 +20,8 @@ abstract class AuthenticationRepo { Future>> sendActivationCodeRepo({required dynamic sendActivationCodeReq, String? languageID, bool isRegister = false, bool isFormFamilyFile = false}); - Future>> checkActivationCodeRepo({required dynamic newRequest, required String? activationCode, required bool isRegister, bool isExcludedUser = false}); + Future>> checkActivationCodeRepo( + {required dynamic newRequest, required String? activationCode, required bool isRegister, bool isFormFamilyFile = false, int? patientShareRequestID, int? responseID}); Future>> checkIfUserAgreed({required dynamic commonAuthanticatedRequest}); @@ -178,7 +180,9 @@ class AuthenticationRepoImp implements AuthenticationRepo { required dynamic newRequest, // could be CheckActivationCodeReq or CheckActivationCodeRegisterReq required String? activationCode, required bool isRegister, - bool isExcludedUser = false, + bool isFormFamilyFile = false, + int? patientShareRequestID, + int? responseID, }) async { if (isRegister) { newRequest["activationCode"] = activationCode ?? "0000"; @@ -191,8 +195,16 @@ class AuthenticationRepoImp implements AuthenticationRepo { newRequest.forRegisteration = newRequest.isRegister ?? false; newRequest.isRegister = false; } + Map familyRequest = {}; + if (isFormFamilyFile) { + familyRequest = newRequest.toJson(); + familyRequest['PatientShareRequestID'] = patientShareRequestID; + familyRequest['ResponseID'] = responseID; + familyRequest['Status'] = 3; + familyRequest["PatientID"] = newRequest["PatientID"]; + } - final endpoint = isExcludedUser + final endpoint = isFormFamilyFile ? ApiConsts.checkActivationCodeForFamily : isRegister ? ApiConsts.checkActivationCodeRegister @@ -204,7 +216,11 @@ class AuthenticationRepoImp implements AuthenticationRepo { await apiClient.post( endpoint, - body: isRegister ? newRequest : newRequest.toJson(), + body: isFormFamilyFile + ? familyRequest + : isRegister + ? newRequest + : newRequest.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 558f02d..a108835 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -382,12 +382,16 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setUserRegistrationPayload = RegistrationDataModelPayload.fromJson(payload); } - final resultEither = - await _authenticationRepo.sendActivationCodeRepo(sendActivationCodeReq: request, isRegister: checkIsUserComingForRegister(request: payload), languageID: 'er', isFormFamilyFile: isFormFamilyFile); + final resultEither = await _authenticationRepo.sendActivationCodeRepo( + sendActivationCodeReq: request, isRegister: checkIsUserComingForRegister(request: payload), languageID: 'er', isFormFamilyFile: isFormFamilyFile); resultEither.fold( (failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + int? patientShareRequestID = 0; + if (isFormFamilyFile) { + patientShareRequestID = apiResponse.data['PatientShareRequestID']; + } if (apiResponse.messageStatus == 2) { LoaderBottomSheet.hideLoader(); await _dialogService.showCommonBottomSheetWithoutH( @@ -400,7 +404,15 @@ class AuthenticationViewModel extends ChangeNotifier { LoaderBottomSheet.hideLoader(); if (!isComingFromResendOTP) { navigateToOTPScreen( - otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber, isComingFromRegister: checkIsUserComingForRegister(request: payload), payload: payload, isExcludedUser: isExcludedUser); + otpTypeEnum: otpTypeEnum, + phoneNumber: phoneNumber, + isComingFromRegister: checkIsUserComingForRegister(request: payload), + payload: payload, + isFormFamilyFile: isFormFamilyFile, + isExcludedUser: isExcludedUser, + responseID: responseID, + patientShareRequestID: patientShareRequestID, + ); } } else { // TODO: Handle isSMSSent false @@ -424,8 +436,8 @@ class AuthenticationViewModel extends ChangeNotifier { required OTPTypeEnum otpTypeEnum, required Function(String? message) onWrongActivationCode, Function()? onResendActivation, - bool isExcludedUser = false, - dynamic requestID, + bool isFormFamilyFile = false, + dynamic patientShareRequestID, dynamic responseID}) async { bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1); @@ -453,12 +465,6 @@ class AuthenticationViewModel extends ChangeNotifier { loginType: loginTypeEnum.toInt) .toJson(); - if (isExcludedUser) { - request['PatientShareRequestID'] = requestID; - request['ResponseID'] = responseID; - request['Status'] = 3; - } - LoaderBottomSheet.showLoader(); if (isForRegister) { if (_appState.getUserRegistrationPayload.patientOutSa == 0) request['DOB'] = _appState.getUserRegistrationPayload.dob; @@ -468,7 +474,11 @@ class AuthenticationViewModel extends ChangeNotifier { request["ForRegisteration"] = _appState.getUserRegistrationPayload.isRegister; request["isRegister"] = false; - final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: request, activationCode: activationCode.toString(), isRegister: true); + final resultEither = await _authenticationRepo.checkActivationCodeRepo( + newRequest: request, + activationCode: activationCode.toString(), + isRegister: true, + ); LoaderBottomSheet.hideLoader(); @@ -495,7 +505,12 @@ class AuthenticationViewModel extends ChangeNotifier { }); } else { final resultEither = await _authenticationRepo.checkActivationCodeRepo( - newRequest: CheckActivationCodeRegisterReq.fromJson(request), activationCode: activationCode, isRegister: false, isExcludedUser: isExcludedUser); + newRequest: CheckActivationCodeRegisterReq.fromJson(request), + activationCode: activationCode, + isRegister: false, + isFormFamilyFile: isFormFamilyFile, + patientShareRequestID: patientShareRequestID, + responseID: responseID); resultEither.fold( (failure) async => await _errorHandlerService.handleError( @@ -509,6 +524,8 @@ class AuthenticationViewModel extends ChangeNotifier { LoaderBottomSheet.hideLoader(); await _dialogService.showCommonBottomSheetWithoutH(message: failure.message, label: LocaleKeys.notice.tr(), onOkPressed: () {}); }), (apiResponse) async { + print("API Response Data: ${apiResponse.data}"); + final activation = CheckActivationCode.fromJson(apiResponse.data as Map); if (activation.errorCode == '699') { @@ -605,14 +622,25 @@ class AuthenticationViewModel extends ChangeNotifier { _navigationService.pushAndReplace(AppRoutes.landingScreen); } - Future navigateToOTPScreen({required OTPTypeEnum otpTypeEnum, required String phoneNumber, required bool isComingFromRegister, dynamic payload, bool isExcludedUser = false}) async { + Future navigateToOTPScreen( + {required OTPTypeEnum otpTypeEnum, + required String phoneNumber, + required bool isComingFromRegister, + dynamic payload, + bool isFormFamilyFile = false, + bool isExcludedUser = false, + int? responseID, + int? patientShareRequestID}) async { _navigationService.pushToOtpScreen( phoneNumber: phoneNumber, + isFormFamilyFile: isFormFamilyFile, checkActivationCode: (int activationCode) async { await checkActivationCode( activationCode: activationCode.toString(), - isExcludedUser: isExcludedUser, + isFormFamilyFile: isFormFamilyFile, otpTypeEnum: otpTypeEnum, + responseID: responseID, + patientShareRequestID: patientShareRequestID, onWrongActivationCode: (String? value) { onWrongActivationCode(message: value); }, @@ -620,12 +648,16 @@ class AuthenticationViewModel extends ChangeNotifier { }, onResendOTPPressed: (String phoneNumber) async { await sendActivationCode( - otpTypeEnum: otpTypeEnum, - phoneNumber: phoneNumberController.text, - nationalIdOrFileNumber: nationalIdController.text, - isForRegister: isComingFromRegister, - isComingFromResendOTP: true, - payload: payload); + otpTypeEnum: otpTypeEnum, + phoneNumber: phoneNumberController.text, + nationalIdOrFileNumber: nationalIdController.text, + isForRegister: isComingFromRegister, + isComingFromResendOTP: true, + payload: payload, + isFormFamilyFile: isFormFamilyFile, + isExcludedUser: isExcludedUser, + responseID: responseID, + ); }, ); } diff --git a/lib/features/authentication/widgets/otp_verification_screen.dart b/lib/features/authentication/widgets/otp_verification_screen.dart index e7f8d72..10e34cd 100644 --- a/lib/features/authentication/widgets/otp_verification_screen.dart +++ b/lib/features/authentication/widgets/otp_verification_screen.dart @@ -428,13 +428,9 @@ class OTPVerificationScreen extends StatefulWidget { final String phoneNumber; final Function(int code) checkActivationCode; final Function(String phoneNumber) onResendOTPPressed; + final bool isFormFamilyFile; - const OTPVerificationScreen({ - super.key, - required this.phoneNumber, - required this.checkActivationCode, - required this.onResendOTPPressed, - }); + const OTPVerificationScreen({super.key, required this.phoneNumber, required this.checkActivationCode, required this.onResendOTPPressed, required this.isFormFamilyFile}); @override State createState() => _OTPVerificationScreenState(); @@ -559,7 +555,7 @@ class _OTPVerificationScreenState extends State { LocaleKeys.weHaveSendOTP.tr().toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), _getMaskedPhoneNumber().toText15(color: AppColors.inputLabelTextColor, isBold: true), LocaleKeys.via.tr().toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), - authVM.loginTypeEnum.displayName.toText15(color: AppColors.inputLabelTextColor, isBold: true, letterSpacing: -0.4), + (widget.isFormFamilyFile ? LoginTypeEnum.sms.displayName : authVM.loginTypeEnum.displayName).toText15(color: AppColors.inputLabelTextColor, isBold: true, letterSpacing: -0.4), appState.getUserRegistrationPayload.isRegister != null && appState.getUserRegistrationPayload.isRegister == true ? LocaleKeys.forRegistrationVerification.tr().toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4) : LocaleKeys.forLoginVerification.tr().toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 1c3d353..b3b1db1 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; class MedicalFileViewModel extends ChangeNotifier { int selectedTabIndex = 0; @@ -301,6 +302,7 @@ class MedicalFileViewModel extends ChangeNotifier { } Future addFamilyFile({required OTPTypeEnum otpTypeEnum, required bool isExcludedUser}) async { + LoaderBottomSheet.showLoader(); AuthenticationViewModel authVM = getIt.get(); NavigationService navigationService = getIt.get(); FamilyFileRequest request = @@ -311,9 +313,11 @@ class MedicalFileViewModel extends ChangeNotifier { if (apiResponse != null && apiResponse.data != null) { request.isPatientExcluded = apiResponse.data["IsPatientExcluded"]; request.responseID = apiResponse.data["ReponseID"]; + LoaderBottomSheet.hideLoader(); _dialogService.showExceptionBottomSheet( message: apiResponse.data['Message'], onOkPressed: () { + LoaderBottomSheet.showLoader(); print("=================== On Press Ok =================="); authVM.sendActivationCode( otpTypeEnum: otpTypeEnum, @@ -324,7 +328,6 @@ class MedicalFileViewModel extends ChangeNotifier { responseID: apiResponse.data["ReponseID"], isFormFamilyFile: true); - // insertFamilyData(payload: apiResponse.data![0]['ShareFamilyFileObj'], isExcludedPatient: apiResponse.data![0]['ShareFamilyFileObj']['IsPatientExcluded']); }, onCancelPressed: () { navigationService.pop(); diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index 420c4cf..0c2a901 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -22,9 +22,9 @@ class NavigationService { navigatorKey.currentState?.pushReplacementNamed(routeName); } - Future pushToOtpScreen({required String phoneNumber, required Function(int code) checkActivationCode, required Function(String phoneNumber) onResendOTPPressed}) { + Future pushToOtpScreen({required String phoneNumber, required Function(int code) checkActivationCode, required Function(String phoneNumber) onResendOTPPressed, bool isFormFamilyFile = false}) { return navigatorKey.currentState!.push( - MaterialPageRoute(builder: (_) => OTPVerificationScreen(phoneNumber: phoneNumber, checkActivationCode: checkActivationCode, onResendOTPPressed: onResendOTPPressed)), + MaterialPageRoute(builder: (_) => OTPVerificationScreen(phoneNumber: phoneNumber, checkActivationCode: checkActivationCode, onResendOTPPressed: onResendOTPPressed, isFormFamilyFile : isFormFamilyFile)), ); } From 3acd549de7ba1c42a37332914a5448e6cb4a055d Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Wed, 24 Sep 2025 15:07:36 +0300 Subject: [PATCH 05/12] no message --- .../medical_file/medical_file_view_model.dart | 40 ++++--------------- lib/presentation/my_family/my_Family.dart | 5 ++- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 1c3d353..cb21b90 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -45,7 +45,7 @@ class MedicalFileViewModel extends ChangeNotifier { int selectedMedicalReportsTabIndex = 0; static final DialogService _dialogService = getIt.get(); AppState _appState = getIt(); - + AuthenticationViewModel authVM = getIt.get(); MedicalFileViewModel({required this.medicalFileRepo, required this.errorHandlerService}); initMedicalFileProvider() { @@ -267,41 +267,15 @@ class MedicalFileViewModel extends ChangeNotifier { ); } - Future switchFamilyFiles({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await medicalFileRepo.getPatientFamilyFiles(); + Future switchFamilyFiles( {Function(dynamic)? onSuccess,int? responseID,int? patientID, String? phoneNumber, Function(String)? onError}) async { + authVM.phoneNumberController.text = phoneNumber!; + + await authVM.checkActivationCode(activationCode: '0000', otpTypeEnum: OTPTypeEnum.sms, onWrongActivationCode: (String? str) {}, responseID: responseID, requestID: patientID, isExcludedUser: true); - 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({required OTPTypeEnum otpTypeEnum, required bool isExcludedUser}) async { - AuthenticationViewModel authVM = getIt.get(); + NavigationService navigationService = getIt.get(); FamilyFileRequest request = await RequestUtils.getAddFamilyRequest(nationalIDorFile: authVM.nationalIdController.text, mobileNo: authVM.phoneNumberController.text, countryCode: authVM.selectedCountrySignup.countryCode); diff --git a/lib/presentation/my_family/my_Family.dart b/lib/presentation/my_family/my_Family.dart index 7ebd069..25fe55c 100644 --- a/lib/presentation/my_family/my_Family.dart +++ b/lib/presentation/my_family/my_Family.dart @@ -70,7 +70,10 @@ class _FamilyMedicalScreenState extends State { onTabChange: (int index) {}, ), SizedBox(height: 25.h), - FamilyCards(profiles: widget.profiles, onSelect: widget.onSelect, isShowDetails: true), + FamilyCards(profiles: widget.profiles, onSelect: (FamilyFileResponseModelLists profile){ + medicalVM?.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + + }, isShowDetails: true), SizedBox(height: 20.h), ], ), From 9e8011c34c58100f82657328d9502f8621a3c210 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Wed, 24 Sep 2025 15:26:15 +0300 Subject: [PATCH 06/12] family screen & widgets --- lib/extensions/string_extensions.dart | 5 ++- .../authentication/authentication_repo.dart | 23 +++++----- .../authentication_view_model.dart | 43 +++++++++++++------ lib/routes/app_routes.dart | 3 ++ 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 5465ce3..4432737 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -1,4 +1,6 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:auto_size_text/auto_size_text.dart'; @@ -365,7 +367,8 @@ extension DynamicTextStyleExtension on BuildContext { TextBaseline? textBaseline, FontStyle? fontStyle, bool isLanguageSwitcher = false}) { - final family = FontUtils.getFontFamilyForLanguage(true); + AppState appState = getIt.get(); + final family = appState.getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'; return TextStyle( fontFamily: family, fontSize: fontSize, diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index a02ef70..c2a49bf 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/common_models/privilege/PrivilegeModel.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_activation_code_register_request_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; @@ -21,7 +22,7 @@ abstract class AuthenticationRepo { Future>> sendActivationCodeRepo({required dynamic sendActivationCodeReq, String? languageID, bool isRegister = false, bool isFormFamilyFile = false}); Future>> checkActivationCodeRepo( - {required dynamic newRequest, required String? activationCode, required bool isRegister, bool isFormFamilyFile = false, int? patientShareRequestID, int? responseID}); + {required dynamic newRequest, required String? activationCode, required bool isRegister, bool isFormFamilyFile = false, int? patientShareRequestID, int? responseID, String? familyFileTokenID}); Future>> checkIfUserAgreed({required dynamic commonAuthanticatedRequest}); @@ -176,14 +177,14 @@ class AuthenticationRepoImp implements AuthenticationRepo { } @override - Future>> checkActivationCodeRepo({ - required dynamic newRequest, // could be CheckActivationCodeReq or CheckActivationCodeRegisterReq - required String? activationCode, - required bool isRegister, - bool isFormFamilyFile = false, - int? patientShareRequestID, - int? responseID, - }) async { + Future>> checkActivationCodeRepo( + {required dynamic newRequest, // could be CheckActivationCodeReq or CheckActivationCodeRegisterReq + required String? activationCode, + required bool isRegister, + bool isFormFamilyFile = false, + int? patientShareRequestID, + int? responseID, + String? familyFileTokenID}) async { if (isRegister) { newRequest["activationCode"] = activationCode ?? "0000"; newRequest["isSilentLogin"] = activationCode != null ? false : true; @@ -197,11 +198,13 @@ class AuthenticationRepoImp implements AuthenticationRepo { } Map familyRequest = {}; if (isFormFamilyFile) { + AppState appState = getIt.get(); familyRequest = newRequest.toJson(); familyRequest['PatientShareRequestID'] = patientShareRequestID; familyRequest['ResponseID'] = responseID; familyRequest['Status'] = 3; - familyRequest["PatientID"] = newRequest["PatientID"]; + familyRequest["PatientID"] = appState.getAuthenticatedUser()!.patientId ?? 0; + familyRequest["LogInTokenID"] = familyFileTokenID; } final endpoint = isFormFamilyFile diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index a108835..8db44ab 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/features/authentication/models/resp_models/c import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; import 'package:hmg_patient_app_new/presentation/authentication/saved_login_screen.dart'; @@ -389,8 +390,10 @@ class AuthenticationViewModel extends ChangeNotifier { (failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { int? patientShareRequestID = 0; + String? familyFileTokenID; if (isFormFamilyFile) { patientShareRequestID = apiResponse.data['PatientShareRequestID']; + familyFileTokenID = apiResponse.data['LogInTokenID']; } if (apiResponse.messageStatus == 2) { LoaderBottomSheet.hideLoader(); @@ -404,15 +407,15 @@ class AuthenticationViewModel extends ChangeNotifier { LoaderBottomSheet.hideLoader(); if (!isComingFromResendOTP) { navigateToOTPScreen( - otpTypeEnum: otpTypeEnum, - phoneNumber: phoneNumber, - isComingFromRegister: checkIsUserComingForRegister(request: payload), - payload: payload, - isFormFamilyFile: isFormFamilyFile, - isExcludedUser: isExcludedUser, - responseID: responseID, - patientShareRequestID: patientShareRequestID, - ); + otpTypeEnum: otpTypeEnum, + phoneNumber: phoneNumber, + isComingFromRegister: checkIsUserComingForRegister(request: payload), + payload: payload, + isFormFamilyFile: isFormFamilyFile, + isExcludedUser: isExcludedUser, + responseID: responseID, + patientShareRequestID: patientShareRequestID, + familyFileTokenID: familyFileTokenID); } } else { // TODO: Handle isSMSSent false @@ -438,7 +441,8 @@ class AuthenticationViewModel extends ChangeNotifier { Function()? onResendActivation, bool isFormFamilyFile = false, dynamic patientShareRequestID, - dynamic responseID}) async { + dynamic responseID, + String? familyFileTokenID}) async { bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1); final request = RequestUtils.getCommonRequestWelcome( @@ -510,7 +514,8 @@ class AuthenticationViewModel extends ChangeNotifier { isRegister: false, isFormFamilyFile: isFormFamilyFile, patientShareRequestID: patientShareRequestID, - responseID: responseID); + responseID: responseID, + familyFileTokenID: familyFileTokenID); resultEither.fold( (failure) async => await _errorHandlerService.handleError( @@ -543,6 +548,18 @@ class AuthenticationViewModel extends ChangeNotifier { // Navigator.popUntil(context, (route) => Utils.route(route, equalsTo: RegisterNew)); return; } else { + if (isFormFamilyFile) { + _dialogService.showCommonBottomSheetWithoutH( + message: "Family File Added Successfully", + onOkPressed: () { + LoaderBottomSheet.showLoader(); + MedicalFileViewModel medicalFileVM = GetIt.instance(); + medicalFileVM.getFamilyFiles(); + LoaderBottomSheet.hideLoader(); + _navigationService.popUntilNamed(AppRoutes.medicalFilePage); + }); + } + if (activation.list != null && activation.list!.isNotEmpty) { _appState.setAuthenticatedUser(activation.list!.first); _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); @@ -630,7 +647,8 @@ class AuthenticationViewModel extends ChangeNotifier { bool isFormFamilyFile = false, bool isExcludedUser = false, int? responseID, - int? patientShareRequestID}) async { + int? patientShareRequestID, + String? familyFileTokenID}) async { _navigationService.pushToOtpScreen( phoneNumber: phoneNumber, isFormFamilyFile: isFormFamilyFile, @@ -641,6 +659,7 @@ class AuthenticationViewModel extends ChangeNotifier { otpTypeEnum: otpTypeEnum, responseID: responseID, patientShareRequestID: patientShareRequestID, + familyFileTokenID: familyFileTokenID, onWrongActivationCode: (String? value) { onWrongActivationCode(message: value); }, diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 67bf208..5a93216 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -4,6 +4,7 @@ import 'package:hmg_patient_app_new/presentation/authentication/register.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register_step2.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/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; class AppRoutes { @@ -12,6 +13,7 @@ class AppRoutes { static const String register = '/register'; static const String registerStepTwo = '/registerStepTwo'; static const String landingScreen = '/landingScreen'; + static const String medicalFilePage = '/medicalFilePage'; static Map get routes => { initialRoute: (context) => SplashPage(), @@ -19,5 +21,6 @@ class AppRoutes { landingScreen: (context) => LandingNavigation(), register: (context) => RegisterNew(), registerStepTwo: (context) => RegisterNewStep2(), + medicalFilePage: (context) => MedicalFilePage(), }; } From e2188b5a6931b38961d1aa1aeff89ae76c588e1f Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Thu, 25 Sep 2025 15:24:15 +0300 Subject: [PATCH 07/12] switch family member with super user --- .../authentication/authentication_repo.dart | 16 +++++++++++++--- .../authentication_view_model.dart | 8 +++++--- .../medical_file/medical_file_view_model.dart | 4 +++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 7fe9297..2ed43d8 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -28,7 +28,9 @@ abstract class AuthenticationRepo { int? patientShareRequestID, int? responseID, bool isSwitchUser = false, - int? patientID}); + int? patientID, + int? loginType + }); Future>> checkIfUserAgreed({required dynamic commonAuthanticatedRequest}); @@ -192,6 +194,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { int? responseID, bool isSwitchUser = false, int? patientID, + int? loginType }) async { if (isRegister) { newRequest["activationCode"] = activationCode ?? "0000"; @@ -215,12 +218,19 @@ class AuthenticationRepoImp implements AuthenticationRepo { Map switchRequest = {}; if (isSwitchUser) { switchRequest = newRequest.toJson(); - switchRequest['SuperUser'] = patientID; + switchRequest['PatientID'] = responseID; switchRequest['IsSilentLogin'] = true; switchRequest['LogInTokenID'] = null; - switchRequest['DeviceToken'] = null; switchRequest['SearchType'] = 2; + if(loginType != 0) { + switchRequest['SuperUser'] = patientID; + switchRequest['DeviceToken'] = null; + }else{ + switchRequest['LoginType'] = 2; + } + + } final endpoint = isFormFamilyFile diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index d42c8cf..8cb5eed 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -247,7 +247,7 @@ class AuthenticationViewModel extends ChangeNotifier { await selectDeviceImei(onSuccess: (dynamic respData) async { try { if (respData != null) { - dynamic data = SelectDeviceByImeiRespModelElement.fromJson(respData.toJson()); + dynamic data = await SelectDeviceByImeiRespModelElement.fromJson(respData.toJson()); _appState.setSelectDeviceByImeiRespModelElement(data); LoaderBottomSheet.hideLoader(); @@ -440,7 +440,8 @@ class AuthenticationViewModel extends ChangeNotifier { dynamic patientShareRequestID, dynamic responseID, bool isSwitchUser =false, - int? patientID + int? patientID, + }) async { bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1); @@ -515,7 +516,8 @@ class AuthenticationViewModel extends ChangeNotifier { patientShareRequestID: patientShareRequestID, responseID: responseID, isSwitchUser: isSwitchUser, - patientID: patientID + patientID: patientID, + loginType: _appState.superUserID != null ? 0 : 2, ); resultEither.fold( diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 51836a6..aa77700 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -275,7 +275,9 @@ class MedicalFileViewModel extends ChangeNotifier { ? phoneNumber.replaceFirst("0", "") : phoneNumber; - await authVM.checkActivationCode(activationCode: '0000', otpTypeEnum: OTPTypeEnum.sms, onWrongActivationCode: (String? str) {}, responseID: responseID, isFormFamilyFile:false, isSwitchUser: true, patientID: patientID); + + + await authVM.checkActivationCode(activationCode: '0000', otpTypeEnum: OTPTypeEnum.sms, onWrongActivationCode: (String? str) {}, responseID: responseID, isFormFamilyFile:false, isSwitchUser: true, patientID: patientID, ); } From 02433e1bef3e67bd92c0978d0358034302370f88 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Sun, 28 Sep 2025 09:31:49 +0300 Subject: [PATCH 08/12] family screen & widgets --- lib/core/api_consts.dart | 1 + lib/core/app_state.dart | 10 ++ .../authentication/authentication_repo.dart | 36 ++++- .../authentication_view_model.dart | 135 +++++++++--------- .../medical_file/medical_file_repo.dart | 42 ++++++ .../medical_file/medical_file_view_model.dart | 25 +++- lib/presentation/my_family/my_Family.dart | 2 +- 7 files changed, 173 insertions(+), 78 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 9b013a5..e11de15 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -810,6 +810,7 @@ class ApiConsts { static final String addFamilyFile = 'Services/Patients.svc/REST/ShareFamilyFileService'; static final String sendFamilyFileActivation = 'Services/Authentication.svc/REST/SendActivationCodeForFamilyFile'; static final String checkActivationCodeForFamily = 'Services/Authentication.svc/REST/CheckActivationCodeForFamilyFile'; + static final String getAllPendingRecordsByResponseId = 'Services/Authentication.svc/REST/GetAllPendingRecordsByResponseId'; // static values for Api diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index c906374..3e0bbc1 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -97,6 +97,16 @@ class AppState { set setDeviceTypeID(v) => deviceTypeID = v; + + + String _familyFileTokenID = ""; + + String get getFamilyFileTokenID => _familyFileTokenID; + + set setFamilyFileTokenID(String value) { + _familyFileTokenID = value; + } + List vidaPlusProjectList = []; List privilegeModelList = []; List hMCProjectListModel = []; diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index c2a49bf..3ed05c6 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -22,7 +22,7 @@ abstract class AuthenticationRepo { Future>> sendActivationCodeRepo({required dynamic sendActivationCodeReq, String? languageID, bool isRegister = false, bool isFormFamilyFile = false}); Future>> checkActivationCodeRepo( - {required dynamic newRequest, required String? activationCode, required bool isRegister, bool isFormFamilyFile = false, int? patientShareRequestID, int? responseID, String? familyFileTokenID}); + {required dynamic newRequest, required String? activationCode, required bool isRegister, bool isFormFamilyFile = false, int? patientShareRequestID, int? responseID}); Future>> checkIfUserAgreed({required dynamic commonAuthanticatedRequest}); @@ -139,6 +139,16 @@ class AuthenticationRepoImp implements AuthenticationRepo { int isOutKsa = (sendActivationCodeReq.zipCode == '966' || sendActivationCodeReq.zipCode == '+966') ? 0 : 1; sendActivationCodeReq.patientOutSA = isOutKsa; sendActivationCodeReq.isDentalAllowedBackend = false; + final payload = sendActivationCodeReq.toJson(); + if (isFormFamilyFile) { + payload.remove("MobileNo"); + payload.remove("NationalID"); + payload.remove("SMSSignature"); + payload.remove("ResponseID"); + print("=================== Final Payload ==================="); + print(payload); + print("====================================================="); + } try { GenericApiModel? apiResponse; @@ -150,7 +160,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { : isRegister ? ApiConsts.sendActivationCodeRegister : ApiConsts.sendActivationCode, - body: sendActivationCodeReq.toJson(), + body: isFormFamilyFile ? payload : sendActivationCodeReq.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, @@ -183,8 +193,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { required bool isRegister, bool isFormFamilyFile = false, int? patientShareRequestID, - int? responseID, - String? familyFileTokenID}) async { + int? responseID}) async { if (isRegister) { newRequest["activationCode"] = activationCode ?? "0000"; newRequest["isSilentLogin"] = activationCode != null ? false : true; @@ -199,12 +208,27 @@ class AuthenticationRepoImp implements AuthenticationRepo { Map familyRequest = {}; if (isFormFamilyFile) { AppState appState = getIt.get(); - familyRequest = newRequest.toJson(); + familyRequest = {}; familyRequest['PatientShareRequestID'] = patientShareRequestID; familyRequest['ResponseID'] = responseID; familyRequest['Status'] = 3; familyRequest["PatientID"] = appState.getAuthenticatedUser()!.patientId ?? 0; - familyRequest["LogInTokenID"] = familyFileTokenID; + familyRequest["LogInTokenID"] = appState.getFamilyFileTokenID; + + // // Remove unnecessary keys from familyRequest + // familyRequest.remove("MobileNo"); + // familyRequest.remove("DeviceToken"); + // familyRequest.remove("ProjectOutSA"); + // familyRequest.remove("LoginType"); + // familyRequest.remove("ZipCode"); + // familyRequest.remove("isRegister"); + // familyRequest.remove("SearchType"); + // familyRequest.remove("NationalID"); + // familyRequest.remove("IsSilentLogin"); + // familyRequest.remove("isDentalAllowedBackend"); + // familyRequest.remove("ForRegisteration"); + + } final endpoint = isFormFamilyFile diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 8db44ab..f314cc4 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -376,6 +376,9 @@ class AuthenticationViewModel extends ChangeNotifier { isFormFamilyFile: isFormFamilyFile, responseID: responseID); + + + // TODO: GET APP SMS SIGNATURE HERE request.sMSSignature = await getSignature(); @@ -390,10 +393,9 @@ class AuthenticationViewModel extends ChangeNotifier { (failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { int? patientShareRequestID = 0; - String? familyFileTokenID; if (isFormFamilyFile) { patientShareRequestID = apiResponse.data['PatientShareRequestID']; - familyFileTokenID = apiResponse.data['LogInTokenID']; + _appState.setFamilyFileTokenID = apiResponse.data['LogInTokenID']; } if (apiResponse.messageStatus == 2) { LoaderBottomSheet.hideLoader(); @@ -407,15 +409,15 @@ class AuthenticationViewModel extends ChangeNotifier { LoaderBottomSheet.hideLoader(); if (!isComingFromResendOTP) { navigateToOTPScreen( - otpTypeEnum: otpTypeEnum, - phoneNumber: phoneNumber, - isComingFromRegister: checkIsUserComingForRegister(request: payload), - payload: payload, - isFormFamilyFile: isFormFamilyFile, - isExcludedUser: isExcludedUser, - responseID: responseID, - patientShareRequestID: patientShareRequestID, - familyFileTokenID: familyFileTokenID); + otpTypeEnum: otpTypeEnum, + phoneNumber: phoneNumber, + isComingFromRegister: checkIsUserComingForRegister(request: payload), + payload: payload, + isFormFamilyFile: isFormFamilyFile, + isExcludedUser: isExcludedUser, + responseID: responseID, + patientShareRequestID: patientShareRequestID, + ); } } else { // TODO: Handle isSMSSent false @@ -441,8 +443,7 @@ class AuthenticationViewModel extends ChangeNotifier { Function()? onResendActivation, bool isFormFamilyFile = false, dynamic patientShareRequestID, - dynamic responseID, - String? familyFileTokenID}) async { + dynamic responseID}) async { bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1); final request = RequestUtils.getCommonRequestWelcome( @@ -478,11 +479,7 @@ class AuthenticationViewModel extends ChangeNotifier { request["ForRegisteration"] = _appState.getUserRegistrationPayload.isRegister; request["isRegister"] = false; - final resultEither = await _authenticationRepo.checkActivationCodeRepo( - newRequest: request, - activationCode: activationCode.toString(), - isRegister: true, - ); + final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: request, activationCode: activationCode.toString(), isRegister: true); LoaderBottomSheet.hideLoader(); @@ -514,8 +511,7 @@ class AuthenticationViewModel extends ChangeNotifier { isRegister: false, isFormFamilyFile: isFormFamilyFile, patientShareRequestID: patientShareRequestID, - responseID: responseID, - familyFileTokenID: familyFileTokenID); + responseID: responseID); resultEither.fold( (failure) async => await _errorHandlerService.handleError( @@ -549,59 +545,62 @@ class AuthenticationViewModel extends ChangeNotifier { return; } else { if (isFormFamilyFile) { - _dialogService.showCommonBottomSheetWithoutH( - message: "Family File Added Successfully", - onOkPressed: () { - LoaderBottomSheet.showLoader(); - MedicalFileViewModel medicalFileVM = GetIt.instance(); - medicalFileVM.getFamilyFiles(); - LoaderBottomSheet.hideLoader(); - _navigationService.popUntilNamed(AppRoutes.medicalFilePage); - }); - } - - if (activation.list != null && activation.list!.isNotEmpty) { - _appState.setAuthenticatedUser(activation.list!.first); - _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); - } - _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); - _appState.setAppAuthToken = activation.authenticationTokenId; - final request = RequestUtils.getAuthanticatedCommonRequest().toJson(); - bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); - - //updating the last login type in app state to show the fingerprint/face id option on home screen - if (_appState.getSelectDeviceByImeiRespModelElement != null) { - _appState.getSelectDeviceByImeiRespModelElement!.logInType = loginTypeEnum.toInt; - } - LoaderBottomSheet.hideLoader(); - insertPatientIMEIData(loginTypeEnum.toInt); - await clearDefaultInputValues(); - if (isUserAgreedBefore) { - navigateToHomeScreen(); + await navigateToFamilyFilePage(); + // _dialogService.showCommonBottomSheetWithoutH( + // message: "Family File Added Successfully", + // onOkPressed: () async { + // print("navigating to family file page"); + // + // }); } else { - navigateToHomeScreen(); - //Agreement page not designed yet so we are navigating to home screen directly - // getUserAgreementContent(request: request); + if (activation.list != null && activation.list!.isNotEmpty) { + _appState.setAuthenticatedUser(activation.list!.first); + _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); + } + _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); + _appState.setAppAuthToken = activation.authenticationTokenId; + final request = RequestUtils.getAuthanticatedCommonRequest().toJson(); + bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); + + //updating the last login type in app state to show the fingerprint/face id option on home screen + if (_appState.getSelectDeviceByImeiRespModelElement != null) { + _appState.getSelectDeviceByImeiRespModelElement!.logInType = loginTypeEnum.toInt; + } + LoaderBottomSheet.hideLoader(); + insertPatientIMEIData(loginTypeEnum.toInt); + await clearDefaultInputValues(); + if (isUserAgreedBefore) { + navigateToHomeScreen(); + } else { + navigateToHomeScreen(); + //Agreement page not designed yet so we are navigating to home screen directly + // getUserAgreementContent(request: request); + } + // TODO: setPreferences and stuff + // sharedPref.remove(FAMILY_FILE); + // activation.list!.isFamily = false; + // userData = activation.list; + // sharedPref.setString(BLOOD_TYPE, activation.patientBloodType ?? ""); + // authenticatedUserObject.user = activation.list!; + // projectViewModel.setPrivilege(privilegeList: res); + // await sharedPref.setObject(MAIN_USER, activation.list); + // await sharedPref.setObject(USER_PROFILE, activation.list); + // loginTokenID = activation.logInTokenID; + // await sharedPref.setObject(LOGIN_TOKEN_ID, activation.logInTokenID); + // await sharedPref.setString(TOKEN, activation.authenticationTokenID!); + + // projectViewModel.analytics.loginRegistration.login_successful(); } - // TODO: setPreferences and stuff - // sharedPref.remove(FAMILY_FILE); - // activation.list!.isFamily = false; - // userData = activation.list; - // sharedPref.setString(BLOOD_TYPE, activation.patientBloodType ?? ""); - // authenticatedUserObject.user = activation.list!; - // projectViewModel.setPrivilege(privilegeList: res); - // await sharedPref.setObject(MAIN_USER, activation.list); - // await sharedPref.setObject(USER_PROFILE, activation.list); - // loginTokenID = activation.logInTokenID; - // await sharedPref.setObject(LOGIN_TOKEN_ID, activation.logInTokenID); - // await sharedPref.setString(TOKEN, activation.authenticationTokenID!); - - // projectViewModel.analytics.loginRegistration.login_successful(); } }); } } + Future navigateToFamilyFilePage() async { + MedicalFileViewModel medicalFileVM = GetIt.instance(); + medicalFileVM.handleFamilyFileRequestOTPVerification(); + } + Future checkIfUserAgreedBefore({required dynamic request}) async { bool isAgreed = false; if (havePrivilege(109)) { @@ -647,8 +646,7 @@ class AuthenticationViewModel extends ChangeNotifier { bool isFormFamilyFile = false, bool isExcludedUser = false, int? responseID, - int? patientShareRequestID, - String? familyFileTokenID}) async { + int? patientShareRequestID}) async { _navigationService.pushToOtpScreen( phoneNumber: phoneNumber, isFormFamilyFile: isFormFamilyFile, @@ -659,7 +657,6 @@ class AuthenticationViewModel extends ChangeNotifier { otpTypeEnum: otpTypeEnum, responseID: responseID, patientShareRequestID: patientShareRequestID, - familyFileTokenID: familyFileTokenID, onWrongActivationCode: (String? value) { onWrongActivationCode(message: value); }, diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index 3042aba..0997cbc 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -26,6 +26,8 @@ abstract class MedicalFileRepo { Future>>> getPatientFamilyFiles(); + Future>>> getAllPendingRecordsByResponseId({required Map request}); + Future>> addFamilyFile({required dynamic request}); } @@ -312,6 +314,45 @@ class MedicalFileRepoImp implements MedicalFileRepo { } } + @override + Future>>> getAllPendingRecordsByResponseId({required Map request}) async { + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getAllPendingRecordsByResponseId, + body: request, + 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 { @@ -325,6 +366,7 @@ class MedicalFileRepoImp implements MedicalFileRepo { }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { + apiResponse = GenericApiModel( messageStatus: messageStatus, statusCode: statusCode, diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index b3b1db1..59363ba 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -268,6 +268,22 @@ class MedicalFileViewModel extends ChangeNotifier { ); } + Future getAllPendingRecordsByResponseId() async { + AppState appState = getIt(); + final result = await medicalFileRepo.getAllPendingRecordsByResponseId(request: {'ResponseID': appState.getAuthenticatedUser()!.patientId ?? "0", "Status": 2}); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + print("======= Pending Records Response: ${jsonEncode(apiResponse.data)}"); + } + }, + ); + } + Future switchFamilyFiles({Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await medicalFileRepo.getPatientFamilyFiles(); @@ -318,7 +334,6 @@ class MedicalFileViewModel extends ChangeNotifier { message: apiResponse.data['Message'], onOkPressed: () { LoaderBottomSheet.showLoader(); - print("=================== On Press Ok =================="); authVM.sendActivationCode( otpTypeEnum: otpTypeEnum, nationalIdOrFileNumber: request.sharedPatientIdentificationId!, @@ -327,7 +342,6 @@ class MedicalFileViewModel extends ChangeNotifier { isExcludedUser: apiResponse.data['IsPatientExcluded'], responseID: apiResponse.data["ReponseID"], isFormFamilyFile: true); - }, onCancelPressed: () { navigationService.pop(); @@ -335,4 +349,11 @@ class MedicalFileViewModel extends ChangeNotifier { } }); } + + Future handleFamilyFileRequestOTPVerification() async { + LoaderBottomSheet.showLoader(); + await getFamilyFiles(); + await getAllPendingRecordsByResponseId(); + LoaderBottomSheet.hideLoader(); + } } diff --git a/lib/presentation/my_family/my_Family.dart b/lib/presentation/my_family/my_Family.dart index 7ebd069..a4be2bf 100644 --- a/lib/presentation/my_family/my_Family.dart +++ b/lib/presentation/my_family/my_Family.dart @@ -38,7 +38,7 @@ class FamilyMedicalScreen extends StatefulWidget { } class _FamilyMedicalScreenState extends State { - List tabs = [CustomTabBarModel("", LocaleKeys.medicalFile.tr()), CustomTabBarModel("", LocaleKeys.request.tr())]; + List tabs = [CustomTabBarModel(null, LocaleKeys.medicalFile.tr()), CustomTabBarModel(null, LocaleKeys.request.tr())]; MedicalFileViewModel? medicalVM; @override From 288a0018ce424ec726c9fa73070287c1c80a2317 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Mon, 29 Sep 2025 09:46:45 +0300 Subject: [PATCH 09/12] family screen & widgets --- assets/images/png/dummy_user.png | Bin 0 -> 73018 bytes lib/core/api_consts.dart | 1 + lib/core/app_assets.dart | 1 + lib/core/enums.dart | 53 ++++ .../authentication/authentication_repo.dart | 42 ++-- .../medical_file/medical_file_repo.dart | 11 +- .../medical_file/medical_file_view_model.dart | 226 +++++++++++++----- .../medical_file/medical_file_page.dart | 5 +- lib/presentation/my_family/my_Family.dart | 34 ++- .../my_family/widget/family_cards.dart | 28 ++- 10 files changed, 298 insertions(+), 103 deletions(-) create mode 100644 assets/images/png/dummy_user.png diff --git a/assets/images/png/dummy_user.png b/assets/images/png/dummy_user.png new file mode 100644 index 0000000000000000000000000000000000000000..b4bace408fe0553e11eccc275651b680afa66675 GIT binary patch literal 73018 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 literal 0 HcmV?d00001 diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index e11de15..206809d 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -811,6 +811,7 @@ class ApiConsts { static final String sendFamilyFileActivation = 'Services/Authentication.svc/REST/SendActivationCodeForFamilyFile'; static final String checkActivationCodeForFamily = 'Services/Authentication.svc/REST/CheckActivationCodeForFamilyFile'; static final String getAllPendingRecordsByResponseId = 'Services/Authentication.svc/REST/GetAllPendingRecordsByResponseId'; + static final String getAllSharedRecordsByStatus = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus'; // static values for Api diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 83fb842..be651b7 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -158,6 +158,7 @@ class AppAssets { static const String tamara_en = '$pngBasePath/tamara_en.png'; static const String visa = '$pngBasePath/visa.png'; static const String lockIcon = '$pngBasePath/lock-icon.png'; + static const String dummy_user = '$pngBasePath/dummy_user.png'; } class AppAnimations { diff --git a/lib/core/enums.dart b/lib/core/enums.dart index bb6c6fe..e94b3e2 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -30,6 +30,8 @@ enum LoginTypeEnum { sms, whatsapp, face, fingerprint } enum AppEnvironmentTypeEnum { dev, uat, preProd, qa, staging, prod } +enum FamilyFileEnum { active, inactive, blocked, deleted, pending } + extension CalenderExtension on CalenderEnum { int get toInt { switch (this) { @@ -138,6 +140,57 @@ extension OTPTypeEnumExtension on OTPTypeEnum { } } +extension FamilyFileEnumExtenshion on FamilyFileEnum { + int get toInt { + switch (this) { + case FamilyFileEnum.active: + return 3; + case FamilyFileEnum.blocked: + return 1; + case FamilyFileEnum.deleted: + return 0; + case FamilyFileEnum.pending: + return 2; + case FamilyFileEnum.inactive: + return 4; + } + } + + String get displayName { + AppState appState = getIt.get(); + bool isArabic = appState.getLanguageID() == 1 ? true : false; + switch (this) { + case FamilyFileEnum.active: + return isArabic ? 'نشط' : 'Active'; + case FamilyFileEnum.inactive: + return isArabic ? 'غير نشط' : 'Inactive'; + case FamilyFileEnum.blocked: + return isArabic ? 'محظور' : 'Blocked'; + case FamilyFileEnum.deleted: + return isArabic ? 'محذوف' : 'Deleted'; + case FamilyFileEnum.pending: + return isArabic ? 'قيد الانتظار' : 'Pending'; + } + } + + static FamilyFileEnum? fromValue(int value) { + switch (value) { + case 0: + return FamilyFileEnum.pending; + case 2: + return FamilyFileEnum.blocked; + case 1: + return FamilyFileEnum.deleted; + case 3: + return FamilyFileEnum.active; + case 4: + return FamilyFileEnum.inactive; + default: + return null; + } + } +} + enum ServiceTypeEnum { advancePayment, //3 ancillaryOrder, //3 diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 8d1af92..9244e4a 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -30,8 +30,7 @@ abstract class AuthenticationRepo { int? responseID, bool isSwitchUser = false, int? patientID, - int? loginType - }); + int? loginType}); Future>> checkIfUserAgreed({required dynamic commonAuthanticatedRequest}); @@ -196,17 +195,16 @@ class AuthenticationRepoImp implements AuthenticationRepo { } @override - Future>> checkActivationCodeRepo({ - required dynamic newRequest, // could be CheckActivationCodeReq or CheckActivationCodeRegisterReq - required String? activationCode, - required bool isRegister, - bool isFormFamilyFile = false, - int? patientShareRequestID, - int? responseID, - bool isSwitchUser = false, - int? patientID, - int? loginType - }) async { + Future>> checkActivationCodeRepo( + {required dynamic newRequest, // could be CheckActivationCodeReq or CheckActivationCodeRegisterReq + required String? activationCode, + required bool isRegister, + bool isFormFamilyFile = false, + int? patientShareRequestID, + int? responseID, + bool isSwitchUser = false, + int? patientID, + int? loginType}) async { if (isRegister) { newRequest["activationCode"] = activationCode ?? "0000"; newRequest["isSilentLogin"] = activationCode != null ? false : true; @@ -227,7 +225,9 @@ class AuthenticationRepoImp implements AuthenticationRepo { familyRequest['Status'] = 3; familyRequest["PatientID"] = appState.getAuthenticatedUser()!.patientId ?? 0; familyRequest["LogInTokenID"] = appState.getFamilyFileTokenID; - + familyRequest["activationCode"] = activationCode ?? "0000"; + familyRequest["PatientMobileNumber"] = newRequest.patientMobileNumber; + familyRequest["PatientIdentificationID"] = newRequest.patientIdentificationID; } Map switchRequest = {}; if (isSwitchUser) { @@ -237,14 +237,12 @@ class AuthenticationRepoImp implements AuthenticationRepo { switchRequest['IsSilentLogin'] = true; switchRequest['LogInTokenID'] = null; switchRequest['SearchType'] = 2; - if(loginType != 0) { - switchRequest['SuperUser'] = patientID; - switchRequest['DeviceToken'] = null; - }else{ - switchRequest['LoginType'] = 2; - } - - + if (loginType != 0) { + switchRequest['SuperUser'] = patientID; + switchRequest['DeviceToken'] = null; + } else { + switchRequest['LoginType'] = 2; + } } final endpoint = isFormFamilyFile diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index b90e7fb..9c18696 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -24,7 +24,7 @@ abstract class MedicalFileRepo { Future>> getPatientMedicalReportPDF(PatientMedicalReportResponseModel patientMedicalReportResponseModel, AuthenticatedUser authenticatedUser); - Future>>> getPatientFamilyFiles(int status, int patientId); + Future>>> getPatientFamilyFiles(int? status, int patientId); Future>>> getAllPendingRecordsByResponseId({required Map request}); @@ -276,13 +276,13 @@ class MedicalFileRepoImp implements MedicalFileRepo { } @override - Future>>> getPatientFamilyFiles(int status, int patientID) async { + Future>>> getPatientFamilyFiles(int? status, int patientID) async { try { GenericApiModel>? apiResponse; Failure? failure; await apiClient.post( - FAMILY_FILES, - body: {"Status": status, "PatientID":patientID}, + ApiConsts.getAllSharedRecordsByStatus, + body: {if (status != null) "Status": status, "PatientID": patientID}, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, @@ -327,7 +327,7 @@ class MedicalFileRepoImp implements MedicalFileRepo { }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - final list = response['GetAllSharedRecordsByStatusList']; + final list = response['GetAllPendingRecordsList']; // if (list == null || list.isEmpty) { // throw Exception("lab list is empty"); // } @@ -366,7 +366,6 @@ class MedicalFileRepoImp implements MedicalFileRepo { }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - apiResponse = GenericApiModel( messageStatus: messageStatus, statusCode: statusCode, diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 746e592..153828c 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -39,14 +39,27 @@ class MedicalFileViewModel extends ChangeNotifier { List patientMedicalReportCancelledList = []; List patientFamilyFiles = []; + List pendingFamilyFiles = []; String patientSickLeavePDFBase64 = ""; String patientMedicalReportPDFBase64 = ""; int selectedMedicalReportsTabIndex = 0; + int _selectedFamilyFileTabIndex = 0; + + int get getSelectedFamilyFileTabIndex => _selectedFamilyFileTabIndex; + + set setSelectedFamilyFileTabIndex(int value) { + if (_selectedFamilyFileTabIndex != value) { + _selectedFamilyFileTabIndex = value; + notifyListeners(); + } + } + static final DialogService _dialogService = getIt.get(); - AppState _appState = getIt(); + final AppState _appState = getIt(); AuthenticationViewModel authVM = getIt.get(); + MedicalFileViewModel({required this.medicalFileRepo, required this.errorHandlerService}); initMedicalFileProvider() { @@ -67,6 +80,14 @@ class MedicalFileViewModel extends ChangeNotifier { notifyListeners(); } + void onFamilyFileTabChange(int index) { + setSelectedFamilyFileTabIndex = index; + if (index == 1) { + getAllPendingRecordsByResponseId(); + } + notifyListeners(); + } + setIsPatientVaccineListLoading(bool isLoading) { isPatientVaccineListLoading = isLoading; notifyListeners(); @@ -235,8 +256,8 @@ class MedicalFileViewModel extends ChangeNotifier { ); } - Future getFamilyFiles({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await medicalFileRepo.getPatientFamilyFiles(); + Future getFamilyFiles({int? status, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await medicalFileRepo.getPatientFamilyFiles(status, _appState.superUserID != null ? _appState.superUserID! : _appState.getAuthenticatedUser()!.patientId!); result.fold( (failure) async => await errorHandlerService.handleError( @@ -250,15 +271,80 @@ class MedicalFileViewModel extends ChangeNotifier { _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { patientFamilyFiles = apiResponse.data!; - patientFamilyFiles.insert( - 0, - FamilyFileResponseModelLists( + if (apiResponse.data != null) { + 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), - ); + responseId: _appState.getAuthenticatedUser()!.patientId, + age: _appState.getAuthenticatedUser()!.age, + mobileNumber: _appState.getAuthenticatedUser()!.mobileNumber, + patientIdenficationNumber: _appState.getAuthenticatedUser()!.patientIdentificationNo, + emaiLAddress: _appState.getAuthenticatedUser()!.emailAddress, + genderDescription: _appState.getAuthenticatedUser()!.genderDescription, + ), + ); + + final List activeFamilyFiles = []; + final List tempPendingFamilyFiles = []; + for (var element in apiResponse.data!) { + if (element.status != null && element.status == FamilyFileEnum.active.toInt) { + activeFamilyFiles.add(FamilyFileResponseModelLists( + patientId: element.patientId, + patientName: element.patientName!, + isActive: element.status == FamilyFileEnum.active.toInt ? true : false, + gender: element.gender!, + responseId: element.patientId, + mobileNumber: element.mobileNumber, + age: element.age, + patientIdenficationNumber: element.patientIdenficationNumber, + relationship: element.relationship, + relationshipId: element.relationshipId, + relationshipN: element.relationshipN, + status: element.status, + statusDescription: element.statusDescription, + createdOn: element.createdOn, + editedOn: element.editedOn, + patientDataVerified: element.patientDataVerified, + regionId: element.regionId, + familyRegionId: element.familyRegionId, + genderDescription: element.genderDescription, + genderImage: element.genderImage, + emaiLAddress: element.emaiLAddress)); + } + + if (element.status != null && element.status == FamilyFileEnum.pending.toInt) { + tempPendingFamilyFiles.add(FamilyFileResponseModelLists( + patientId: element.patientId, + patientName: element.patientName!, + isActive: element.status == FamilyFileEnum.active.toInt ? true : false, + gender: element.gender!, + responseId: element.patientId, + mobileNumber: element.mobileNumber, + age: element.age, + patientIdenficationNumber: element.patientIdenficationNumber, + relationship: element.relationship, + relationshipId: element.relationshipId, + relationshipN: element.relationshipN, + status: element.status, + statusDescription: element.statusDescription, + createdOn: element.createdOn, + editedOn: element.editedOn, + patientDataVerified: element.patientDataVerified, + regionId: element.regionId, + familyRegionId: element.familyRegionId, + genderDescription: element.genderDescription, + genderImage: element.genderImage, + emaiLAddress: element.emaiLAddress)); + } + } + patientFamilyFiles.addAll(activeFamilyFiles.where((element) => !patientFamilyFiles.any((e) => e.patientId == element.patientId))); + pendingFamilyFiles.addAll(tempPendingFamilyFiles); + } + notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -278,61 +364,93 @@ class MedicalFileViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 2) { _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { - print("======= Pending Records Response: ${jsonEncode(apiResponse.data)}"); + if (apiResponse.data != null) { + final List tempPendingFamilyFiles = []; + for (var element in apiResponse.data!) { + if (element.status != null && element.status == FamilyFileEnum.pending.toInt) { + tempPendingFamilyFiles.add(FamilyFileResponseModelLists( + patientId: element.patientId, + patientName: element.patientName!, + isActive: element.status == FamilyFileEnum.active.toInt ? true : false, + gender: element.gender, + responseId: element.patientId, + mobileNumber: element.mobileNumber, + age: element.age, + patientIdenficationNumber: element.patientIdenficationNumber, + relationship: element.relationship, + relationshipId: element.relationshipId, + relationshipN: element.relationshipN, + status: element.status, + statusDescription: element.statusDescription, + createdOn: element.createdOn, + editedOn: element.editedOn, + patientDataVerified: element.patientDataVerified, + regionId: element.regionId, + familyRegionId: element.familyRegionId, + genderDescription: element.genderDescription, + genderImage: element.genderImage, + emaiLAddress: element.emaiLAddress)); + } + } + pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => !pendingFamilyFiles.any((e) => e.patientId == element.patientId))); + } + notifyListeners(); } }, ); } - Future switchFamilyFiles({Function(dynamic)? onSuccess, Function(String)? onError}) async { - // final result = await medicalFileRepo.getPatientFamilyFiles(); - final result = await medicalFileRepo.getPatientFamilyFiles(3,_appState.superUserID !=null ? _appState.superUserID! : _appState.getAuthenticatedUser()!.patientId!); - - 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!, - age: _appState.getAuthenticatedUser()!.age, - mobileNumber: _appState.getAuthenticatedUser()!.mobileNumber, - responseId: _appState.getAuthenticatedUser()!.patientId), - ); - notifyListeners(); - if (onSuccess != null) { - onSuccess(apiResponse); - } - } - }, + // Future switchFamilyFiles({Function(dynamic)? onSuccess, Function(String)? onError}) async { + // // final result = await medicalFileRepo.getPatientFamilyFiles(); + // final result = await medicalFileRepo.getPatientFamilyFiles(3, _appState.superUserID != null ? _appState.superUserID! : _appState.getAuthenticatedUser()!.patientId!); + // + // 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!, + // age: _appState.getAuthenticatedUser()!.age, + // mobileNumber: _appState.getAuthenticatedUser()!.mobileNumber, + // responseId: _appState.getAuthenticatedUser()!.patientId), + // ); + // notifyListeners(); + // if (onSuccess != null) { + // onSuccess(apiResponse); + // } + // } + // }, + // ); + // } + + Future switchFamilyFiles({Function(dynamic)? onSuccess, int? responseID, int? patientID, String? phoneNumber, Function(String)? onError}) async { + authVM.phoneNumberController.text = phoneNumber!.startsWith("0") ? phoneNumber.replaceFirst("0", "") : phoneNumber; + + await authVM.checkActivationCode( + activationCode: '0000', + otpTypeEnum: OTPTypeEnum.sms, + onWrongActivationCode: (String? str) {}, + responseID: responseID, + isFormFamilyFile: false, + isSwitchUser: true, + patientID: patientID, ); } - Future switchFamilyFiles( {Function(dynamic)? onSuccess,int? responseID,int? patientID, String? phoneNumber, Function(String)? onError}) async { - authVM.phoneNumberController.text =phoneNumber!.startsWith("0") - ? phoneNumber.replaceFirst("0", "") - : phoneNumber; - - - - await authVM.checkActivationCode(activationCode: '0000', otpTypeEnum: OTPTypeEnum.sms, onWrongActivationCode: (String? str) {}, responseID: responseID, isFormFamilyFile:false, isSwitchUser: true, patientID: patientID, ); - - - } Future addFamilyFile({required OTPTypeEnum otpTypeEnum, required bool isExcludedUser}) async { - LoaderBottomSheet.showLoader(); AuthenticationViewModel authVM = getIt.get(); NavigationService navigationService = getIt.get(); diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index ea0b86b..afea4e6 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -78,7 +78,9 @@ class _MedicalFilePageState extends State { insuranceViewModel.initInsuranceProvider(); medicalFileViewModel.setIsPatientSickLeaveListLoading(true); medicalFileViewModel.getPatientSickLeaveList(); - medicalFileViewModel.getFamilyFiles(); + medicalFileViewModel.getFamilyFiles(); //TODO: Remove status: 1 by Aamir Need to Discuss With Sultan + medicalFileViewModel.getAllPendingRecordsByResponseId(); //TODO: Added By Aamir + medicalFileViewModel.onTabChanged(0); } }); @@ -198,7 +200,6 @@ class _MedicalFilePageState extends State { CustomTabBarModel(AppAssets.more, "More".needTranslation), ], onTabChange: (index) { - print(index); medicalFileVM.onTabChanged(index); }, ).paddingSymmetrical(24.h, 0.0), diff --git a/lib/presentation/my_family/my_Family.dart b/lib/presentation/my_family/my_Family.dart index cb49933..1944704 100644 --- a/lib/presentation/my_family/my_Family.dart +++ b/lib/presentation/my_family/my_Family.dart @@ -22,6 +22,7 @@ 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'; +import 'package:provider/provider.dart'; class FamilyMedicalScreen extends StatefulWidget { final List profiles; @@ -44,7 +45,7 @@ class _FamilyMedicalScreenState extends State { @override void initState() { super.initState(); - medicalVM = getIt.get(); + medicalVM = context.read(); } @override @@ -67,13 +68,12 @@ class _FamilyMedicalScreenState extends State { SizedBox(height: 25.h), CustomTabBar( tabs: tabs, - onTabChange: (int index) {}, + onTabChange: (index) { + medicalVM!.onFamilyFileTabChange(index); + }, ), SizedBox(height: 25.h), - FamilyCards(profiles: widget.profiles, onSelect: (FamilyFileResponseModelLists profile){ - medicalVM?.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); - - }, isShowDetails: true), + Consumer(builder: (context, medicalVM, child) => getFamilyTabs(index: medicalVM.getSelectedFamilyFileTabIndex)), SizedBox(height: 20.h), ], ), @@ -96,6 +96,28 @@ class _FamilyMedicalScreenState extends State { ); } + Widget getFamilyTabs({required int index}) { + switch (index) { + case 0: + return FamilyCards( + profiles: medicalVM!.patientFamilyFiles, + onSelect: (FamilyFileResponseModelLists profile) { + medicalVM!.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + }, + isShowDetails: true, + ); + case 1: + return FamilyCards( + profiles: medicalVM!.pendingFamilyFiles, + onSelect: (FamilyFileResponseModelLists profile) { + // medicalVM!.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + }, + isShowDetails: true, + ); default: + return SizedBox.shrink(); + } + } + void showModelSheet() { AuthenticationViewModel authVm = getIt.get(); return showCommonBottomSheetWithoutHeight(context, diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 480f91f..40a2d8c 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -43,20 +43,22 @@ class _FamilyCardsState extends State { ), itemBuilder: (context, index) { final profile = widget.profiles[index]; - final isActive = (profile.responseId == appState - .getAuthenticatedUser() - ?.patientId); + 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 + opacity: isActive || profile.status == FamilyFileEnum.pending.toInt ? 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), + icon: profile.gender == null + ? AppAssets.dummy_user + : 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), @@ -73,13 +75,13 @@ class _FamilyCardsState extends State { 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, - ) + 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(), @@ -102,7 +104,7 @@ class _FamilyCardsState extends State { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 13.h, - icon: widget.isBottomSheet ? null : AppAssets.heart, + icon: widget.isBottomSheet ? null : AppAssets.heart, iconColor: AppColors.primaryRedColor, padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), ).paddingOnly(top: 0, bottom: 0), From 1335d3a7c8a010c7fb0a00e7087fe4d7fee02c04 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Mon, 29 Sep 2025 16:20:51 +0300 Subject: [PATCH 10/12] family screen & widgets --- assets/images/svg/switch_user.svg | 8 + lib/core/app_assets.dart | 1 + lib/core/app_state.dart | 2 +- .../authentication/authentication_repo.dart | 11 + .../authentication_view_model.dart | 2 +- .../medical_file/medical_file_view_model.dart | 198 +++++++++++++----- .../medical_file/medical_file_page.dart | 8 +- lib/presentation/my_family/my_Family.dart | 107 ++++++---- .../my_family/widget/family_cards.dart | 7 +- 9 files changed, 249 insertions(+), 95 deletions(-) create mode 100644 assets/images/svg/switch_user.svg diff --git a/assets/images/svg/switch_user.svg b/assets/images/svg/switch_user.svg new file mode 100644 index 0000000..fa8e47e --- /dev/null +++ b/assets/images/svg/switch_user.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index dd4bce4..424b90c 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -141,6 +141,7 @@ class AppAssets { static const String ic_normal_result = '$svgBasePath/normal_result.svg'; static const String ic_low_result = '$svgBasePath/low_result.svg'; static const String ic_critical_low_result = '$svgBasePath/critical_low_result.svg'; + static const String switch_user = '$svgBasePath/switch_user.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 206adc1..030cc36 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -57,7 +57,7 @@ class AppState { } } - int? get superUserID => _superUserID; + int? get getSuperUserID => _superUserID; set setSuperUserID(int? value) => _superUserID = value; diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 9244e4a..96bdc0b 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -205,6 +205,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { bool isSwitchUser = false, int? patientID, int? loginType}) async { + AppState appState = getIt.get(); if (isRegister) { newRequest["activationCode"] = activationCode ?? "0000"; newRequest["isSilentLogin"] = activationCode != null ? false : true; @@ -243,6 +244,16 @@ class AuthenticationRepoImp implements AuthenticationRepo { } else { switchRequest['LoginType'] = 2; } + + if (appState.getSuperUserID == responseID) { + switchRequest['LoginType'] = 3; + switchRequest['PatientIdentificationID'] = ""; + // switchRequest['ProjectOutSA'] = newRequest.zipCode == '966' ? false : true; + switchRequest.remove('NationalID'); + switchRequest.remove('isDentalAllowedBackend'); + switchRequest.remove('ProjectOutSA'); + switchRequest.remove('ForRegisteration'); + } } final endpoint = isFormFamilyFile diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 1bdc09e..0a11eda 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -518,7 +518,7 @@ class AuthenticationViewModel extends ChangeNotifier { responseID: responseID, isSwitchUser: isSwitchUser, patientID: patientID, - loginType: _appState.superUserID != null ? 0 : 2, + loginType: _appState.getSuperUserID != null ? 0 : 2, ); resultEither.fold( diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 153828c..64075b4 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -257,7 +257,7 @@ class MedicalFileViewModel extends ChangeNotifier { } Future getFamilyFiles({int? status, Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await medicalFileRepo.getPatientFamilyFiles(status, _appState.superUserID != null ? _appState.superUserID! : _appState.getAuthenticatedUser()!.patientId!); + final result = await medicalFileRepo.getPatientFamilyFiles(status, _appState.getSuperUserID != null ? _appState.getSuperUserID! : _appState.getAuthenticatedUser()!.patientId!); result.fold( (failure) async => await errorHandlerService.handleError( @@ -288,61 +288,157 @@ class MedicalFileViewModel extends ChangeNotifier { ), ); + // final List activeFamilyFiles = []; + // final List tempPendingFamilyFiles = []; + // for (var element in apiResponse.data!) { + // if (element.status != null && element.status == FamilyFileEnum.active.toInt) { + // activeFamilyFiles.add(FamilyFileResponseModelLists( + // patientId: element.patientId, + // patientName: element.patientName!, + // isActive: element.status == FamilyFileEnum.active.toInt ? true : false, + // gender: element.gender!, + // responseId: element.patientId, + // mobileNumber: element.mobileNumber, + // age: element.age, + // patientIdenficationNumber: element.patientIdenficationNumber, + // relationship: element.relationship, + // relationshipId: element.relationshipId, + // relationshipN: element.relationshipN, + // status: element.status, + // statusDescription: element.statusDescription, + // createdOn: element.createdOn, + // editedOn: element.editedOn, + // patientDataVerified: element.patientDataVerified, + // regionId: element.regionId, + // familyRegionId: element.familyRegionId, + // genderDescription: element.genderDescription, + // genderImage: element.genderImage, + // emaiLAddress: element.emaiLAddress)); + // } + // + // if (element.status != null && element.status == FamilyFileEnum.pending.toInt) { + // tempPendingFamilyFiles.add(FamilyFileResponseModelLists( + // patientId: element.patientId, + // patientName: element.patientName!, + // isActive: element.status == FamilyFileEnum.active.toInt ? true : false, + // gender: element.gender!, + // responseId: element.patientId, + // mobileNumber: element.mobileNumber, + // age: element.age, + // patientIdenficationNumber: element.patientIdenficationNumber, + // relationship: element.relationship, + // relationshipId: element.relationshipId, + // relationshipN: element.relationshipN, + // status: element.status, + // statusDescription: element.statusDescription, + // createdOn: element.createdOn, + // editedOn: element.editedOn, + // patientDataVerified: element.patientDataVerified, + // regionId: element.regionId, + // familyRegionId: element.familyRegionId, + // genderDescription: element.genderDescription, + // genderImage: element.genderImage, + // emaiLAddress: element.emaiLAddress)); + // } + // } + // patientFamilyFiles.addAll(activeFamilyFiles.where((element) => !patientFamilyFiles.any((e) => e.patientId == element.patientId))); + // pendingFamilyFiles.addAll(tempPendingFamilyFiles); + final List activeFamilyFiles = []; final List tempPendingFamilyFiles = []; - for (var element in apiResponse.data!) { - if (element.status != null && element.status == FamilyFileEnum.active.toInt) { - activeFamilyFiles.add(FamilyFileResponseModelLists( - patientId: element.patientId, - patientName: element.patientName!, - isActive: element.status == FamilyFileEnum.active.toInt ? true : false, - gender: element.gender!, - responseId: element.patientId, - mobileNumber: element.mobileNumber, - age: element.age, - patientIdenficationNumber: element.patientIdenficationNumber, - relationship: element.relationship, - relationshipId: element.relationshipId, - relationshipN: element.relationshipN, - status: element.status, - statusDescription: element.statusDescription, - createdOn: element.createdOn, - editedOn: element.editedOn, - patientDataVerified: element.patientDataVerified, - regionId: element.regionId, - familyRegionId: element.familyRegionId, - genderDescription: element.genderDescription, - genderImage: element.genderImage, - emaiLAddress: element.emaiLAddress)); - } + // final Set pendingIds = {}; + + // for (var element in apiResponse.data!) { + // if (element.status != null && element.status == FamilyFileEnum.pending.toInt) { + // tempPendingFamilyFiles.add(FamilyFileResponseModelLists( + // patientId: element.patientId, + // patientName: element.patientName!, + // isActive: false, + // gender: element.gender!, + // responseId: element.patientId, + // mobileNumber: element.mobileNumber, + // age: element.age, + // patientIdenficationNumber: element.patientIdenficationNumber, + // relationship: element.relationship, + // relationshipId: element.relationshipId, + // relationshipN: element.relationshipN, + // status: element.status, + // statusDescription: element.statusDescription, + // createdOn: element.createdOn, + // editedOn: element.editedOn, + // patientDataVerified: element.patientDataVerified, + // regionId: element.regionId, + // familyRegionId: element.familyRegionId, + // genderDescription: element.genderDescription, + // genderImage: element.genderImage, + // emaiLAddress: element.emaiLAddress)); + // } else if (element.status != null && element.status == FamilyFileEnum.active.toInt) { + // activeFamilyFiles.add(FamilyFileResponseModelLists( + // patientId: element.patientId, + // patientName: element.patientName!, + // isActive: element.status == FamilyFileEnum.active.toInt ? true : false, + // gender: element.gender!, + // responseId: element.patientId, + // mobileNumber: element.mobileNumber, + // age: element.age, + // patientIdenficationNumber: element.patientIdenficationNumber, + // relationship: element.relationship, + // relationshipId: element.relationshipId, + // relationshipN: element.relationshipN, + // status: element.status, + // statusDescription: element.statusDescription, + // createdOn: element.createdOn, + // editedOn: element.editedOn, + // patientDataVerified: element.patientDataVerified, + // regionId: element.regionId, + // familyRegionId: element.familyRegionId, + // genderDescription: element.genderDescription, + // genderImage: element.genderImage, + // emaiLAddress: element.emaiLAddress)); + // } + // } - if (element.status != null && element.status == FamilyFileEnum.pending.toInt) { - tempPendingFamilyFiles.add(FamilyFileResponseModelLists( - patientId: element.patientId, - patientName: element.patientName!, - isActive: element.status == FamilyFileEnum.active.toInt ? true : false, - gender: element.gender!, - responseId: element.patientId, - mobileNumber: element.mobileNumber, - age: element.age, - patientIdenficationNumber: element.patientIdenficationNumber, - relationship: element.relationship, - relationshipId: element.relationshipId, - relationshipN: element.relationshipN, - status: element.status, - statusDescription: element.statusDescription, - createdOn: element.createdOn, - editedOn: element.editedOn, - patientDataVerified: element.patientDataVerified, - regionId: element.regionId, - familyRegionId: element.familyRegionId, - genderDescription: element.genderDescription, - genderImage: element.genderImage, - emaiLAddress: element.emaiLAddress)); + for (var element in apiResponse.data!) { + if (element.status == null) continue; + + final isPending = element.status == FamilyFileEnum.pending.toInt; + final isActive = element.status == FamilyFileEnum.active.toInt; + + if (!isPending && !isActive) continue; + + final familyFile = FamilyFileResponseModelLists( + patientId: element.patientId, + patientName: element.patientName!, + isActive: isActive, + gender: element.gender!, + responseId: element.patientId, + mobileNumber: element.mobileNumber, + age: element.age, + patientIdenficationNumber: element.patientIdenficationNumber, + relationship: element.relationship, + relationshipId: element.relationshipId, + relationshipN: element.relationshipN, + status: element.status, + statusDescription: element.statusDescription, + createdOn: element.createdOn, + editedOn: element.editedOn, + patientDataVerified: element.patientDataVerified, + regionId: element.regionId, + familyRegionId: element.familyRegionId, + genderDescription: element.genderDescription, + genderImage: element.genderImage, + emaiLAddress: element.emaiLAddress, + ); + + if (isPending) { + tempPendingFamilyFiles.add(familyFile); + } else if (isActive) { + activeFamilyFiles.add(familyFile); } } - patientFamilyFiles.addAll(activeFamilyFiles.where((element) => !patientFamilyFiles.any((e) => e.patientId == element.patientId))); - pendingFamilyFiles.addAll(tempPendingFamilyFiles); + + patientFamilyFiles.addAll(activeFamilyFiles.where((element) => patientFamilyFiles.every((e) => e.patientId != element.patientId || e.status != FamilyFileEnum.active.toInt))); + pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => pendingFamilyFiles.every((e) => e.patientId != element.patientId))); } notifyListeners(); diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index afea4e6..dcd83c6 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -73,13 +73,16 @@ class _MedicalFilePageState extends State { @override void initState() { + appState = getIt.get(); scheduleMicrotask(() { if (appState.isAuthenticated) { insuranceViewModel.initInsuranceProvider(); medicalFileViewModel.setIsPatientSickLeaveListLoading(true); medicalFileViewModel.getPatientSickLeaveList(); - medicalFileViewModel.getFamilyFiles(); //TODO: Remove status: 1 by Aamir Need to Discuss With Sultan - medicalFileViewModel.getAllPendingRecordsByResponseId(); //TODO: Added By Aamir + if (appState.getSuperUserID == null) { + medicalFileViewModel.getFamilyFiles(status: 3); //TODO: Remove status: 1 by Aamir Need to Discuss With Sultan + medicalFileViewModel.getAllPendingRecordsByResponseId(); //TODO: Added By Aamir + } medicalFileViewModel.onTabChanged(0); } @@ -93,7 +96,6 @@ class _MedicalFilePageState extends State { myAppointmentsViewModel = Provider.of(context, listen: false); medicalFileViewModel = Provider.of(context, listen: false); bookAppointmentsViewModel = Provider.of(context, listen: false); - appState = getIt.get(); NavigationService navigationService = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, diff --git a/lib/presentation/my_family/my_Family.dart b/lib/presentation/my_family/my_Family.dart index 1944704..c0d85c0 100644 --- a/lib/presentation/my_family/my_Family.dart +++ b/lib/presentation/my_family/my_Family.dart @@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ 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/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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'; @@ -51,33 +52,23 @@ class _FamilyMedicalScreenState extends State { @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: (index) { - medicalVM!.onFamilyFileTabChange(index); - }, - ), - SizedBox(height: 25.h), - Consumer(builder: (context, medicalVM, child) => getFamilyTabs(index: medicalVM.getSelectedFamilyFileTabIndex)), - SizedBox(height: 20.h), - ], - ), - ).paddingSymmetrical(20, 0), + body: CollapsingListView( + title: "My Medical File".needTranslation, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomTabBar( + tabs: tabs, + onTabChange: (index) { + medicalVM!.onFamilyFileTabChange(index); + }, + ), + SizedBox(height: 25.h), + Consumer(builder: (context, medicalVM, child) => getFamilyTabs(index: medicalVM.getSelectedFamilyFileTabIndex)), + SizedBox(height: 20.h), + ], + ).paddingSymmetrical(20, 0)), bottomSheet: Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -85,15 +76,58 @@ class _FamilyMedicalScreenState extends State { ), 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, - )), + text: "Add a new family member", + onPressed: () { + showModelSheet(); + }, + icon: AppAssets.add_icon, + height: 56.h, + fontWeight: FontWeight.w600)), ); + // 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: (index) { + // medicalVM!.onFamilyFileTabChange(index); + // }, + // ), + // SizedBox(height: 25.h), + // Consumer(builder: (context, medicalVM, child) => getFamilyTabs(index: medicalVM.getSelectedFamilyFileTabIndex)), + // 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, + // )), + // ); } Widget getFamilyTabs({required int index}) { @@ -113,7 +147,8 @@ class _FamilyMedicalScreenState extends State { // medicalVM!.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); }, isShowDetails: true, - ); default: + ); + default: return SizedBox.shrink(); } } diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 40a2d8c..3916756 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -33,7 +33,7 @@ class _FamilyCardsState extends State { Widget build(BuildContext context) { return GridView.builder( shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), + physics: NeverScrollableScrollPhysics(), itemCount: widget.profiles.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, @@ -41,6 +41,7 @@ class _FamilyCardsState extends State { mainAxisSpacing: 10.h, childAspectRatio: widget.isShowDetails ? 0.56.h : 0.74.h, ), + padding: EdgeInsets.only(bottom: 80.h), itemBuilder: (context, index) { final profile = widget.profiles[index]; final isActive = (profile.responseId == appState.getAuthenticatedUser()?.patientId); @@ -99,12 +100,12 @@ class _FamilyCardsState extends State { CustomButton( height: 40.h, onPressed: () => widget.onSelect(profile), - text: LocaleKeys.select.tr(), + text: LocaleKeys.switchAccount.tr(), backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 13.h, - icon: widget.isBottomSheet ? null : AppAssets.heart, + icon: widget.isBottomSheet ? null : AppAssets.switch_user, iconColor: AppColors.primaryRedColor, padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), ).paddingOnly(top: 0, bottom: 0), From d918b6e4645be019499913d1203b2115aee20bdd Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Tue, 7 Oct 2025 08:28:14 +0300 Subject: [PATCH 11/12] family screen & widgets --- assets/images/svg/active-check.svg | 3 + assets/images/svg/alert-square.svg | 3 + assets/images/svg/arrow-right.svg | 3 + assets/images/svg/delete_icon.svg | 3 + lib/core/api/api_client.dart | 62 +-- lib/core/api_consts.dart | 4 +- lib/core/app_assets.dart | 4 + lib/core/app_state.dart | 13 +- lib/core/dependencies.dart | 6 +- lib/core/enums.dart | 6 +- lib/core/utils/request_utils.dart | 2 +- lib/core/utils/utils.dart | 11 +- .../authentication/authentication_repo.dart | 98 +++-- .../authentication_view_model.dart | 85 ++-- .../authenticated_user_resp_model.dart | 307 +++++++------- .../medical_file/medical_file_repo.dart | 78 +++- .../medical_file/medical_file_view_model.dart | 329 +++++++-------- .../models/family_file_response_model.dart | 147 +++---- lib/presentation/home/landing_page.dart | 2 + .../medical_file/medical_file_page.dart | 283 +++++++------ lib/presentation/my_family/my_family.dart | 213 +++------- .../my_family/widget/family_cards.dart | 391 ++++++++++++++---- .../my_family/widget/my_family_sheet.dart | 3 +- .../profile_settings/profile_settings.dart | 142 ++++++- lib/services/dialog_service.dart | 47 +++ lib/widgets/appbar/collapsing_list_view.dart | 6 +- lib/widgets/buttons/custom_button.dart | 65 ++- lib/widgets/chip/custom_chip_widget.dart | 3 + .../family_files/family_file_add_widget.dart | 105 +++++ 29 files changed, 1480 insertions(+), 944 deletions(-) create mode 100644 assets/images/svg/active-check.svg create mode 100644 assets/images/svg/alert-square.svg create mode 100644 assets/images/svg/arrow-right.svg create mode 100644 assets/images/svg/delete_icon.svg create mode 100644 lib/widgets/family_files/family_file_add_widget.dart diff --git a/assets/images/svg/active-check.svg b/assets/images/svg/active-check.svg new file mode 100644 index 0000000..c94a3a0 --- /dev/null +++ b/assets/images/svg/active-check.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/alert-square.svg b/assets/images/svg/alert-square.svg new file mode 100644 index 0000000..87b52e3 --- /dev/null +++ b/assets/images/svg/alert-square.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/arrow-right.svg b/assets/images/svg/arrow-right.svg new file mode 100644 index 0000000..8818d72 --- /dev/null +++ b/assets/images/svg/arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/delete_icon.svg b/assets/images/svg/delete_icon.svg new file mode 100644 index 0000000..2e37d0d --- /dev/null +++ b/assets/images/svg/delete_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index dc8b576..b1d1d87 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -18,25 +18,25 @@ abstract class ApiClient { static final NavigationService _navigationService = getIt.get(); Future post( - String endPoint, { - required Map body, - required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, - required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, - bool isAllowAny, - bool isExternal, - bool isRCService, - bool isPaymentServices, - bool bypassConnectionCheck, - }); + String endPoint, { + required Map body, + required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, + required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, + bool isAllowAny, + bool isExternal, + bool isRCService, + bool isPaymentServices, + bool bypassConnectionCheck, + }); Future get( - String endPoint, { - required Function(dynamic response, int statusCode) onSuccess, - required Function(String error, int statusCode) onFailure, - Map? queryParams, - bool isExternal, - bool isRCService, - }); + String endPoint, { + required Function(dynamic response, int statusCode) onSuccess, + required Function(String error, int statusCode) onFailure, + Map? queryParams, + bool isExternal, + bool isRCService, + }); String getSessionId(String id); @@ -87,16 +87,16 @@ class ApiClientImp implements ApiClient { @override post( - String endPoint, { - required Map body, - required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, - required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, - bool isAllowAny = false, - bool isExternal = false, - bool isRCService = false, - bool isPaymentServices = false, - bool bypassConnectionCheck = false, - }) async { + String endPoint, { + required Map body, + required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, + required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, + bool isAllowAny = false, + bool isExternal = false, + bool isRCService = false, + bool isPaymentServices = false, + bool bypassConnectionCheck = false, + }) async { String url; if (isExternal) { url = endPoint; @@ -322,10 +322,10 @@ class ApiClientImp implements ApiClient { @override get(String endPoint, {required Function(dynamic response, int statusCode) onSuccess, - required Function(String error, int statusCode) onFailure, - Map? queryParams, - bool isExternal = false, - bool isRCService = false}) async { + required Function(String error, int statusCode) onFailure, + Map? queryParams, + bool isExternal = false, + bool isRCService = false}) async { String url; if (isExternal) { url = endPoint; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 9f5abce..fd61480 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -727,7 +727,7 @@ const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatu 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 @@ -812,6 +812,8 @@ class ApiConsts { static final String checkActivationCodeForFamily = 'Services/Authentication.svc/REST/CheckActivationCodeForFamilyFile'; static final String getAllPendingRecordsByResponseId = 'Services/Authentication.svc/REST/GetAllPendingRecordsByResponseId'; static final String getAllSharedRecordsByStatus = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus'; + static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile'; + static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus'; // static values for Api diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 513a52a..89f2e82 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -146,6 +146,8 @@ class AppAssets { static const String ic_low_result = '$svgBasePath/low_result.svg'; static const String ic_critical_low_result = '$svgBasePath/critical_low_result.svg'; static const String switch_user = '$svgBasePath/switch_user.svg'; + static const String activeCheck = '$svgBasePath/active-check.svg'; + static const String deleteIcon = '$svgBasePath/delete_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; @@ -157,6 +159,8 @@ class AppAssets { static const String feedback = '$svgBasePath/feedback.svg'; static const String news = '$svgBasePath/news.svg'; static const String heart = '$svgBasePath/heart.svg'; + static const String alertSquare = '$svgBasePath/alert-square.svg'; + static const String arrowRight = '$svgBasePath/arrow-right.svg'; // PNGS // static const String hmg_logo = '$pngBasePath/hmg_logo.png'; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 030cc36..404e22b 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -38,15 +38,15 @@ class AppState { AuthenticatedUser? _authenticatedChildUser; int? _superUserID; + bool isChildLoggedIn = false; - void setAuthenticatedUser(AuthenticatedUser authenticatedUser, {bool isFamily = false}) { + void setAuthenticatedUser(AuthenticatedUser? authenticatedUser, {bool isFamily = false}) { if (isFamily) { _authenticatedChildUser = authenticatedUser; } else { setIsAuthenticated = true; _authenticatedRootUser = authenticatedUser; } - } AuthenticatedUser? getAuthenticatedUser({bool isFamily = false}) { @@ -59,8 +59,12 @@ class AppState { int? get getSuperUserID => _superUserID; + bool get getIsChildLoggedIn => isChildLoggedIn; + set setSuperUserID(int? value) => _superUserID = value; + set setIsChildLoggedIn(bool value) => isChildLoggedIn = value; + String _userBloodGroup = ""; String get getUserBloodGroup => _userBloodGroup; @@ -103,8 +107,6 @@ class AppState { set setDeviceTypeID(v) => deviceTypeID = v; - - String _familyFileTokenID = ""; String get getFamilyFileTokenID => _familyFileTokenID; @@ -151,9 +153,8 @@ class AppState { } ///this will be called if there is any problem in getting the user location - void resetLocation(){ + void resetLocation() { userLong = 0.0; userLong = 0.0; } - } diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 1fe2317..a3bb4c7 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -97,11 +97,7 @@ class AppDependencies { // Global/shared VMs → LazySingleton getIt.registerLazySingleton( - () => LabViewModel( - labRepo: getIt(), - errorHandlerService: getIt(), - navigationService: getIt() - ), + () => LabViewModel(labRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()), ); getIt.registerLazySingleton( diff --git a/lib/core/enums.dart b/lib/core/enums.dart index e94b3e2..4151bed 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -30,7 +30,7 @@ enum LoginTypeEnum { sms, whatsapp, face, fingerprint } enum AppEnvironmentTypeEnum { dev, uat, preProd, qa, staging, prod } -enum FamilyFileEnum { active, inactive, blocked, deleted, pending } +enum FamilyFileEnum { active, inactive, blocked, deleted, pending, rejected } extension CalenderExtension on CalenderEnum { int get toInt { @@ -152,6 +152,8 @@ extension FamilyFileEnumExtenshion on FamilyFileEnum { case FamilyFileEnum.pending: return 2; case FamilyFileEnum.inactive: + return 6; + case FamilyFileEnum.rejected: return 4; } } @@ -170,6 +172,8 @@ extension FamilyFileEnumExtenshion on FamilyFileEnum { return isArabic ? 'محذوف' : 'Deleted'; case FamilyFileEnum.pending: return isArabic ? 'قيد الانتظار' : 'Pending'; + case FamilyFileEnum.rejected: + return isArabic ? 'مرفوض' : 'Rejected'; } } diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index cd813ed..a4ea936 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -262,7 +262,7 @@ class RequestUtils { static Future getAddFamilyRequest({required String nationalIDorFile, required String mobileNo, required String countryCode}) async { FamilyFileRequest request = FamilyFileRequest(); - int? loginType = 0; // Default to National ID + int? loginType = 0; if (countryCode == CountryEnum.saudiArabia.countryCode || countryCode == '+966') { loginType = (nationalIDorFile.length == 10) ? 1 : 2; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index efa01d0..bac2470 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -627,8 +627,17 @@ class Utils { double width = 24, double height = 24, BoxFit fit = BoxFit.cover, + double? border, + double? borderRadius, }) { - return Image.asset(icon, width: width, height: height, fit: fit); + return Container( + decoration: BoxDecoration( + border: border != null ? Border.all(color: AppColors.whiteColor, width: border) : null, + borderRadius: border != null ? BorderRadius.circular(borderRadius ?? 0) : null, + ), + child: Image.asset(icon, width: width, height: height, fit: fit), + ); + // return Image.asset(icon, width: width, height: height, fit: fit, ); } /// Widget to build an SVG from network diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 96bdc0b..c034a69 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -206,38 +206,93 @@ class AuthenticationRepoImp implements AuthenticationRepo { int? patientID, int? loginType}) async { AppState appState = getIt.get(); + // if (isRegister) { + // newRequest["activationCode"] = activationCode ?? "0000"; + // newRequest["isSilentLogin"] = activationCode != null ? false : true; + // } else { + // newRequest.activationCode = activationCode ?? "0000"; + // newRequest.isSilentLogin = activationCode != null ? false : true; + // newRequest.projectOutSA = newRequest.zipCode == '966' ? false : true; + // newRequest.isDentalAllowedBackend = false; + // newRequest.forRegisteration = newRequest.isRegister ?? false; + // newRequest.isRegister = false; + // } + // Map familyRequest = {}; + // if (isFormFamilyFile) { + // AppState appState = getIt.get(); + // familyRequest = {}; + // familyRequest['PatientShareRequestID'] = patientShareRequestID; + // familyRequest['ResponseID'] = responseID; + // familyRequest['Status'] = 3; + // familyRequest["PatientID"] = appState.getAuthenticatedUser()!.patientId ?? 0; + // familyRequest["LogInTokenID"] = appState.getFamilyFileTokenID; + // familyRequest["activationCode"] = activationCode ?? "0000"; + // familyRequest["PatientMobileNumber"] = newRequest.patientMobileNumber; + // familyRequest["PatientIdentificationID"] = newRequest.patientIdentificationID; + // } + // Map switchRequest = {}; + // if (isSwitchUser) { + // switchRequest = newRequest.toJson(); + // + // switchRequest['PatientID'] = responseID; + // switchRequest['IsSilentLogin'] = true; + // switchRequest['LogInTokenID'] = null; + // switchRequest['SearchType'] = 2; + // if (loginType != 0) { + // switchRequest['SuperUser'] = patientID; + // switchRequest['DeviceToken'] = null; + // } else { + // switchRequest["LoginType"] = 2; + // } + // + // if (appState.getSuperUserID == responseID) { + // // switchRequest['LoginType'] = 3; + // switchRequest['PatientIdentificationID'] = ""; + // // switchRequest['ProjectOutSA'] = newRequest.zipCode == '966' ? false : true; + // switchRequest.remove('NationalID'); + // switchRequest.remove('isDentalAllowedBackend'); + // switchRequest.remove('ProjectOutSA'); + // switchRequest.remove('ForRegisteration'); + // appState.setSuperUserID = null; + // } + // } + if (isRegister) { newRequest["activationCode"] = activationCode ?? "0000"; - newRequest["isSilentLogin"] = activationCode != null ? false : true; + newRequest["isSilentLogin"] = activationCode == null; } else { newRequest.activationCode = activationCode ?? "0000"; - newRequest.isSilentLogin = activationCode != null ? false : true; - newRequest.projectOutSA = newRequest.zipCode == '966' ? false : true; + newRequest.isSilentLogin = activationCode == null; + newRequest.projectOutSA = newRequest.zipCode != '966'; newRequest.isDentalAllowedBackend = false; newRequest.forRegisteration = newRequest.isRegister ?? false; newRequest.isRegister = false; } + Map familyRequest = {}; if (isFormFamilyFile) { - AppState appState = getIt.get(); - familyRequest = {}; - familyRequest['PatientShareRequestID'] = patientShareRequestID; - familyRequest['ResponseID'] = responseID; - familyRequest['Status'] = 3; - familyRequest["PatientID"] = appState.getAuthenticatedUser()!.patientId ?? 0; - familyRequest["LogInTokenID"] = appState.getFamilyFileTokenID; - familyRequest["activationCode"] = activationCode ?? "0000"; - familyRequest["PatientMobileNumber"] = newRequest.patientMobileNumber; - familyRequest["PatientIdentificationID"] = newRequest.patientIdentificationID; + familyRequest = { + 'PatientShareRequestID': patientShareRequestID, + 'ResponseID': responseID, + 'Status': 3, + 'PatientID': appState.getAuthenticatedUser()?.patientId ?? 0, + 'LogInTokenID': appState.getFamilyFileTokenID, + 'activationCode': activationCode ?? "0000", + 'PatientMobileNumber': newRequest.patientMobileNumber, + 'PatientIdentificationID': newRequest.patientIdentificationID, + }; } + Map switchRequest = {}; if (isSwitchUser) { switchRequest = newRequest.toJson(); + switchRequest.addAll({ + 'PatientID': responseID, + 'IsSilentLogin': true, + 'LogInTokenID': null, + 'SearchType': 2, + }); - switchRequest['PatientID'] = responseID; - switchRequest['IsSilentLogin'] = true; - switchRequest['LogInTokenID'] = null; - switchRequest['SearchType'] = 2; if (loginType != 0) { switchRequest['SuperUser'] = patientID; switchRequest['DeviceToken'] = null; @@ -246,13 +301,8 @@ class AuthenticationRepoImp implements AuthenticationRepo { } if (appState.getSuperUserID == responseID) { - switchRequest['LoginType'] = 3; switchRequest['PatientIdentificationID'] = ""; - // switchRequest['ProjectOutSA'] = newRequest.zipCode == '966' ? false : true; - switchRequest.remove('NationalID'); - switchRequest.remove('isDentalAllowedBackend'); - switchRequest.remove('ProjectOutSA'); - switchRequest.remove('ForRegisteration'); + switchRequest.removeWhere((key, value) => ['NationalID', 'isDentalAllowedBackend', 'ProjectOutSA', 'ForRegisteration'].contains(key)); } } @@ -303,7 +353,6 @@ class AuthenticationRepoImp implements AuthenticationRepo { @override Future>> checkIfUserAgreed({required dynamic commonAuthanticatedRequest}) async { commonAuthanticatedRequest['Region'] = 1; - print("dsfsdd"); try { GenericApiModel? apiResponse; Failure? failure; @@ -338,7 +387,6 @@ class AuthenticationRepoImp implements AuthenticationRepo { @override Future>> getUserAgreementContent({required dynamic commonAuthanticatedRequest}) async { commonAuthanticatedRequest['Region'] = 1; - print("dsfsdd"); try { GenericApiModel? apiResponse; Failure? failure; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index b3f9617..467a476 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -366,7 +366,7 @@ class AuthenticationViewModel extends ChangeNotifier { isForRegister: isForRegister, patientOutSA: isForRegister ? isPatientOutsideSA(request: payload) - : selectedCountrySignup.countryCode == CountryEnum.saudiArabia + : selectedCountrySignup.countryCode == CountryEnum.saudiArabia.countryCode ? false : true, payload: payload, @@ -374,9 +374,6 @@ class AuthenticationViewModel extends ChangeNotifier { isFormFamilyFile: isFormFamilyFile, responseID: responseID); - - - // TODO: GET APP SMS SIGNATURE HERE request.sMSSignature = await getSignature(); @@ -434,18 +431,17 @@ class AuthenticationViewModel extends ChangeNotifier { return isUserComingForRegister; } - Future checkActivationCode( - {required String? activationCode, - required OTPTypeEnum otpTypeEnum, - required Function(String? message) onWrongActivationCode, - Function()? onResendActivation, - bool isFormFamilyFile = false, - dynamic patientShareRequestID, - dynamic responseID, - bool isSwitchUser =false, - int? patientID, - - }) async { + Future checkActivationCode({ + required String? activationCode, + required OTPTypeEnum otpTypeEnum, + required Function(String? message) onWrongActivationCode, + Function()? onResendActivation, + bool isFormFamilyFile = false, + dynamic patientShareRequestID, + dynamic responseID, + bool isSwitchUser = false, + int? patientID, + }) async { bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1); final request = RequestUtils.getCommonRequestWelcome( @@ -508,15 +504,15 @@ class AuthenticationViewModel extends ChangeNotifier { }); } else { final resultEither = await _authenticationRepo.checkActivationCodeRepo( - newRequest: CheckActivationCodeRegisterReq.fromJson(request), - activationCode: activationCode, - isRegister: false, - isFormFamilyFile: isFormFamilyFile, - patientShareRequestID: patientShareRequestID, - responseID: responseID, - isSwitchUser: isSwitchUser, - patientID: patientID, - loginType: _appState.getSuperUserID != null ? 0 : 2, + newRequest: CheckActivationCodeRegisterReq.fromJson(request), + activationCode: activationCode, + isRegister: false, + isFormFamilyFile: isFormFamilyFile, + patientShareRequestID: patientShareRequestID, + responseID: responseID, + isSwitchUser: isSwitchUser, + patientID: patientID, + loginType: _appState.getSuperUserID != null ? 0 : 2, ); resultEither.fold( @@ -531,8 +527,6 @@ class AuthenticationViewModel extends ChangeNotifier { LoaderBottomSheet.hideLoader(); await _dialogService.showCommonBottomSheetWithoutH(message: failure.message, label: LocaleKeys.notice.tr(), onOkPressed: () {}); }), (apiResponse) async { - print("API Response Data: ${apiResponse.data}"); - final activation = CheckActivationCode.fromJson(apiResponse.data as Map); if (activation.errorCode == '699') { @@ -551,17 +545,27 @@ class AuthenticationViewModel extends ChangeNotifier { return; } else { if (isFormFamilyFile) { - await navigateToFamilyFilePage(); - // _dialogService.showCommonBottomSheetWithoutH( - // message: "Family File Added Successfully", - // onOkPressed: () async { - // print("navigating to family file page"); - // - // }); + // await navigateToFamilyFilePage(); + MedicalFileViewModel medicalVm = getIt(); + if (!_appState.getIsChildLoggedIn) { + await medicalVm.getFamilyFiles(status: 0); + await medicalVm.getAllPendingRecordsByResponseId(); + _navigationService.popUntilNamed(AppRoutes.medicalFilePage); + } } else { if (activation.list != null && activation.list!.isNotEmpty) { - if(isSwitchUser){ - _appState.setSuperUserID = _appState.getAuthenticatedUser()?.patientId; + if (isSwitchUser) { + if (_appState.getIsChildLoggedIn) { + _appState.setSuperUserID = null; + _appState.setIsChildLoggedIn = false; + activation.list!.first.isParentUser = true; + } else { + _appState.setSuperUserID = _appState.getAuthenticatedUser()?.patientId; + _appState.setIsChildLoggedIn = true; + activation.list!.first.isParentUser = false; + } + } else { + activation.list!.first.isParentUser = true; } _appState.setAuthenticatedUser(activation.list!.first); _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); @@ -576,7 +580,14 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.getSelectDeviceByImeiRespModelElement!.logInType = loginTypeEnum.toInt; } LoaderBottomSheet.hideLoader(); - insertPatientIMEIData(loginTypeEnum.toInt); +// + if (!isSwitchUser && !_appState.getIsChildLoggedIn) { + MedicalFileViewModel medicalVm = getIt(); + insertPatientIMEIData(loginTypeEnum.toInt); + await medicalVm.getFamilyFiles(status: 0); //TODO: Remove status: 1 by Aamir Need to Discuss With Sultan + // medicalVm.getAllPendingRecordsByResponseId(); + } + await clearDefaultInputValues(); if (isUserAgreedBefore) { navigateToHomeScreen(); diff --git a/lib/features/authentication/models/resp_models/authenticated_user_resp_model.dart b/lib/features/authentication/models/resp_models/authenticated_user_resp_model.dart index e6a8e17..8209a0f 100644 --- a/lib/features/authentication/models/resp_models/authenticated_user_resp_model.dart +++ b/lib/features/authentication/models/resp_models/authenticated_user_resp_model.dart @@ -70,6 +70,8 @@ class AuthenticatedUser { dynamic authenticatedUserPatientType; dynamic authenticatedUserStatus; int? superUser; + bool? isParentUser; + AuthenticatedUser({ this.setupId, this.patientType, @@ -139,7 +141,8 @@ class AuthenticatedUser { this.authenticatedUserPatientPayType, this.authenticatedUserPatientType, this.authenticatedUserStatus, - this.superUser + this.superUser, + this.isParentUser, }); factory AuthenticatedUser.fromRawJson(String str) => AuthenticatedUser.fromJson(json.decode(str)); @@ -147,148 +150,150 @@ class AuthenticatedUser { String toRawJson() => json.encode(toJson()); factory AuthenticatedUser.fromJson(Map json) => AuthenticatedUser( - setupId: json["SetupID"], - patientType: json["PatientType"], - patientId: json["PatientID"], - firstName: json["FirstName"], - middleName: json["MiddleName"], - lastName: json["LastName"], - firstNameN: json["FirstNameN"], - middleNameN: json["MiddleNameN"], - lastNameN: json["LastNameN"], - relationshipId: json["RelationshipID"], - gender: json["Gender"], - dateofBirth: json["DateofBirth"], - dateofBirthN: json["DateofBirthN"], - nationalityId: json["NationalityID"], - phoneResi: json["PhoneResi"], - phoneOffice: json["PhoneOffice"], - mobileNumber: json["MobileNumber"], - faxNumber: json["FaxNumber"], - emailAddress: json["EmailAddress"], - bloodGroup: json["BloodGroup"], - rhFactor: json["RHFactor"], - isEmailAlertRequired: json["IsEmailAlertRequired"], - isSmsAlertRequired: json["IsSMSAlertRequired"], - preferredLanguage: json["PreferredLanguage"], - isPrivilegedMember: json["IsPrivilegedMember"], - memberId: json["MemberID"], - expiryDate: json["ExpiryDate"], - isHmgEmployee: json["IsHmgEmployee"], - employeeId: json["EmployeeID"], - emergencyContactName: json["EmergencyContactName"], - emergencyContactNo: json["EmergencyContactNo"], - patientPayType: json["PatientPayType"], - dhccPatientRefId: json["DHCCPatientRefID"], - isPatientDummy: json["IsPatientDummy"], - status: json["Status"], - isStatusCleared: json["IsStatusCleared"], - patientIdentificationType: json["PatientIdentificationType"], - patientIdentificationNo: json["PatientIdentificationNo"], - projectId: json["ProjectID"], - infoSourceId: json["InfoSourceID"], - address: json["Address"], - age: json["Age"], - ageDesc: json["AgeDesc"], - areaId: json["AreaID"], - crsVerificationStatus: json["CRSVerificationStatus"], - crsVerificationStatusDesc: json["CRSVerificationStatusDesc"], - crsVerificationStatusDescN: json["CRSVerificationStatusDescN"], - createdBy: json["CreatedBy"], - genderDescription: json["GenderDescription"], - healthIdFromNhicViaVida: json["HealthIDFromNHICViaVida"], - ir: json["IR"], - isoCityId: json["ISOCityID"], - isoCountryId: json["ISOCountryID"], - isVerfiedFromNhic: json["IsVerfiedFromNHIC"], - listPrivilege: json["ListPrivilege"] == null ? [] : List.from(json["ListPrivilege"]!.map((x) => ListPrivilege.fromJson(x))), - marital: json["Marital"], - occupationId: json["OccupationID"], - outSa: json["OutSA"], - poBox: json["POBox"], - receiveHealthSummaryReport: json["ReceiveHealthSummaryReport"], - sourceType: json["SourceType"], - strDateofBirth: json["StrDateofBirth"], - tempAddress: json["TempAddress"], - zipCode: json["ZipCode"], - eHealthIdField: json["eHealthIDField"], - authenticatedUserPatientPayType: json["patientPayType"], - authenticatedUserPatientType: json["patientType"], - authenticatedUserStatus: json["status"], - superUser: json["superUser"], - ); + setupId: json["SetupID"], + patientType: json["PatientType"], + patientId: json["PatientID"], + firstName: json["FirstName"], + middleName: json["MiddleName"], + lastName: json["LastName"], + firstNameN: json["FirstNameN"], + middleNameN: json["MiddleNameN"], + lastNameN: json["LastNameN"], + relationshipId: json["RelationshipID"], + gender: json["Gender"], + dateofBirth: json["DateofBirth"], + dateofBirthN: json["DateofBirthN"], + nationalityId: json["NationalityID"], + phoneResi: json["PhoneResi"], + phoneOffice: json["PhoneOffice"], + mobileNumber: json["MobileNumber"], + faxNumber: json["FaxNumber"], + emailAddress: json["EmailAddress"], + bloodGroup: json["BloodGroup"], + rhFactor: json["RHFactor"], + isEmailAlertRequired: json["IsEmailAlertRequired"], + isSmsAlertRequired: json["IsSMSAlertRequired"], + preferredLanguage: json["PreferredLanguage"], + isPrivilegedMember: json["IsPrivilegedMember"], + memberId: json["MemberID"], + expiryDate: json["ExpiryDate"], + isHmgEmployee: json["IsHmgEmployee"], + employeeId: json["EmployeeID"], + emergencyContactName: json["EmergencyContactName"], + emergencyContactNo: json["EmergencyContactNo"], + patientPayType: json["PatientPayType"], + dhccPatientRefId: json["DHCCPatientRefID"], + isPatientDummy: json["IsPatientDummy"], + status: json["Status"], + isStatusCleared: json["IsStatusCleared"], + patientIdentificationType: json["PatientIdentificationType"], + patientIdentificationNo: json["PatientIdentificationNo"], + projectId: json["ProjectID"], + infoSourceId: json["InfoSourceID"], + address: json["Address"], + age: json["Age"], + ageDesc: json["AgeDesc"], + areaId: json["AreaID"], + crsVerificationStatus: json["CRSVerificationStatus"], + crsVerificationStatusDesc: json["CRSVerificationStatusDesc"], + crsVerificationStatusDescN: json["CRSVerificationStatusDescN"], + createdBy: json["CreatedBy"], + genderDescription: json["GenderDescription"], + healthIdFromNhicViaVida: json["HealthIDFromNHICViaVida"], + ir: json["IR"], + isoCityId: json["ISOCityID"], + isoCountryId: json["ISOCountryID"], + isVerfiedFromNhic: json["IsVerfiedFromNHIC"], + listPrivilege: json["ListPrivilege"] == null ? [] : List.from(json["ListPrivilege"]!.map((x) => ListPrivilege.fromJson(x))), + marital: json["Marital"], + occupationId: json["OccupationID"], + outSa: json["OutSA"], + poBox: json["POBox"], + receiveHealthSummaryReport: json["ReceiveHealthSummaryReport"], + sourceType: json["SourceType"], + strDateofBirth: json["StrDateofBirth"], + tempAddress: json["TempAddress"], + zipCode: json["ZipCode"], + eHealthIdField: json["eHealthIDField"], + authenticatedUserPatientPayType: json["patientPayType"], + authenticatedUserPatientType: json["patientType"], + authenticatedUserStatus: json["status"], + superUser: json["superUser"], + isParentUser: json["isParentUser"] ?? false, + ); Map toJson() => { - "SetupID": setupId, - "PatientType": patientType, - "PatientID": patientId, - "FirstName": firstName, - "MiddleName": middleName, - "LastName": lastName, - "FirstNameN": firstNameN, - "MiddleNameN": middleNameN, - "LastNameN": lastNameN, - "RelationshipID": relationshipId, - "Gender": gender, - "DateofBirth": dateofBirth, - "DateofBirthN": dateofBirthN, - "NationalityID": nationalityId, - "PhoneResi": phoneResi, - "PhoneOffice": phoneOffice, - "MobileNumber": mobileNumber, - "FaxNumber": faxNumber, - "EmailAddress": emailAddress, - "BloodGroup": bloodGroup, - "RHFactor": rhFactor, - "IsEmailAlertRequired": isEmailAlertRequired, - "IsSMSAlertRequired": isSmsAlertRequired, - "PreferredLanguage": preferredLanguage, - "IsPrivilegedMember": isPrivilegedMember, - "MemberID": memberId, - "ExpiryDate": expiryDate, - "IsHmgEmployee": isHmgEmployee, - "EmployeeID": employeeId, - "EmergencyContactName": emergencyContactName, - "EmergencyContactNo": emergencyContactNo, - "PatientPayType": patientPayType, - "DHCCPatientRefID": dhccPatientRefId, - "IsPatientDummy": isPatientDummy, - "Status": status, - "IsStatusCleared": isStatusCleared, - "PatientIdentificationType": patientIdentificationType, - "PatientIdentificationNo": patientIdentificationNo, - "ProjectID": projectId, - "InfoSourceID": infoSourceId, - "Address": address, - "Age": age, - "AgeDesc": ageDesc, - "AreaID": areaId, - "CRSVerificationStatus": crsVerificationStatus, - "CRSVerificationStatusDesc": crsVerificationStatusDesc, - "CRSVerificationStatusDescN": crsVerificationStatusDescN, - "CreatedBy": createdBy, - "GenderDescription": genderDescription, - "HealthIDFromNHICViaVida": healthIdFromNhicViaVida, - "IR": ir, - "ISOCityID": isoCityId, - "ISOCountryID": isoCountryId, - "IsVerfiedFromNHIC": isVerfiedFromNhic, - "ListPrivilege": listPrivilege == null ? [] : List.from(listPrivilege!.map((x) => x.toJson())), - "Marital": marital, - "OccupationID": occupationId, - "OutSA": outSa, - "POBox": poBox, - "ReceiveHealthSummaryReport": receiveHealthSummaryReport, - "SourceType": sourceType, - "StrDateofBirth": strDateofBirth, - "TempAddress": tempAddress, - "ZipCode": zipCode, - "eHealthIDField": eHealthIdField, - "patientPayType": authenticatedUserPatientPayType, - "patientType": authenticatedUserPatientType, - "status": authenticatedUserStatus, - "superUser": superUser, - }; + "SetupID": setupId, + "PatientType": patientType, + "PatientID": patientId, + "FirstName": firstName, + "MiddleName": middleName, + "LastName": lastName, + "FirstNameN": firstNameN, + "MiddleNameN": middleNameN, + "LastNameN": lastNameN, + "RelationshipID": relationshipId, + "Gender": gender, + "DateofBirth": dateofBirth, + "DateofBirthN": dateofBirthN, + "NationalityID": nationalityId, + "PhoneResi": phoneResi, + "PhoneOffice": phoneOffice, + "MobileNumber": mobileNumber, + "FaxNumber": faxNumber, + "EmailAddress": emailAddress, + "BloodGroup": bloodGroup, + "RHFactor": rhFactor, + "IsEmailAlertRequired": isEmailAlertRequired, + "IsSMSAlertRequired": isSmsAlertRequired, + "PreferredLanguage": preferredLanguage, + "IsPrivilegedMember": isPrivilegedMember, + "MemberID": memberId, + "ExpiryDate": expiryDate, + "IsHmgEmployee": isHmgEmployee, + "EmployeeID": employeeId, + "EmergencyContactName": emergencyContactName, + "EmergencyContactNo": emergencyContactNo, + "PatientPayType": patientPayType, + "DHCCPatientRefID": dhccPatientRefId, + "IsPatientDummy": isPatientDummy, + "Status": status, + "IsStatusCleared": isStatusCleared, + "PatientIdentificationType": patientIdentificationType, + "PatientIdentificationNo": patientIdentificationNo, + "ProjectID": projectId, + "InfoSourceID": infoSourceId, + "Address": address, + "Age": age, + "AgeDesc": ageDesc, + "AreaID": areaId, + "CRSVerificationStatus": crsVerificationStatus, + "CRSVerificationStatusDesc": crsVerificationStatusDesc, + "CRSVerificationStatusDescN": crsVerificationStatusDescN, + "CreatedBy": createdBy, + "GenderDescription": genderDescription, + "HealthIDFromNHICViaVida": healthIdFromNhicViaVida, + "IR": ir, + "ISOCityID": isoCityId, + "ISOCountryID": isoCountryId, + "IsVerfiedFromNHIC": isVerfiedFromNhic, + "ListPrivilege": listPrivilege == null ? [] : List.from(listPrivilege!.map((x) => x.toJson())), + "Marital": marital, + "OccupationID": occupationId, + "OutSA": outSa, + "POBox": poBox, + "ReceiveHealthSummaryReport": receiveHealthSummaryReport, + "SourceType": sourceType, + "StrDateofBirth": strDateofBirth, + "TempAddress": tempAddress, + "ZipCode": zipCode, + "eHealthIDField": eHealthIdField, + "patientPayType": authenticatedUserPatientPayType, + "patientType": authenticatedUserPatientType, + "status": authenticatedUserStatus, + "superUser": superUser, + "isParentUser": isParentUser, + }; } class ListPrivilege { @@ -309,16 +314,16 @@ class ListPrivilege { String toRawJson() => json.encode(toJson()); factory ListPrivilege.fromJson(Map json) => ListPrivilege( - id: json["ID"], - serviceName: json["ServiceName"], - previlege: json["Previlege"], - region: json["Region"], - ); + id: json["ID"], + serviceName: json["ServiceName"], + previlege: json["Previlege"], + region: json["Region"], + ); Map toJson() => { - "ID": id, - "ServiceName": serviceName, - "Previlege": previlege, - "Region": region, - }; + "ID": id, + "ServiceName": serviceName, + "Previlege": previlege, + "Region": region, + }; } diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index 9c18696..994f9f9 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -29,6 +29,10 @@ abstract class MedicalFileRepo { Future>>> getAllPendingRecordsByResponseId({required Map request}); Future>> addFamilyFile({required dynamic request}); + + Future>> removeFamilyFile({required int? id}); + + Future>> acceptRejectFamilyFile({required int? id, required int? status}); } class MedicalFileRepoImp implements MedicalFileRepo { @@ -328,10 +332,6 @@ class MedicalFileRepoImp implements MedicalFileRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final list = response['GetAllPendingRecordsList']; - // 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>( @@ -384,4 +384,74 @@ class MedicalFileRepoImp implements MedicalFileRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> removeFamilyFile({required int? id}) async { + Map request = {}; + request["ID"] = id; + request['IsActive'] = false; + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.removeFileFromFamilyMembers, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> acceptRejectFamilyFile({required int? id, required int? status}) async { + Map request = {}; + request["ID"] = id; + request['Status'] = status; + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.acceptAndRejectFamilyFile, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 64075b4..df8027f 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -83,7 +84,7 @@ class MedicalFileViewModel extends ChangeNotifier { void onFamilyFileTabChange(int index) { setSelectedFamilyFileTabIndex = index; if (index == 1) { - getAllPendingRecordsByResponseId(); + // getAllPendingRecordsByResponseId(); } notifyListeners(); } @@ -270,148 +271,50 @@ class MedicalFileViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 2) { _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { - patientFamilyFiles = apiResponse.data!; if (apiResponse.data != null) { - 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, - age: _appState.getAuthenticatedUser()!.age, - mobileNumber: _appState.getAuthenticatedUser()!.mobileNumber, - patientIdenficationNumber: _appState.getAuthenticatedUser()!.patientIdentificationNo, - emaiLAddress: _appState.getAuthenticatedUser()!.emailAddress, - genderDescription: _appState.getAuthenticatedUser()!.genderDescription, - ), + // Add current user as the first active family file + final currentUser = _appState.getAuthenticatedUser()!; + final currentUserFamilyFile = FamilyFileResponseModelLists( + patientId: currentUser.patientId, + patientName: '${currentUser.firstName!} ${currentUser.lastName!}', + isActive: true, + gender: currentUser.gender!, + responseId: currentUser.patientId, + age: currentUser.age, + mobileNumber: currentUser.mobileNumber, + patientIdenficationNumber: currentUser.patientIdentificationNo, + emaiLAddress: currentUser.emailAddress, + genderDescription: currentUser.genderDescription, ); - // final List activeFamilyFiles = []; - // final List tempPendingFamilyFiles = []; - // for (var element in apiResponse.data!) { - // if (element.status != null && element.status == FamilyFileEnum.active.toInt) { - // activeFamilyFiles.add(FamilyFileResponseModelLists( - // patientId: element.patientId, - // patientName: element.patientName!, - // isActive: element.status == FamilyFileEnum.active.toInt ? true : false, - // gender: element.gender!, - // responseId: element.patientId, - // mobileNumber: element.mobileNumber, - // age: element.age, - // patientIdenficationNumber: element.patientIdenficationNumber, - // relationship: element.relationship, - // relationshipId: element.relationshipId, - // relationshipN: element.relationshipN, - // status: element.status, - // statusDescription: element.statusDescription, - // createdOn: element.createdOn, - // editedOn: element.editedOn, - // patientDataVerified: element.patientDataVerified, - // regionId: element.regionId, - // familyRegionId: element.familyRegionId, - // genderDescription: element.genderDescription, - // genderImage: element.genderImage, - // emaiLAddress: element.emaiLAddress)); - // } - // - // if (element.status != null && element.status == FamilyFileEnum.pending.toInt) { - // tempPendingFamilyFiles.add(FamilyFileResponseModelLists( - // patientId: element.patientId, - // patientName: element.patientName!, - // isActive: element.status == FamilyFileEnum.active.toInt ? true : false, - // gender: element.gender!, - // responseId: element.patientId, - // mobileNumber: element.mobileNumber, - // age: element.age, - // patientIdenficationNumber: element.patientIdenficationNumber, - // relationship: element.relationship, - // relationshipId: element.relationshipId, - // relationshipN: element.relationshipN, - // status: element.status, - // statusDescription: element.statusDescription, - // createdOn: element.createdOn, - // editedOn: element.editedOn, - // patientDataVerified: element.patientDataVerified, - // regionId: element.regionId, - // familyRegionId: element.familyRegionId, - // genderDescription: element.genderDescription, - // genderImage: element.genderImage, - // emaiLAddress: element.emaiLAddress)); - // } - // } - // patientFamilyFiles.addAll(activeFamilyFiles.where((element) => !patientFamilyFiles.any((e) => e.patientId == element.patientId))); - // pendingFamilyFiles.addAll(tempPendingFamilyFiles); + // Clear and start fresh with current user + patientFamilyFiles.clear(); + patientFamilyFiles.add(currentUserFamilyFile); final List activeFamilyFiles = []; - final List tempPendingFamilyFiles = []; - // final Set pendingIds = {}; - - // for (var element in apiResponse.data!) { - // if (element.status != null && element.status == FamilyFileEnum.pending.toInt) { - // tempPendingFamilyFiles.add(FamilyFileResponseModelLists( - // patientId: element.patientId, - // patientName: element.patientName!, - // isActive: false, - // gender: element.gender!, - // responseId: element.patientId, - // mobileNumber: element.mobileNumber, - // age: element.age, - // patientIdenficationNumber: element.patientIdenficationNumber, - // relationship: element.relationship, - // relationshipId: element.relationshipId, - // relationshipN: element.relationshipN, - // status: element.status, - // statusDescription: element.statusDescription, - // createdOn: element.createdOn, - // editedOn: element.editedOn, - // patientDataVerified: element.patientDataVerified, - // regionId: element.regionId, - // familyRegionId: element.familyRegionId, - // genderDescription: element.genderDescription, - // genderImage: element.genderImage, - // emaiLAddress: element.emaiLAddress)); - // } else if (element.status != null && element.status == FamilyFileEnum.active.toInt) { - // activeFamilyFiles.add(FamilyFileResponseModelLists( - // patientId: element.patientId, - // patientName: element.patientName!, - // isActive: element.status == FamilyFileEnum.active.toInt ? true : false, - // gender: element.gender!, - // responseId: element.patientId, - // mobileNumber: element.mobileNumber, - // age: element.age, - // patientIdenficationNumber: element.patientIdenficationNumber, - // relationship: element.relationship, - // relationshipId: element.relationshipId, - // relationshipN: element.relationshipN, - // status: element.status, - // statusDescription: element.statusDescription, - // createdOn: element.createdOn, - // editedOn: element.editedOn, - // patientDataVerified: element.patientDataVerified, - // regionId: element.regionId, - // familyRegionId: element.familyRegionId, - // genderDescription: element.genderDescription, - // genderImage: element.genderImage, - // emaiLAddress: element.emaiLAddress)); - // } - // } + final List pendingFamilyFiles = []; for (var element in apiResponse.data!) { - if (element.status == null) continue; + if (element.status == null) { + continue; + } - final isPending = element.status == FamilyFileEnum.pending.toInt; + final isPending = element.status == FamilyFileEnum.pending.toInt || element.status == FamilyFileEnum.rejected.toInt; final isActive = element.status == FamilyFileEnum.active.toInt; - if (!isPending && !isActive) continue; + print("====== Element Status: ${element.status}, isPending: $isPending, isActive: $isActive ============"); + + if (!isPending && !isActive) { + continue; + } final familyFile = FamilyFileResponseModelLists( + id: element.id, patientId: element.patientId, patientName: element.patientName!, - isActive: isActive, + isActive: element.isActive, gender: element.gender!, - responseId: element.patientId, + responseId: element.responseId, mobileNumber: element.mobileNumber, age: element.age, patientIdenficationNumber: element.patientIdenficationNumber, @@ -431,14 +334,22 @@ class MedicalFileViewModel extends ChangeNotifier { ); if (isPending) { - tempPendingFamilyFiles.add(familyFile); - } else if (isActive) { + familyFile.isRequestFromMySide = true; + pendingFamilyFiles.add(familyFile); + } + if (isActive) { activeFamilyFiles.add(familyFile); } } - patientFamilyFiles.addAll(activeFamilyFiles.where((element) => patientFamilyFiles.every((e) => e.patientId != element.patientId || e.status != FamilyFileEnum.active.toInt))); - pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => pendingFamilyFiles.every((e) => e.patientId != element.patientId))); + for (var activeFile in activeFamilyFiles) { + if (!patientFamilyFiles.any((e) => e.responseId == activeFile.responseId)) { + patientFamilyFiles.add(activeFile); + } + } + + this.pendingFamilyFiles.clear(); + this.pendingFamilyFiles.addAll(pendingFamilyFiles); } notifyListeners(); @@ -463,11 +374,12 @@ class MedicalFileViewModel extends ChangeNotifier { if (apiResponse.data != null) { final List tempPendingFamilyFiles = []; for (var element in apiResponse.data!) { - if (element.status != null && element.status == FamilyFileEnum.pending.toInt) { + if (element.status != null && element.status == FamilyFileEnum.pending.toInt || element.status == FamilyFileEnum.active.toInt) { tempPendingFamilyFiles.add(FamilyFileResponseModelLists( + id: element.id, patientId: element.patientId, patientName: element.patientName!, - isActive: element.status == FamilyFileEnum.active.toInt ? true : false, + isActive: element.status, gender: element.gender, responseId: element.patientId, mobileNumber: element.mobileNumber, @@ -488,7 +400,11 @@ class MedicalFileViewModel extends ChangeNotifier { emaiLAddress: element.emaiLAddress)); } } + // pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => !pendingFamilyFiles.any((e) => e.responseId == element.responseId))); pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => !pendingFamilyFiles.any((e) => e.patientId == element.patientId))); + + print("====== Pending Family Length: ${pendingFamilyFiles.length} ============"); + print("====== Pending Family Files: ${jsonEncode(pendingFamilyFiles)} ============"); } notifyListeners(); } @@ -496,42 +412,6 @@ class MedicalFileViewModel extends ChangeNotifier { ); } - // Future switchFamilyFiles({Function(dynamic)? onSuccess, Function(String)? onError}) async { - // // final result = await medicalFileRepo.getPatientFamilyFiles(); - // final result = await medicalFileRepo.getPatientFamilyFiles(3, _appState.superUserID != null ? _appState.superUserID! : _appState.getAuthenticatedUser()!.patientId!); - // - // 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!, - // age: _appState.getAuthenticatedUser()!.age, - // mobileNumber: _appState.getAuthenticatedUser()!.mobileNumber, - // responseId: _appState.getAuthenticatedUser()!.patientId), - // ); - // notifyListeners(); - // if (onSuccess != null) { - // onSuccess(apiResponse); - // } - // } - // }, - // ); - // } - Future switchFamilyFiles({Function(dynamic)? onSuccess, int? responseID, int? patientID, String? phoneNumber, Function(String)? onError}) async { authVM.phoneNumberController.text = phoneNumber!.startsWith("0") ? phoneNumber.replaceFirst("0", "") : phoneNumber; @@ -555,34 +435,107 @@ class MedicalFileViewModel extends ChangeNotifier { final resultEither = await medicalFileRepo.addFamilyFile(request: request.toJson()); resultEither.fold((failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) async { - if (apiResponse != null && apiResponse.data != null) { - request.isPatientExcluded = apiResponse.data["IsPatientExcluded"]; - request.responseID = apiResponse.data["ReponseID"]; + if (apiResponse.messageStatus == 2) { LoaderBottomSheet.hideLoader(); - _dialogService.showExceptionBottomSheet( - message: apiResponse.data['Message'], + _dialogService.showErrorBottomSheet( + message: apiResponse.errorMessage!, onOkPressed: () { - LoaderBottomSheet.showLoader(); - authVM.sendActivationCode( - otpTypeEnum: otpTypeEnum, - nationalIdOrFileNumber: request.sharedPatientIdentificationId!, - phoneNumber: request.sharedPatientMobileNumber!, - isForRegister: false, - isExcludedUser: apiResponse.data['IsPatientExcluded'], - responseID: apiResponse.data["ReponseID"], - isFormFamilyFile: true); - }, - onCancelPressed: () { navigationService.pop(); }); + } else if (apiResponse.messageStatus == 1) { + if (apiResponse.data != null) { + request.isPatientExcluded = apiResponse.data["IsPatientExcluded"]; + request.responseID = apiResponse.data["ReponseID"]; + LoaderBottomSheet.hideLoader(); + _dialogService.showExceptionBottomSheet( + message: apiResponse.data["Message"], + onOkPressed: () { + LoaderBottomSheet.showLoader(); + authVM.sendActivationCode( + otpTypeEnum: otpTypeEnum, + nationalIdOrFileNumber: request.sharedPatientIdentificationId!, + phoneNumber: request.sharedPatientMobileNumber!, + isForRegister: false, + isExcludedUser: apiResponse.data['IsPatientExcluded'], + responseID: apiResponse.data["ReponseID"], + isFormFamilyFile: true); + }, + onCancelPressed: () { + navigationService.pop(); + }); + } } }); } Future handleFamilyFileRequestOTPVerification() async { LoaderBottomSheet.showLoader(); - await getFamilyFiles(); - await getAllPendingRecordsByResponseId(); + if (!_appState.getIsChildLoggedIn) { + await getFamilyFiles(status: 0); + await getAllPendingRecordsByResponseId(); + } + LoaderBottomSheet.hideLoader(); } + + Future removeFileFromFamilyMembers({int? id}) async { + NavigationService navigationService = getIt.get(); + _dialogService.showExceptionBottomSheet( + message: "Remove this member?", + onOkPressed: () async { + LoaderBottomSheet.showLoader(); + final result = await medicalFileRepo.removeFamilyFile(id: id); + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + LoaderBottomSheet.hideLoader(); + _dialogService.showErrorBottomSheet( + message: apiResponse.errorMessage!, + onOkPressed: () { + navigationService.pop(); + }); + } else if (apiResponse.messageStatus == 1) { + patientFamilyFiles.removeWhere((element) => element.id == id); + LoaderBottomSheet.hideLoader(); + notifyListeners(); + navigationService.pop(); + } + }, + ); + }, + onCancelPressed: () { + navigationService.pop(); + }); + } + + Future acceptRejectFileFromFamilyMembers({int? id, int? status}) async { + NavigationService navigationService = getIt.get(); + LoaderBottomSheet.showLoader(); + final result = await medicalFileRepo.acceptRejectFamilyFile(id: id, status: status); + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + LoaderBottomSheet.hideLoader(); + _dialogService.showErrorBottomSheet( + message: apiResponse.errorMessage!, + onOkPressed: () { + navigationService.pop(); + }); + } else if (apiResponse.messageStatus == 1) { + // FamilyFileResponseModelLists moveProfile = pendingFamilyFiles.firstWhere((element) => element.id == patientID); + // moveProfile.status = 3; + // moveProfile.statusDescription = "Approved"; + // patientFamilyFiles.add(moveProfile); + pendingFamilyFiles.removeWhere((element) => element.id == id); + //TODO: Call Api Here To Load Family Members + getFamilyFiles(status: 0); + getAllPendingRecordsByResponseId(); + LoaderBottomSheet.hideLoader(); + onFamilyFileTabChange(0); + } + }, + ); + } } diff --git a/lib/features/medical_file/models/family_file_response_model.dart b/lib/features/medical_file/models/family_file_response_model.dart index 82fe3d8..f4aa557 100644 --- a/lib/features/medical_file/models/family_file_response_model.dart +++ b/lib/features/medical_file/models/family_file_response_model.dart @@ -23,83 +23,90 @@ class FamilyFileResponseModelLists { String? patientIdenficationNumber; String? patientName; String? statusDescription; + bool? isSuperUser = false; + bool? isRequestFromMySide; - 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, - }); + 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, + this.isSuperUser, + this.isRequestFromMySide}); 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"], - ); + 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"], + isSuperUser: json["isSuperUser"] ?? false, + isRequestFromMySide: json["isRequestFromMySide"] ?? false, + ); 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, - }; + "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, + "isSuperUser": isSuperUser, + "isRequestFromMySide": isRequestFromMySide, + }; } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index cb2f76f..da19747 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -16,6 +16,7 @@ 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/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; @@ -81,6 +82,7 @@ class _LandingPageState extends State { myAppointmentsViewModel.getPatientAppointments(true, false); myAppointmentsViewModel.getPatientMyDoctors(); prescriptionsViewModel.initPrescriptionsViewModel(); + } }); super.initState(); diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 4b5259f..b87d657 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointme import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_reports_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; @@ -80,7 +81,7 @@ class _MedicalFilePageState extends State { medicalFileViewModel.setIsPatientSickLeaveListLoading(true); medicalFileViewModel.getPatientSickLeaveList(); if (appState.getSuperUserID == null) { - medicalFileViewModel.getFamilyFiles(status: 3); //TODO: Remove status: 1 by Aamir Need to Discuss With Sultan + medicalFileViewModel.getFamilyFiles(status: 0); //TODO: Remove status: 1 by Aamir Need to Discuss With Sultan medicalFileViewModel.getAllPendingRecordsByResponseId(); //TODO: Added By Aamir } @@ -97,121 +98,139 @@ class _MedicalFilePageState extends State { medicalFileViewModel = Provider.of(context, listen: false); bookAppointmentsViewModel = Provider.of(context, listen: false); NavigationService navigationService = getIt.get(); - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: LocaleKeys.medicalFile.tr(context: context), - isLeading: false, - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - TextInputWidget( - labelText: LocaleKeys.search.tr(context: context), - hintText: "Type any record", - controller: TextEditingController(), - keyboardType: TextInputType.number, - isEnable: true, - prefix: null, - autoFocus: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), - leadingIcon: AppAssets.student_card, - ).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 16.h), - 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( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 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, - children: [ - "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true), - SizedBox(height: 4.h), - Wrap( - direction: Axis.horizontal, - spacing: 4.h, - runSpacing: 4.h, - children: [ - AppCustomChipWidget( - icon: AppAssets.file_icon, - labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}", - onChipTap: () { - navigationService.pushPage( - page: FamilyMedicalScreen( - profiles: medicalFileViewModel.patientFamilyFiles, - onSelect: (FamilyFileResponseModelLists p1) {}, - )); - }, - ), - AppCustomChipWidget( - icon: AppAssets.checkmark_icon, - labelText: LocaleKeys.verified.tr(context: context), - iconColor: AppColors.successColor, - ), - ], - ), - ], - ) - ], - ), - SizedBox(height: 16.h), - Divider(color: AppColors.dividerColor, height: 1.h), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 4.h, - runSpacing: 4.h, - children: [ - AppCustomChipWidget( - labelText: "${appState.getAuthenticatedUser()!.age} Years Old", - ), - AppCustomChipWidget( - icon: AppAssets.blood_icon, - labelText: "${LocaleKeys.bloodType.tr(context: context)}: ${appState.getUserBloodGroup}", - iconColor: AppColors.primaryRedColor, - ), - ], - ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 16.h), - Consumer(builder: (context, medicalFileVM, child) { - return Column( + return CollapsingListView( + title: "Medical File".needTranslation, + trailing: Row( + children: [ + Wrap(spacing: -15, children: [ + Utils.buildImgWithAssets(icon: AppAssets.babyGirlImg, width: 32.h, height: 32.h, border: 1.5.h, borderRadius: 50.h), + Utils.buildImgWithAssets(icon: AppAssets.femaleImg, width: 32.h, height: 32.h, border: 1.5.h, borderRadius: 50.h), + Utils.buildImgWithAssets(icon: AppAssets.male_img, width: 32.h, height: 32.h, border: 1.5.h, borderRadius: 50.h), + ]), + SizedBox(width: 4.h), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down) + ], + ).onPress(() { + DialogService dialogService = getIt.get(); + dialogService.showFamilyBottomSheetWithoutH( + label: "Who do you want to book for?".needTranslation, + message: "This clinic or doctor is only available for the below eligible profiles.".needTranslation, + onSwitchPress: (FamilyFileResponseModelLists profile) { + medicalFileViewModel.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + }, + profiles: medicalFileViewModel.patientFamilyFiles); + }), + isLeading: false, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: "Type any record".needTranslation, + controller: TextEditingController(), + keyboardType: TextInputType.number, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + leadingIcon: AppAssets.student_card, + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 16.h), + 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: [ - CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), - tabs: [ - CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation), - CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)), - CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation), - CustomTabBarModel(AppAssets.more, "More".needTranslation), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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, + children: [ + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" + .toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1), + SizedBox(height: 4.h), + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.file_icon, + labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}", + onChipTap: () { + navigationService.pushPage( + page: FamilyMedicalScreen( + profiles: medicalFileViewModel.patientFamilyFiles, + onSelect: (FamilyFileResponseModelLists p1) {}, + )); + }, + ), + AppCustomChipWidget( + icon: AppAssets.checkmark_icon, + labelText: LocaleKeys.verified.tr(context: context), + iconColor: AppColors.successColor, + ), + ], + ), + ], + ) + ], + ), + SizedBox(height: 16.h), + Divider(color: AppColors.dividerColor, height: 1.h), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: "${appState.getAuthenticatedUser()!.age} Years Old", + ), + AppCustomChipWidget( + icon: AppAssets.blood_icon, + labelText: "${LocaleKeys.bloodType.tr(context: context)}: ${appState.getUserBloodGroup}", + iconColor: AppColors.primaryRedColor, + ), ], - onTabChange: (index) { - medicalFileVM.onTabChanged(index); - }, - ).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 24.h), - getSelectedTabData(medicalFileVM.selectedTabIndex), + ), ], - ); - }), - ], - ), + ), + ), + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 16.h), + Consumer(builder: (context, medicalFileVM, child) { + return Column( + children: [ + CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + tabs: [ + CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation), + CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)), + CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation), + CustomTabBarModel(AppAssets.more, "More".needTranslation), + ], + onTabChange: (index) { + medicalFileVM.onTabChanged(index); + }, + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 24.h), + getSelectedTabData(medicalFileVM.selectedTabIndex), + ], + ); + }), + ], ), ), ); @@ -576,28 +595,28 @@ class _MedicalFilePageState extends State { horizontalOffset: 100.0, child: FadeInAnimation( child: SizedBox( - width: 80.h, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Image.network( - myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, - width: 64.h, - height: 64.h, - fit: BoxFit.fill, - ).circle(100).toShimmer2(isShow: false, radius: 50.h), - SizedBox(height: 8.h), - Expanded( - child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) - .toString() - .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) - .toShimmer2(isShow: false), + width: 80.h, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, + width: 64.h, + height: 64.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: false, radius: 50.h), + SizedBox(height: 8.h), + Expanded( + child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) + .toString() + .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) + .toShimmer2(isShow: false), + ), + ], ), - ], + ), ), ), - ), - ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any completed visits yet.".needTranslation, isSmallWidget: true, width: 62, height: 62) .paddingSymmetrical(24.h, 0.h); diff --git a/lib/presentation/my_family/my_family.dart b/lib/presentation/my_family/my_family.dart index a2bcfaa..7de92f4 100644 --- a/lib/presentation/my_family/my_family.dart +++ b/lib/presentation/my_family/my_family.dart @@ -1,7 +1,10 @@ +import 'dart:convert'; + 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/validation_utils.dart'; @@ -11,10 +14,10 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ 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/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/appbar/collapsing_list_view.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'; @@ -48,100 +51,75 @@ class _FamilyMedicalScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - body: CollapsingListView( - title: "My Medical File".needTranslation, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomTabBar( - tabs: tabs, - onTabChange: (index) { - medicalVM!.onFamilyFileTabChange(index); - }, + AppState appState = getIt.get(); + return CollapsingListView( + title: "My Medical File".needTranslation, + bottomChild: appState.getAuthenticatedUser()!.isParentUser! + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), ), - SizedBox(height: 25.h), - Consumer(builder: (context, medicalVM, child) => getFamilyTabs(index: medicalVM.getSelectedFamilyFileTabIndex)), - 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".needTranslation, + onPressed: () { + DialogService dialogService = getIt.get(); + dialogService.showAddFamilyFileSheet( + label: "Add Family Member".needTranslation, + message: "Please fill the below field to add a new family member to your profile".needTranslation, + onVerificationPress: () { + medicalVM?.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms, isExcludedUser: true); + }); + }, + icon: AppAssets.add_icon, + height: 56.h, + fontWeight: FontWeight.w600)) + : SizedBox(), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomTabBar( + tabs: tabs, + onTabChange: (index) { + medicalVM!.onFamilyFileTabChange(index); + }, ), - 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)), + SizedBox(height: 25.h), + Consumer(builder: (context, medicalVM, child) => getFamilyTabs(index: medicalVM.getSelectedFamilyFileTabIndex)), + SizedBox(height: 20.h), + ], + ).paddingSymmetrical(20, 0), ); - // 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: (index) { - // medicalVM!.onFamilyFileTabChange(index); - // }, - // ), - // SizedBox(height: 25.h), - // Consumer(builder: (context, medicalVM, child) => getFamilyTabs(index: medicalVM.getSelectedFamilyFileTabIndex)), - // 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, - // )), - // ); } Widget getFamilyTabs({required int index}) { switch (index) { case 0: + print(jsonEncode(medicalVM!.patientFamilyFiles)); return FamilyCards( profiles: medicalVM!.patientFamilyFiles, onSelect: (FamilyFileResponseModelLists profile) { medicalVM!.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); }, + onRemove: (FamilyFileResponseModelLists profile) { + medicalVM!.removeFileFromFamilyMembers(id: profile.id); + }, + isLeftAligned: true, isShowDetails: true, + isShowRemoveButton: true, ); case 1: + print(jsonEncode(medicalVM!.pendingFamilyFiles)); return FamilyCards( profiles: medicalVM!.pendingFamilyFiles, + isRequestDesign: true, onSelect: (FamilyFileResponseModelLists profile) { - // medicalVM!.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + medicalVM!.acceptRejectFileFromFamilyMembers(id: profile.id, status: 3); + }, + onRemove: (FamilyFileResponseModelLists profile) { + medicalVM!.acceptRejectFileFromFamilyMembers(id: profile.id, status: 4); }, isShowDetails: true, ); @@ -149,85 +127,4 @@ class _FamilyMedicalScreenState extends State { return SizedBox.shrink(); } } - - Future showModelSheet() async { - AuthenticationViewModel authVm = getIt.get(); - return await showCommonBottomSheetWithoutHeight(context, - title: "Add Family Member", - useSafeArea: true, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - 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: "", - 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.h), - ], - ), - ), - 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(); - }, - )) { - // authVm.addFamilyMember(otpTypeEnum: OTPTypeEnum.sms, isExcludedUser: true); - medicalVM?.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms, isExcludedUser: true); - } - }, - 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 index 3916756..166b06d 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -17,10 +17,23 @@ import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; class FamilyCards extends StatefulWidget { final List profiles; final Function(FamilyFileResponseModelLists) onSelect; + final Function(FamilyFileResponseModelLists) onRemove; final bool isShowDetails; final bool isBottomSheet; + final bool isRequestDesign; + final bool isLeftAligned; + final bool isShowRemoveButton; - const FamilyCards({super.key, required this.profiles, required this.onSelect, this.isShowDetails = false, this.isBottomSheet = false}); + const FamilyCards( + {super.key, + required this.profiles, + required this.onSelect, + required this.onRemove, + this.isShowDetails = false, + this.isBottomSheet = false, + this.isRequestDesign = false, + this.isLeftAligned = false, + this.isShowRemoveButton = false}); @override State createState() => _FamilyCardsState(); @@ -29,91 +42,315 @@ class FamilyCards extends StatefulWidget { class _FamilyCardsState extends State { AppState appState = getIt(); + // bool isShowActions = true; + @override Widget build(BuildContext context) { - return GridView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: widget.profiles.length, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 10.h, - mainAxisSpacing: 10.h, - childAspectRatio: widget.isShowDetails ? 0.56.h : 0.74.h, - ), - padding: EdgeInsets.only(bottom: 80.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 || profile.status == FamilyFileEnum.pending.toInt ? 0.4 : 1.0, // Fade all content if active - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(height: 5.h), - Utils.buildImgWithAssets( - icon: profile.gender == null - ? AppAssets.dummy_user - : 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( + if (widget.isRequestDesign) { + return Column( + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.alertSquare), + SizedBox(width: 8.h), + "Sent Requests".needTranslation.toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500), + SizedBox(width: 4.h), + Utils.buildSvgWithAssets(icon: AppAssets.arrowRight), + ], + ), + SizedBox(height: 24.h), + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: widget.profiles.where((profile) => profile.isRequestFromMySide ?? false).length, + itemBuilder: (context, index) { + final mySideProfiles = widget.profiles.where((profile) => profile.isRequestFromMySide ?? false).toList(); + FamilyFileResponseModelLists profile = mySideProfiles[index]; + + return Container( + margin: EdgeInsets.only( + bottom: 12.h, + ), + padding: EdgeInsets.symmetric( + vertical: 15.h, + horizontal: 15.h, + ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), + child: Opacity( + opacity: 1.0, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: profile.status == FamilyFileEnum.pending.toInt + ? AppColors.alertLightColor.withValues(alpha: 0.20) + : profile.status == FamilyFileEnum.rejected.toInt + ? AppColors.primaryRedColor.withValues(alpha: 0.20) + : profile.status == FamilyFileEnum.active.toInt + ? AppColors.lightGreenColor + : AppColors.lightGrayBGColor, + chipText: profile.statusDescription ?? "N/A", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: profile.status == FamilyFileEnum.pending.toInt + ? AppColors.alertLightColor + : profile.status == FamilyFileEnum.rejected.toInt + ? AppColors.primaryRedColor + : profile.status == FamilyFileEnum.active.toInt + ? AppColors.textGreenColor + : AppColors.alertColor, + ), + Wrap( + alignment: WrapAlignment.start, + children: [ + (profile.patientName ?? "").toText16( + isBold: false, + isCenter: true, + maxlines: 1, + weight: FontWeight.w600, + ), + ("has ${(profile.statusDescription ?? "").toLowerCase()} your family member request").toText14( + isBold: false, + isCenter: true, + maxlines: 1, + weight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ], + ), + SizedBox(height: 4.h), + CustomChipWidget( + height: 30.h, chipType: ChipTypeEnum.alert, backgroundColor: AppColors.lightGrayBGColor, - chipText: "Age: ${profile.age ?? "N/A"} Years", + chipText: "Medical File: ${profile.responseId ?? "N/A"}", + iconAsset: null, 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.switchAccount.tr(), - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 13.h, - icon: widget.isBottomSheet ? null : AppAssets.switch_user, - iconColor: AppColors.primaryRedColor, - padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), - ).paddingOnly(top: 0, bottom: 0), + ), + ], + ), + ), + ); + }, + ), + SizedBox(height: 20.h), + if (widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).isNotEmpty) + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.alertSquare), + SizedBox(width: 8.h), + "Users who want to view your profile".needTranslation.toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500), + SizedBox(width: 4.h), + Utils.buildSvgWithAssets(icon: AppAssets.arrowRight), ], ), + + // Items for second group (requests from others) + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).length, + itemBuilder: (context, index) { + final otherProfiles = widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).toList(); + FamilyFileResponseModelLists profile = otherProfiles[index]; + return Container( + margin: EdgeInsets.only(bottom: 12.h), + padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), + child: Opacity( + opacity: 1.0, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: profile.status == FamilyFileEnum.pending.toInt + ? AppColors.alertLightColor.withValues(alpha: 0.20) + : profile.status == FamilyFileEnum.rejected.toInt + ? AppColors.primaryRedColor.withValues(alpha: 0.20) + : profile.status == FamilyFileEnum.active.toInt + ? AppColors.lightGreenColor + : AppColors.lightGrayBGColor, + chipText: profile.statusDescription ?? "N/A", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: profile.status == FamilyFileEnum.pending.toInt + ? AppColors.alertLightColor + : profile.status == FamilyFileEnum.rejected.toInt + ? AppColors.primaryRedColor + : profile.status == FamilyFileEnum.active.toInt + ? AppColors.textGreenColor + : AppColors.alertColor, + ), + Wrap( + alignment: WrapAlignment.start, + children: [ + (profile.patientName ?? "").toText16(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), + (profile.status == FamilyFileEnum.active.toInt ? "can view your family".needTranslation : "wants to add you as their family member".needTranslation).toText14( + isBold: false, + isCenter: true, + maxlines: 1, + weight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ], + ), + SizedBox(height: 4.h), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "Medical File: ${profile.patientId ?? "N/A".needTranslation}", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor, + ), + SizedBox(height: 16.h), + Row( + children: [ + profile.status == FamilyFileEnum.active.toInt + ? SizedBox() + : Expanded( + child: CustomButton( + height: 40.h, + text: LocaleKeys.confirm.tr(), + onPressed: () { + widget.onSelect(profile); + }, + backgroundColor: AppColors.lightGreenButtonColor, + borderColor: AppColors.lightGreenButtonColor, + textColor: AppColors.textGreenColor, + icon: null, + ), + ), + profile.status == FamilyFileEnum.active.toInt ? SizedBox() : SizedBox(width: 8.h), + Expanded( + child: CustomButton( + height: 40.h, + text: profile.status == FamilyFileEnum.active.toInt ? LocaleKeys.removeMember.tr() : LocaleKeys.cancel.tr(), + onPressed: () { + widget.onRemove(profile); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + icon: null, + iconColor: AppColors.primaryRedColor, + ), + ), + ], + ), + ], + ), + ), + ); + }, ), - ); - }, - ); + ], + ); + } else { + return GridView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: widget.profiles.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10.h, + mainAxisSpacing: 10.h, + childAspectRatio: widget.isShowDetails ? 0.56.h : 0.66.h, + ), + padding: EdgeInsets.only(bottom: 20.h), + itemBuilder: (context, index) { + final profile = widget.profiles[index]; + final isActive = (profile.responseId == appState.getAuthenticatedUser()?.patientId); + final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false; + final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID); + return Container( + padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), + child: Opacity( + opacity: isActive || profile.status == FamilyFileEnum.pending.toInt || !canSwitch ? 0.4 : 1.0, // Fade all content if active + child: Stack( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Utils.buildImgWithAssets( + icon: profile.gender == null + ? AppAssets.dummy_user + : 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(), + CustomButton( + height: 40.h, + onPressed: () { + if (canSwitch) widget.onSelect(profile); + }, + text: isActive ? "Active".needTranslation : "Switch".needTranslation, + backgroundColor: isActive || !canSwitch ? Colors.grey.shade200 : AppColors.secondaryLightRedColor, + borderColor: isActive || !canSwitch ? Colors.grey.shade200 : AppColors.secondaryLightRedColor, + textColor: isActive || !canSwitch ? AppColors.greyTextColor : AppColors.primaryRedColor, + fontSize: 13.h, + icon: isActive ? AppAssets.activeCheck : AppAssets.switch_user, + iconColor: isActive || !canSwitch ? (isActive ? null : AppColors.greyTextColor) : AppColors.primaryRedColor, + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), + ).paddingOnly(top: 0, bottom: 0), + ], + ), + if (widget.isShowRemoveButton) ...[ + Positioned( + top: 0, + right: 0, + child: Utils.buildSvgWithAssets(icon: AppAssets.deleteIcon).onPress(() { + if (!isActive) widget.onRemove(profile); + }), + ), + ], + ], + ), + ), + ); + }, + ); + } } } diff --git a/lib/presentation/my_family/widget/my_family_sheet.dart b/lib/presentation/my_family/widget/my_family_sheet.dart index da822b1..1c085ca 100644 --- a/lib/presentation/my_family/widget/my_family_sheet.dart +++ b/lib/presentation/my_family/widget/my_family_sheet.dart @@ -7,7 +7,7 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; class MyFamilySheet { static Future show(BuildContext context, List familyLists, Function(FamilyFileResponseModelLists) onSelect) async { - return await showCommonBottomSheetWithoutHeight( + return showCommonBottomSheetWithoutHeight( context, titleWidget: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -22,6 +22,7 @@ class MyFamilySheet { Navigator.of(context).pop(); // Close the bottom sheet onSelect(profile); // Call the onSelect callback }, + onRemove: (profile) {}, isBottomSheet: true), callBackFunc: () {}, ); diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 58273fc..2afa3e0 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -1,16 +1,25 @@ +import 'dart:convert'; +import 'dart:developer'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_swiper_view/flutter_swiper_view.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/int_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/habib_wallet/habib_wallet_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/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/app_language_change.dart'; @@ -48,6 +57,7 @@ class _ProfileSettingsState extends State { @override Widget build(BuildContext context) { + final MedicalFileViewModel medicalFileViewModel = getIt.get(); return CollapsingListView( title: "Profile & Settings".needTranslation, logout: () {}, @@ -57,11 +67,12 @@ class _ProfileSettingsState extends State { physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { + print(jsonEncode(medicalFileViewModel.patientFamilyFiles)); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Swiper( - itemCount: length, + itemCount: medicalFileViewModel.patientFamilyFiles.length, layout: SwiperLayout.STACK, loop: true, itemWidth: MediaQuery.of(context).size.width - 42, @@ -75,7 +86,21 @@ class _ProfileSettingsState extends State { builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), ), itemBuilder: (BuildContext context, int index) { - return FamilyCardWidget().paddingOnly(right: 16); + return FamilyCardWidget( + profile: medicalFileViewModel.patientFamilyFiles[index], + onAddFamilyMemberPress: () { + DialogService dialogService = getIt.get(); + dialogService.showAddFamilyFileSheet( + label: "Add Family Member".needTranslation, + message: "Please fill the below field to add a new family member to your profile".needTranslation, + onVerificationPress: () { + medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms, isExcludedUser: true); + }); + }, + onFamilySwitchPress: (FamilyFileResponseModelLists profile) { + medicalFileViewModel.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + }, + ).paddingOnly(right: 16); }, ), GridView( @@ -224,16 +249,20 @@ class _ProfileSettingsState extends State { } class FamilyCardWidget extends StatelessWidget { - FamilyCardWidget(); + final Function() onAddFamilyMemberPress; + final Function(FamilyFileResponseModelLists member) onFamilySwitchPress; + final FamilyFileResponseModelLists profile; + + const FamilyCardWidget({required this.onAddFamilyMemberPress, required this.profile, required this.onFamilySwitchPress(FamilyFileResponseModelLists member)}); @override Widget build(BuildContext context) { + AppState appState = getIt.get(); + final isActive = (profile.responseId == appState.getAuthenticatedUser()?.patientId); + final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false; + final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID); return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: true, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), child: Column( children: [ Column( @@ -243,16 +272,16 @@ class FamilyCardWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - Image.asset(true ? AppAssets.male_img : AppAssets.femaleImg, width: 56.h, height: 56.h), + Image.asset((profile.gender == 1) ? AppAssets.male_img : AppAssets.femaleImg, width: 56.h, height: 56.h), Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 0.h, mainAxisSize: MainAxisSize.min, children: [ - "Mahmoud Shrouf Shrouf".toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1), + (profile.patientName ?? "").toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1), AppCustomChipWidget( icon: AppAssets.file_icon, - labelText: "File no: 3423443", + labelText: "File no: ${profile.patientId}", iconSize: 14, ), ], @@ -266,11 +295,11 @@ class FamilyCardWidget extends StatelessWidget { alignment: WrapAlignment.start, spacing: 8.h, children: [ - AppCustomChipWidget(labelText: "35 Years Old"), - AppCustomChipWidget(labelText: "Blood: A+"), + AppCustomChipWidget(labelText: "${profile.age} Years Old"), + AppCustomChipWidget(labelText: "Blood: N/A"), AppCustomChipWidget( icon: AppAssets.insurance_active_icon, - labelText: "Insurance Active", + labelText: "Insurance N/A", iconColor: AppColors.bgGreenColor, iconSize: 14, backgroundColor: AppColors.bgGreenColor.withValues(alpha: 0.15), @@ -281,9 +310,92 @@ class FamilyCardWidget extends StatelessWidget { ], ).paddingOnly(top: 16, right: 16, left: 16, bottom: 12).expanded, 1.divider, - CustomButton(icon: AppAssets.add_family, text: "Add a new family member".needTranslation, onPressed: () {}).paddingOnly(top: 12, right: 16, left: 16, bottom: 16), + _buildActionButton(appState), + + // if (appState.getAuthenticatedUser()!.isParentUser ?? false) ...[ + // if (member!.responseId != appState.getAuthenticatedUser()!.patientId) ...[ + // CustomButton( + // icon: AppAssets.switch_user, + // text: "Switch Family File".needTranslation, + // onPressed: () { + // onFamilySwitchPress(member!); + // }, + // ).paddingOnly(top: 12, right: 16, left: 16, bottom: 16), + // ] else + // ...[ + // CustomButton( + // icon: AppAssets.add_family, + // text: "Add a new family member".needTranslation, + // onPressed: () { + // onAddFamilyMemberPress(); + // }, + // ).paddingOnly(top: 12, right: 16, left: 16, bottom: 16), + // ] + // ] else + // ...[ + // if (appState.getSuperUserID != null && appState.getSuperUserID == member!.responseId) ...[ + // CustomButton( + // icon: AppAssets.switch_user, + // text: "Switch Back To Family File".needTranslation, + // onPressed: () { + // onFamilySwitchPress(member!); + // }, + // ).paddingOnly(top: 12, right: 16, left: 16, bottom: 16), + // ] else + // ...[ + // CustomButton( + // icon: AppAssets.switch_user, + // text: "Disabled".needTranslation, + // backgroundColor: Colors.grey.shade200, + // borderColor: Colors.grey.shade200, + // textColor: AppColors.greyTextColor, + // onPressed: () {}, + // iconColor: AppColors.greyTextColor, + // ).paddingOnly(top: 12, right: 16, left: 16, bottom: 16), + // ] + // ] ], ), ); } + + Widget _buildActionButton(AppState appState) { + final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false; + final int? currentUserId = appState.getAuthenticatedUser()?.patientId; + final int? superUserId = appState.getSuperUserID; + + if (isParentUser) { + return _buildParentUserButton(currentUserId); + } else { + return _buildNonParentUserButton(superUserId); + } + } + + Widget _buildParentUserButton(int? currentUserId) { + final canSwitch = profile.responseId != currentUserId; + + return CustomButton( + icon: canSwitch ? AppAssets.switch_user : AppAssets.add_family, + text: canSwitch ? "Switch Family File".needTranslation : "Add a new family member".needTranslation, + onPressed: canSwitch ? () => onFamilySwitchPress(profile) : onAddFamilyMemberPress, + backgroundColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, + borderColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, + textColor: canSwitch ? AppColors.primaryRedColor : AppColors.whiteColor, + iconColor: canSwitch ? AppColors.primaryRedColor : AppColors.whiteColor, + ).paddingOnly(top: 12, right: 16, left: 16, bottom: 16); + } + + Widget _buildNonParentUserButton(int? superUserId) { + final canSwitchBack = superUserId != null && superUserId == profile.responseId; + + return CustomButton( + icon: AppAssets.switch_user, + text: canSwitchBack ? "Switch Back To Family File".needTranslation : "Switch".needTranslation, + backgroundColor: canSwitchBack ? AppColors.primaryRedColor : Colors.grey.shade200, + borderColor: canSwitchBack ? AppColors.primaryRedColor : Colors.grey.shade200, + textColor: canSwitchBack ? AppColors.whiteColor : AppColors.greyTextColor, + iconColor: canSwitchBack ? AppColors.whiteColor : AppColors.greyTextColor, + onPressed: canSwitchBack ? () => onFamilySwitchPress(profile) : () {}, + ).paddingOnly(top: 12, right: 16, left: 16, bottom: 16); + } } diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 6ee0ddc..4d332b3 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -4,12 +4,15 @@ 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/extensions/route_extensions.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/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/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/bottomsheet/exception_bottom_sheet.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/family_files/family_file_add_widget.dart'; abstract class DialogService { Future showErrorBottomSheet({String title = "", required String message, Function()? onOkPressed, Function()? onCancelPressed}); @@ -18,7 +21,12 @@ abstract class DialogService { Future showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}); + Future showFamilyBottomSheetWithoutH( + {String? label, required String message, required Function(FamilyFileResponseModelLists response) onSwitchPress, required List profiles}); + Future showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}); + + Future showAddFamilyFileSheet({String? label, String? message, required Function() onVerificationPress}); // TODO : Need to be Fixed showPhoneNumberPickerSheet ( From Login ADn Signup Bottom Sheet Move Here } @@ -93,6 +101,24 @@ class DialogServiceImp implements DialogService { title: label ?? "", child: exceptionBottomSheetWidget(context: context, message: message, onOkPressed: onOkPressed, onCancelPressed: onCancelPressed), callBackFunc: () {}); } + @override + Future showFamilyBottomSheetWithoutH( + {String? label, required String message, required Function(FamilyFileResponseModelLists response) onSwitchPress, required List profiles}) async { + final context = navigationService.navigatorKey.currentContext; + if (context == null) return; + showCommonBottomSheetWithoutHeight(context, + title: label ?? "", + child: FamilyCards( + profiles: profiles, + onSelect: (FamilyFileResponseModelLists profile) { + onSwitchPress(profile); + }, + onRemove: (FamilyFileResponseModelLists profile) {}, + isShowDetails: false, + ), + callBackFunc: () {}); + } + @override Future showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async { final context = navigationService.navigatorKey.currentContext; @@ -100,6 +126,18 @@ class DialogServiceImp implements DialogService { showCommonBottomSheetWithoutHeight(context, title: label ?? "", child: showPhoneNumberPickerWidget(context: context, message: message, onSMSPress: onSMSPress, onWhatsappPress: onWhatsappPress), callBackFunc: () {}); } + + @override + Future showAddFamilyFileSheet({String? label, String? message, required Function() onVerificationPress}) async { + final context = navigationService.navigatorKey.currentContext; + if (context == null) return; + showCommonBottomSheetWithoutHeight(context, + title: label ?? "", + child: FamilyFileAddWidget(() { + onVerificationPress(); + }, message ?? ""), + callBackFunc: () {}); + } } Widget exceptionBottomSheetWidget({required BuildContext context, required String message, required Function() onOkPressed, Function()? onCancelPressed}) { @@ -216,3 +254,12 @@ Widget showPhoneNumberPickerWidget({required BuildContext context, String? messa // ); }); } + +// Widget familyMemberAddWidget() { +// AuthenticationViewModel authVm = getIt.get(); +// return showCommonBottomSheetWithoutHeight(context, +// title: "Add Family Member".needTranslation, +// useSafeArea: true, +// child: +// callBackFunc: () {}); +// } diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index b18c6ee..60b0f03 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -22,10 +22,11 @@ class CollapsingListView extends StatelessWidget { VoidCallback? logout; VoidCallback? history; Widget? bottomChild; + Widget? trailing; bool isClose; bool isLeading; - CollapsingListView({required this.title, this.child, this.search, this.isClose = false, this.bottomChild, this.report, this.logout, this.history, this.isLeading = true}); + CollapsingListView({required this.title, this.child, this.search, this.isClose = false, this.bottomChild, this.report, this.logout, this.history, this.isLeading = true, this.trailing}); @override Widget build(BuildContext context) { @@ -97,7 +98,8 @@ class CollapsingListView extends StatelessWidget { if (logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!), if (report != null) actionButton(context, t, title: "Report".needTranslation, icon: AppAssets.report_icon).onPress(report!), if (history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(history!), - if (search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(search!).paddingOnly(right: 24) + if (search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(search!).paddingOnly(right: 24), + if (trailing != null) trailing!, ], )), ), diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index 2f8a9ec..b253c36 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -23,28 +23,30 @@ class CustomButton extends StatelessWidget { final double? width; final double iconSize; final TextOverflow? textOverflow; + final BorderSide? borderSide; - CustomButton({ - Key? key, - required this.text, - required this.onPressed, - this.backgroundColor = const Color(0xFFED1C2B), - this.borderColor = const Color(0xFFED1C2B), - this.textColor = Colors.white, - this.borderRadius = 12, - this.borderWidth = 2, - this.padding = const EdgeInsets.fromLTRB(8, 10, 8, 10), - this.fontSize = 16, - this.fontFamily, - this.fontWeight = FontWeight.w500, - this.isDisabled = false, - this.icon, - this.iconColor = Colors.white, - this.height = 56, - this.width, - this.iconSize = 24, - this.textOverflow, - }) : super(key: key); + CustomButton( + {Key? key, + required this.text, + required this.onPressed, + this.backgroundColor = const Color(0xFFED1C2B), + this.borderColor = const Color(0xFFED1C2B), + this.textColor = Colors.white, + this.borderRadius = 12, + this.borderWidth = 2, + this.padding = const EdgeInsets.fromLTRB(8, 10, 8, 10), + this.fontSize = 16, + this.fontFamily, + this.fontWeight = FontWeight.w500, + this.isDisabled = false, + this.icon, + this.iconColor = Colors.white, + this.height = 56, + this.width, + this.iconSize = 24, + this.textOverflow, + this.borderSide}) + : super(key: key); @override Widget build(BuildContext context) { @@ -57,17 +59,15 @@ class CustomButton extends StatelessWidget { decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: isDisabled ? Colors.transparent : backgroundColor, borderRadius: borderRadius, - side: BorderSide( - width: borderWidth.h, - color: isDisabled ? borderColor.withOpacity(0.5) : borderColor, - )), + customBorder: BorderRadius.circular(borderRadius), + side: borderSide ?? BorderSide(width: borderWidth.h, color: isDisabled ? borderColor.withValues(alpha: 0.5) : borderColor)), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ if (icon != null) Padding( - padding: const EdgeInsets.only(right: 8.0, left: 8.0), + padding: EdgeInsets.only(right: 8.h, left: 8.h), child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled, width: iconSize, height: iconSize), ), Padding( @@ -85,17 +85,6 @@ class CustomButton extends StatelessWidget { ), ], ), - ) - - // .toSmoothContainer( - // smoothness: 1, - // side: BorderSide(width: borderWidth, color: backgroundColor), - // borderRadius: BorderRadius.circular(borderRadius * 1.2), - // foregroundDecoration: BoxDecoration( - // color: isDisabled ? backgroundColor.withOpacity(0.5) : Colors.transparent, - // borderRadius: BorderRadius.circular(borderRadius), - // ), - // ), - ); + )); } } diff --git a/lib/widgets/chip/custom_chip_widget.dart b/lib/widgets/chip/custom_chip_widget.dart index 1d0b5ef..5dee4f0 100644 --- a/lib/widgets/chip/custom_chip_widget.dart +++ b/lib/widgets/chip/custom_chip_widget.dart @@ -17,6 +17,7 @@ class CustomChipWidget extends StatelessWidget { final Color? textColor; final Color? borderColor; final bool isShowBorder; + final double? height; const CustomChipWidget({ super.key, @@ -31,6 +32,7 @@ class CustomChipWidget extends StatelessWidget { this.textColor, this.borderColor, this.isShowBorder = false, + this.height, }); @override @@ -39,6 +41,7 @@ class CustomChipWidget extends StatelessWidget { final hasOnTap = onTap != null || hasIcon; return Container( + height: height, decoration: BoxDecoration( borderRadius: BorderRadius.circular(borderRadius), color: isSelected ? chipType.color : backgroundColor ?? chipType.backgroundColor, diff --git a/lib/widgets/family_files/family_file_add_widget.dart b/lib/widgets/family_files/family_file_add_widget.dart new file mode 100644 index 0000000..8e57f91 --- /dev/null +++ b/lib/widgets/family_files/family_file_add_widget.dart @@ -0,0 +1,105 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/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/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; + +class FamilyFileAddWidget extends StatelessWidget { + final Function()? onVerificationPress; + final String message; + + const FamilyFileAddWidget(this.onVerificationPress, this.message, {super.key}); + + @override + Widget build(BuildContext context) { + AuthenticationViewModel authVm = getIt.get(); + MedicalFileViewModel? medicalVM = getIt.get(); + // TODO: implement build + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + message.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, + 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: "", + 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.h), + ], + ), + ), + SizedBox(height: 20.h), + CustomButton( + text: "Verify the member".needTranslation, + onPressed: () { + FocusScope.of(context).unfocus(); + if (ValidationUtils.isValidatedIdAndPhoneWithCountryValidation( + nationalId: authVm.nationalIdController.text, + selectedCountry: authVm.selectedCountrySignup, + phoneNumber: authVm.phoneNumberController.text, + onOkPress: () { + Navigator.of(context).pop(); + }, + )) { + // authVm.addFamilyMember(otpTypeEnum: OTPTypeEnum.sms, isExcludedUser: true); + if (onVerificationPress != null) { + onVerificationPress!(); + } + } + }, + icon: AppAssets.add_icon, + height: 56.h, + fontWeight: FontWeight.w600), + SizedBox(height: 20.h), + ], + ); + } +} From 24362f67cbe92690d0c94e6ea1d5f56e0dccf761 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Tue, 7 Oct 2025 15:43:58 +0300 Subject: [PATCH 12/12] family screen & widgets --- lib/core/dependencies.dart | 2 + .../authentication/authentication_repo.dart | 2 +- .../authentication_view_model.dart | 11 +- .../medical_file/medical_file_repo.dart | 2 +- .../medical_file/medical_file_view_model.dart | 10 +- lib/presentation/home/landing_page.dart | 10 +- .../medical_file/medical_file_page.dart | 8 +- lib/presentation/my_family/my_family.dart | 14 +- .../my_family/widget/family_cards.dart | 375 +++++++++--------- .../my_family/widget/my_family_sheet.dart | 11 +- .../profile_settings/profile_settings.dart | 210 +++++++--- lib/services/dialog_service.dart | 14 + lib/widgets/chip/app_custom_chip_widget.dart | 98 ++--- 13 files changed, 448 insertions(+), 319 deletions(-) diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index e2e06d2..0ddada2 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -24,6 +24,7 @@ import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; +import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; @@ -166,6 +167,7 @@ class AppDependencies { () => AuthenticationViewModel( authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), ); + getIt.registerLazySingleton(() => ProfileSettingsViewModel()); // Screen-specific VMs → Factory // getIt.registerFactory( diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index c034a69..bec5470 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -275,7 +275,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { 'PatientShareRequestID': patientShareRequestID, 'ResponseID': responseID, 'Status': 3, - 'PatientID': appState.getAuthenticatedUser()?.patientId ?? 0, + // 'PatientID': appState.getAuthenticatedUser()?.patientId ?? 0, 'LogInTokenID': appState.getFamilyFileTokenID, 'activationCode': activationCode ?? "0000", 'PatientMobileNumber': newRequest.patientMobileNumber, diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 467a476..444e54e 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -550,7 +550,7 @@ class AuthenticationViewModel extends ChangeNotifier { if (!_appState.getIsChildLoggedIn) { await medicalVm.getFamilyFiles(status: 0); await medicalVm.getAllPendingRecordsByResponseId(); - _navigationService.popUntilNamed(AppRoutes.medicalFilePage); + _navigationService.popUntilNamed(AppRoutes.landingScreen); } } else { if (activation.list != null && activation.list!.isNotEmpty) { @@ -567,12 +567,13 @@ class AuthenticationViewModel extends ChangeNotifier { } else { activation.list!.first.isParentUser = true; } + activation.list!.first.bloodGroup = activation.patientBlodType; _appState.setAuthenticatedUser(activation.list!.first); _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); } - _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); + // _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); _appState.setAppAuthToken = activation.authenticationTokenId; - final request = RequestUtils.getAuthanticatedCommonRequest().toJson(); + final request = await RequestUtils.getAuthanticatedCommonRequest().toJson(); bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); //updating the last login type in app state to show the fingerprint/face id option on home screen @@ -583,9 +584,9 @@ class AuthenticationViewModel extends ChangeNotifier { // if (!isSwitchUser && !_appState.getIsChildLoggedIn) { MedicalFileViewModel medicalVm = getIt(); - insertPatientIMEIData(loginTypeEnum.toInt); + await insertPatientIMEIData(loginTypeEnum.toInt); await medicalVm.getFamilyFiles(status: 0); //TODO: Remove status: 1 by Aamir Need to Discuss With Sultan - // medicalVm.getAllPendingRecordsByResponseId(); + await medicalVm.getAllPendingRecordsByResponseId(); } await clearDefaultInputValues(); diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index 416c372..a460d9f 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -29,7 +29,7 @@ abstract class MedicalFileRepo { Future>>> getAllPendingRecordsByResponseId({required Map request}); - Future>>> addFamilyFile({required dynamic request}); + Future>> addFamilyFile({required dynamic request}); Future>>> getPatientAppointmentsForMedicalReport(); diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 60d14b0..b5d9d03 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -89,9 +89,6 @@ class MedicalFileViewModel extends ChangeNotifier { void onFamilyFileTabChange(int index) { setSelectedFamilyFileTabIndex = index; - if (index == 1) { - // getAllPendingRecordsByResponseId(); - } notifyListeners(); } @@ -323,8 +320,6 @@ class MedicalFileViewModel extends ChangeNotifier { final isPending = element.status == FamilyFileEnum.pending.toInt || element.status == FamilyFileEnum.rejected.toInt; final isActive = element.status == FamilyFileEnum.active.toInt; - print("====== Element Status: ${element.status}, isPending: $isPending, isActive: $isActive ============"); - if (!isPending && !isActive) { continue; } @@ -423,9 +418,6 @@ class MedicalFileViewModel extends ChangeNotifier { } // pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => !pendingFamilyFiles.any((e) => e.responseId == element.responseId))); pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => !pendingFamilyFiles.any((e) => e.patientId == element.patientId))); - - print("====== Pending Family Length: ${pendingFamilyFiles.length} ============"); - print("====== Pending Family Files: ${jsonEncode(pendingFamilyFiles)} ============"); } notifyListeners(); } @@ -447,7 +439,7 @@ class MedicalFileViewModel extends ChangeNotifier { ); } - Future addFamilyFile({required OTPTypeEnum otpTypeEnum, required bool isExcludedUser}) async { + Future addFamilyFile({required OTPTypeEnum otpTypeEnum}) async { LoaderBottomSheet.showLoader(); AuthenticationViewModel authVM = getIt.get(); NavigationService navigationService = getIt.get(); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 42932b1..afaa1c8 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -75,6 +75,11 @@ class _LandingPageState extends State { void initState() { authVM = context.read(); habibWalletVM = context.read(); + myAppointmentsViewModel = context.read(); + prescriptionsViewModel = context.read(); + insuranceViewModel = context.read(); + immediateLiveCareViewModel = context.read(); + authVM.savePushTokenToAppState(); if (mounted) { authVM.checkLastLoginStatus(() { @@ -100,11 +105,6 @@ class _LandingPageState extends State { @override Widget build(BuildContext context) { appState = getIt.get(); - NavigationService navigationService = getIt.get(); - myAppointmentsViewModel = Provider.of(context, listen: false); - prescriptionsViewModel = Provider.of(context, listen: false); - insuranceViewModel = Provider.of(context, listen: false); - immediateLiveCareViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: SingleChildScrollView( diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 92396a8..674732a 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -81,10 +81,10 @@ class _MedicalFilePageState extends State { insuranceViewModel.initInsuranceProvider(); medicalFileViewModel.setIsPatientSickLeaveListLoading(true); medicalFileViewModel.getPatientSickLeaveList(); - if (appState.getSuperUserID == null) { - medicalFileViewModel.getFamilyFiles(status: 0); //TODO: Remove status: 1 by Aamir Need to Discuss With Sultan - medicalFileViewModel.getAllPendingRecordsByResponseId(); //TODO: Added By Aamir - } + // if (appState.getSuperUserID == null) { + // medicalFileViewModel.getFamilyFiles(status: 0); //TODO: Remove status: 1 by Aamir Need to Discuss With Sultan + // medicalFileViewModel.getAllPendingRecordsByResponseId(); //TODO: Added By Aamir + // } medicalFileViewModel.onTabChanged(0); } diff --git a/lib/presentation/my_family/my_family.dart b/lib/presentation/my_family/my_family.dart index 7de92f4..c1773b8 100644 --- a/lib/presentation/my_family/my_family.dart +++ b/lib/presentation/my_family/my_family.dart @@ -40,7 +40,6 @@ class FamilyMedicalScreen extends StatefulWidget { } class _FamilyMedicalScreenState extends State { - List tabs = [CustomTabBarModel(null, LocaleKeys.medicalFile.tr()), CustomTabBarModel(null, LocaleKeys.request.tr())]; MedicalFileViewModel? medicalVM; @override @@ -52,6 +51,7 @@ class _FamilyMedicalScreenState extends State { @override Widget build(BuildContext context) { AppState appState = getIt.get(); + return CollapsingListView( title: "My Medical File".needTranslation, bottomChild: appState.getAuthenticatedUser()!.isParentUser! @@ -69,7 +69,7 @@ class _FamilyMedicalScreenState extends State { label: "Add Family Member".needTranslation, message: "Please fill the below field to add a new family member to your profile".needTranslation, onVerificationPress: () { - medicalVM?.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms, isExcludedUser: true); + medicalVM!.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); }); }, icon: AppAssets.add_icon, @@ -81,13 +81,17 @@ class _FamilyMedicalScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomTabBar( - tabs: tabs, + activeBackgroundColor: AppColors.secondaryLightRedColor, + activeTextColor: AppColors.primaryRedColor, + tabs: appState.isChildLoggedIn + ? [CustomTabBarModel(null, LocaleKeys.medicalFile.tr())] + : [CustomTabBarModel(null, LocaleKeys.medicalFile.tr()), CustomTabBarModel(null, LocaleKeys.request.tr())], onTabChange: (index) { medicalVM!.onFamilyFileTabChange(index); }, ), SizedBox(height: 25.h), - Consumer(builder: (context, medicalVM, child) => getFamilyTabs(index: medicalVM.getSelectedFamilyFileTabIndex)), + Selector(selector: (_, model) => model.getSelectedFamilyFileTabIndex, builder: (context, selectedIndex, child) => getFamilyTabs(index: selectedIndex)), SizedBox(height: 20.h), ], ).paddingSymmetrical(20, 0), @@ -97,7 +101,6 @@ class _FamilyMedicalScreenState extends State { Widget getFamilyTabs({required int index}) { switch (index) { case 0: - print(jsonEncode(medicalVM!.patientFamilyFiles)); return FamilyCards( profiles: medicalVM!.patientFamilyFiles, onSelect: (FamilyFileResponseModelLists profile) { @@ -111,7 +114,6 @@ class _FamilyMedicalScreenState extends State { isShowRemoveButton: true, ); case 1: - print(jsonEncode(medicalVM!.pendingFamilyFiles)); return FamilyCards( profiles: medicalVM!.pendingFamilyFiles, isRequestDesign: true, diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 166b06d..92c85af 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -10,6 +10,8 @@ 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/services/dialog_service.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/custom_chip_widget.dart'; @@ -42,10 +44,9 @@ class FamilyCards extends StatefulWidget { class _FamilyCardsState extends State { AppState appState = getIt(); - // bool isShowActions = true; - @override Widget build(BuildContext context) { + DialogService dialogService = getIt.get(); if (widget.isRequestDesign) { return Column( children: [ @@ -53,7 +54,14 @@ class _FamilyCardsState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.alertSquare), SizedBox(width: 8.h), - "Sent Requests".needTranslation.toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500), + "Who can view my medical file ?".needTranslation.toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500).onPress(() { + dialogService.showFamilyBottomSheetWithoutHWithChild( + label: "Manage Family".needTranslation, + message: "", + child: manageFamily(), + onOkPressed: () {}, + ); + }), SizedBox(width: 4.h), Utils.buildSvgWithAssets(icon: AppAssets.arrowRight), ], @@ -67,100 +75,6 @@ class _FamilyCardsState extends State { itemBuilder: (context, index) { final mySideProfiles = widget.profiles.where((profile) => profile.isRequestFromMySide ?? false).toList(); FamilyFileResponseModelLists profile = mySideProfiles[index]; - - return Container( - margin: EdgeInsets.only( - bottom: 12.h, - ), - padding: EdgeInsets.symmetric( - vertical: 15.h, - horizontal: 15.h, - ), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), - child: Opacity( - opacity: 1.0, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - CustomChipWidget( - height: 30.h, - chipType: ChipTypeEnum.alert, - backgroundColor: profile.status == FamilyFileEnum.pending.toInt - ? AppColors.alertLightColor.withValues(alpha: 0.20) - : profile.status == FamilyFileEnum.rejected.toInt - ? AppColors.primaryRedColor.withValues(alpha: 0.20) - : profile.status == FamilyFileEnum.active.toInt - ? AppColors.lightGreenColor - : AppColors.lightGrayBGColor, - chipText: profile.statusDescription ?? "N/A", - iconAsset: null, - isShowBorder: false, - borderRadius: 8.h, - textColor: profile.status == FamilyFileEnum.pending.toInt - ? AppColors.alertLightColor - : profile.status == FamilyFileEnum.rejected.toInt - ? AppColors.primaryRedColor - : profile.status == FamilyFileEnum.active.toInt - ? AppColors.textGreenColor - : AppColors.alertColor, - ), - Wrap( - alignment: WrapAlignment.start, - children: [ - (profile.patientName ?? "").toText16( - isBold: false, - isCenter: true, - maxlines: 1, - weight: FontWeight.w600, - ), - ("has ${(profile.statusDescription ?? "").toLowerCase()} your family member request").toText14( - isBold: false, - isCenter: true, - maxlines: 1, - weight: FontWeight.w500, - color: AppColors.greyTextColor, - ), - ], - ), - SizedBox(height: 4.h), - CustomChipWidget( - height: 30.h, - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: "Medical File: ${profile.responseId ?? "N/A"}", - iconAsset: null, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor, - ), - ], - ), - ), - ); - }, - ), - SizedBox(height: 20.h), - if (widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).isNotEmpty) - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.alertSquare), - SizedBox(width: 8.h), - "Users who want to view your profile".needTranslation.toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500), - SizedBox(width: 4.h), - Utils.buildSvgWithAssets(icon: AppAssets.arrowRight), - ], - ), - - // Items for second group (requests from others) - ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).length, - itemBuilder: (context, index) { - final otherProfiles = widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).toList(); - FamilyFileResponseModelLists profile = otherProfiles[index]; return Container( margin: EdgeInsets.only(bottom: 12.h), padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), @@ -173,92 +87,49 @@ class _FamilyCardsState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ CustomChipWidget( - height: 30.h, - chipType: ChipTypeEnum.alert, - backgroundColor: profile.status == FamilyFileEnum.pending.toInt - ? AppColors.alertLightColor.withValues(alpha: 0.20) - : profile.status == FamilyFileEnum.rejected.toInt - ? AppColors.primaryRedColor.withValues(alpha: 0.20) - : profile.status == FamilyFileEnum.active.toInt - ? AppColors.lightGreenColor - : AppColors.lightGrayBGColor, - chipText: profile.statusDescription ?? "N/A", - iconAsset: null, - isShowBorder: false, - borderRadius: 8.h, - textColor: profile.status == FamilyFileEnum.pending.toInt - ? AppColors.alertLightColor - : profile.status == FamilyFileEnum.rejected.toInt - ? AppColors.primaryRedColor - : profile.status == FamilyFileEnum.active.toInt - ? AppColors.textGreenColor - : AppColors.alertColor, - ), - Wrap( - alignment: WrapAlignment.start, - children: [ - (profile.patientName ?? "").toText16(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), - (profile.status == FamilyFileEnum.active.toInt ? "can view your family".needTranslation : "wants to add you as their family member".needTranslation).toText14( - isBold: false, - isCenter: true, - maxlines: 1, - weight: FontWeight.w500, - color: AppColors.greyTextColor, - ), - ], - ), - SizedBox(height: 4.h), + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: profile.status == FamilyFileEnum.pending.toInt + ? AppColors.alertLightColor.withValues(alpha: 0.20) + : profile.status == FamilyFileEnum.rejected.toInt + ? AppColors.primaryRedColor.withValues(alpha: 0.20) + : profile.status == FamilyFileEnum.active.toInt + ? AppColors.lightGreenColor + : AppColors.lightGrayBGColor, + chipText: profile.statusDescription ?? "N/A", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: profile.status == FamilyFileEnum.pending.toInt + ? AppColors.alertLightColor + : profile.status == FamilyFileEnum.rejected.toInt + ? AppColors.primaryRedColor + : profile.status == FamilyFileEnum.active.toInt + ? AppColors.textGreenColor + : AppColors.alertColor), + SizedBox(height: 8.h), + Wrap(alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, runAlignment: WrapAlignment.start, spacing: 0.h, children: [ + (profile.patientName ?? "").toText14(isBold: false, isCenter: false, maxlines: 1, weight: FontWeight.w600), + (getStatusTextByRequest(FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)) + .toText14(isBold: false, isCenter: false, maxlines: 1, weight: FontWeight.w500, color: AppColors.greyTextColor), + ]), + SizedBox(height: 8.h), CustomChipWidget( - height: 30.h, - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: "Medical File: ${profile.patientId ?? "N/A".needTranslation}", - iconAsset: null, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor, - ), - SizedBox(height: 16.h), - Row( - children: [ - profile.status == FamilyFileEnum.active.toInt - ? SizedBox() - : Expanded( - child: CustomButton( - height: 40.h, - text: LocaleKeys.confirm.tr(), - onPressed: () { - widget.onSelect(profile); - }, - backgroundColor: AppColors.lightGreenButtonColor, - borderColor: AppColors.lightGreenButtonColor, - textColor: AppColors.textGreenColor, - icon: null, - ), - ), - profile.status == FamilyFileEnum.active.toInt ? SizedBox() : SizedBox(width: 8.h), - Expanded( - child: CustomButton( - height: 40.h, - text: profile.status == FamilyFileEnum.active.toInt ? LocaleKeys.removeMember.tr() : LocaleKeys.cancel.tr(), - onPressed: () { - widget.onRemove(profile); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - icon: null, - iconColor: AppColors.primaryRedColor, - ), - ), - ], - ), + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "Medical File: ${profile.responseId ?? "N/A"}", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), ], ), ), ); }, ), + SizedBox(height: 20.h), ], ); } else { @@ -270,7 +141,7 @@ class _FamilyCardsState extends State { crossAxisCount: 2, crossAxisSpacing: 10.h, mainAxisSpacing: 10.h, - childAspectRatio: widget.isShowDetails ? 0.56.h : 0.66.h, + childAspectRatio: widget.isShowDetails ? 0.56.h : 0.65.h, ), padding: EdgeInsets.only(bottom: 20.h), itemBuilder: (context, index) { @@ -297,8 +168,8 @@ class _FamilyCardsState extends State { 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), + (profile.patientName ?? "Unknown").toText14(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), + SizedBox(height: 8.h), CustomChipWidget( chipType: ChipTypeEnum.alert, backgroundColor: AppColors.lightGrayBGColor, @@ -318,7 +189,11 @@ class _FamilyCardsState extends State { textColor: AppColors.textColor, ) : SizedBox(), - widget.isShowDetails ? SizedBox(height: 8.h) : SizedBox(), + widget.isShowDetails + ? SizedBox(height: 8.h) + : SizedBox( + height: 4.h, + ), Spacer(), CustomButton( height: 40.h, @@ -353,4 +228,144 @@ class _FamilyCardsState extends State { ); } } + + Widget manageFamily() { + NavigationService navigationService = getIt(); + return ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsetsGeometry.zero, + itemCount: widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).length, + itemBuilder: (context, index) { + final otherProfiles = widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).toList(); + FamilyFileResponseModelLists profile = otherProfiles[index]; + return Container( + margin: EdgeInsets.only(bottom: 12.h), + padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), + child: Opacity( + opacity: 1.0, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: profile.status == FamilyFileEnum.pending.toInt + ? AppColors.alertLightColor.withValues(alpha: 0.20) + : profile.status == FamilyFileEnum.rejected.toInt + ? AppColors.primaryRedColor.withValues(alpha: 0.20) + : profile.status == FamilyFileEnum.active.toInt + ? AppColors.lightGreenColor + : AppColors.lightGrayBGColor, + chipText: profile.statusDescription ?? "N/A", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: profile.status == FamilyFileEnum.pending.toInt + ? AppColors.alertLightColor + : profile.status == FamilyFileEnum.rejected.toInt + ? AppColors.primaryRedColor + : profile.status == FamilyFileEnum.active.toInt + ? AppColors.textGreenColor + : AppColors.alertColor, + ), + SizedBox(height: 8.h), + Wrap( + alignment: WrapAlignment.start, + children: [ + (profile.patientName ?? "").toText14(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), + (getStatusTextByRequest(FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)).toText14( + isBold: false, + isCenter: true, + maxlines: 1, + weight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ], + ), + SizedBox(height: 8.h), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "Medical File: ${profile.patientId ?? "N/A".needTranslation}", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor, + ), + SizedBox(height: 16.h), + Row( + children: [ + profile.status == FamilyFileEnum.active.toInt + ? SizedBox() + : Expanded( + child: CustomButton( + height: 40.h, + text: LocaleKeys.confirm.tr(), + onPressed: () { + navigationService.pop(); + widget.onSelect(profile); + }, + backgroundColor: AppColors.lightGreenButtonColor, + borderColor: AppColors.lightGreenButtonColor, + textColor: AppColors.textGreenColor, + icon: null, + ), + ), + profile.status == FamilyFileEnum.active.toInt ? SizedBox() : SizedBox(width: 8.h), + Expanded( + child: CustomButton( + height: 40.h, + text: profile.status == FamilyFileEnum.active.toInt ? LocaleKeys.removeMember.tr() : LocaleKeys.cancel.tr(), + onPressed: () { + navigationService.pop(); + widget.onRemove(profile); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + icon: null, + iconColor: AppColors.primaryRedColor, + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + } + + String getStatusTextByRequest(FamilyFileEnum status, bool isRequestFromMySide) { + switch (status) { + case FamilyFileEnum.active: + if (isRequestFromMySide) { + return "${status.displayName} your request to be your family member".needTranslation; + } else { + return "can view your file".needTranslation; + } + case FamilyFileEnum.pending: + if (isRequestFromMySide) { + return "has a request ${status.displayName} to be your family member".needTranslation; + } else { + return "wants to add you as their family member".needTranslation; + } + case FamilyFileEnum.rejected: + if (isRequestFromMySide) { + return "${status.displayName} your request to be your family member".needTranslation; + } else { + return "${status.displayName} your family member request".needTranslation; + } + case FamilyFileEnum.inactive: + return "Inactive".needTranslation; + default: + return "N/A".needTranslation; + } + } } diff --git a/lib/presentation/my_family/widget/my_family_sheet.dart b/lib/presentation/my_family/widget/my_family_sheet.dart index 1c085ca..d469ab2 100644 --- a/lib/presentation/my_family/widget/my_family_sheet.dart +++ b/lib/presentation/my_family/widget/my_family_sheet.dart @@ -1,26 +1,29 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/dependencies.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/widget/family_cards.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/common_bottom_sheet.dart'; class MyFamilySheet { static Future show(BuildContext context, List familyLists, Function(FamilyFileResponseModelLists) onSelect) async { + NavigationService navigationService = getIt(); 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), + 'Please select a profile'.needTranslation.toText21(isBold: true), + 'switch from the below list of medical file'.needTranslation.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 + navigationService.pop(); + onSelect(profile); }, onRemove: (profile) {}, isBottomSheet: true), diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index d40104b..3954dd7 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -59,11 +59,8 @@ class _ProfileSettingsState extends State { int length = 3; final SwiperController _controller = SwiperController(); - int _index = 0; - @override Widget build(BuildContext context) { - final MedicalFileViewModel medicalFileViewModel = getIt.get(); return CollapsingListView( title: "Profile & Settings".needTranslation, logout: () {}, @@ -71,28 +68,41 @@ class _ProfileSettingsState extends State { child: SingleChildScrollView( padding: EdgeInsets.only(top: 24, bottom: 24), physics: NeverScrollableScrollPhysics(), - child: Consumer( - builder: (context, model, child) { - print(jsonEncode(medicalFileViewModel.patientFamilyFiles)); + child: Consumer2( + builder: (context, profileVm, medicalVm, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Swiper( - itemCount: medicalFileViewModel.patientFamilyFiles.length, + itemCount: medicalVm.patientFamilyFiles.length, layout: SwiperLayout.STACK, loop: true, itemWidth: MediaQuery.of(context).size.width - 42, indicatorLayout: PageIndicatorLayout.COLOR, axisDirection: AxisDirection.right, controller: _controller, - itemHeight: 210 + 16, + itemHeight: 220 + 16, pagination: const SwiperPagination( alignment: Alignment.bottomCenter, margin: EdgeInsets.only(top: 210 + 8 + 24), builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), ), itemBuilder: (BuildContext context, int index) { - return FamilyCardWidget(isRootUser: true).paddingOnly(right: 16); + return FamilyCardWidget( + profile: medicalVm.patientFamilyFiles[index], + onAddFamilyMemberPress: () { + DialogService dialogService = getIt.get(); + dialogService.showAddFamilyFileSheet( + label: "Add Family Member".needTranslation, + message: "Please fill the below field to add a new family member to your profile".needTranslation, + onVerificationPress: () { + medicalVm.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); + }); + }, + onFamilySwitchPress: (FamilyFileResponseModelLists profile) { + medicalVm.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + }, + ).paddingOnly(right: 16); }, ), GridView( @@ -241,15 +251,12 @@ class _ProfileSettingsState extends State { } class FamilyCardWidget extends StatelessWidget { - FamilyCardWidget({this.isRootUser = true, Key? key}) : super(key: key); - - bool isRootUser; late AppState appState; final Function() onAddFamilyMemberPress; final Function(FamilyFileResponseModelLists member) onFamilySwitchPress; final FamilyFileResponseModelLists profile; - const FamilyCardWidget({required this.onAddFamilyMemberPress, required this.profile, required this.onFamilySwitchPress(FamilyFileResponseModelLists member)}); + FamilyCardWidget({super.key, required this.onAddFamilyMemberPress, required this.profile, required this.onFamilySwitchPress(FamilyFileResponseModelLists member)}); @override Widget build(BuildContext context) { @@ -268,17 +275,16 @@ class FamilyCardWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - Image.asset(appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, width: 56.h, height: 56.h), + Image.asset(profile.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, width: 56.h, height: 56.h), Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 0.h, mainAxisSize: MainAxisSize.min, children: [ - "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" - .toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1), + "${profile.patientName}".toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1), AppCustomChipWidget( icon: AppAssets.file_icon, - labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}", + labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${profile.patientId}", iconSize: 12, ), ], @@ -291,46 +297,137 @@ class FamilyCardWidget extends StatelessWidget { child: Wrap( alignment: WrapAlignment.start, spacing: 8.h, + runSpacing: 8.h, children: [ - AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"), + AppCustomChipWidget(labelText: "${profile.age} Years Old"), AppCustomChipWidget( - icon: AppAssets.blood_icon, - labelText: "${LocaleKeys.bloodType.tr(context: context)}: ${appState.getUserBloodGroup}", - iconColor: AppColors.primaryRedColor, - ), - Consumer(builder: (context, insuranceVM, child) { - return AppCustomChipWidget( - icon: insuranceVM.isInsuranceLoading - ? AppAssets.cancel_circle_icon - : (DateTime.now().isAfter( - DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), - )) - ? AppAssets.cancel_circle_icon - : AppAssets.insurance_active_icon, - labelText: insuranceVM.isInsuranceLoading - ? "Insurance" - : (DateTime.now().isAfter( - DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), - ) - ? "Insurance Expired".needTranslation - : "Insurance Active".needTranslation), - iconColor: insuranceVM.isInsuranceLoading - ? AppColors.primaryRedColor - : (DateTime.now().isAfter( - DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), - )) - ? AppColors.primaryRedColor - : AppColors.successColor, - iconSize: 14, - backgroundColor: insuranceVM.isInsuranceLoading - ? AppColors.primaryRedColor - : (DateTime.now().isAfter( - DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), - )) - ? AppColors.primaryRedColor.withValues(alpha: 0.15) - : AppColors.successColor.withValues(alpha: 0.15), - ).toShimmer2(isShow: insuranceVM.isInsuranceLoading); - }), + icon: AppAssets.blood_icon, labelText: "${LocaleKeys.bloodType.tr()}: ${appState.getAuthenticatedUser()!.bloodGroup ?? "N/A"}", iconColor: AppColors.primaryRedColor), + + Selector( + selector: (context, insuranceVM) => ( + isEmpty: insuranceVM.patientInsuranceList.isEmpty, + patientID: insuranceVM.patientInsuranceList.isNotEmpty ? insuranceVM.patientInsuranceList.first.patientID : null, + isLoading: insuranceVM.isInsuranceLoading, + cardValidTo: insuranceVM.patientInsuranceList.isNotEmpty ? insuranceVM.patientInsuranceList.first.cardValidTo : null + ), + builder: (context, data, child) { + if (data.isEmpty) { + return const SizedBox(); + } else if (profile.responseId != data.patientID) { + return SizedBox(); + } + + final isLoading = data.isLoading; + final isExpired = !isLoading && DateTime.now().isAfter(DateUtil.convertStringToDate(data.cardValidTo)); + + final String icon; + final String labelText; + final Color iconColor; + final Color backgroundColor; + + if (isLoading) { + icon = AppAssets.cancel_circle_icon; + labelText = "Insurance"; + iconColor = AppColors.primaryRedColor; + backgroundColor = AppColors.primaryRedColor; + } else if (isExpired) { + icon = AppAssets.cancel_circle_icon; + labelText = "Insurance Expired".needTranslation; + iconColor = AppColors.primaryRedColor; + backgroundColor = AppColors.primaryRedColor.withValues(alpha: 0.15); + } else { + icon = AppAssets.insurance_active_icon; + labelText = "Insurance Active".needTranslation; + iconColor = AppColors.successColor; + backgroundColor = AppColors.successColor.withValues(alpha: 0.15); + } + + return AppCustomChipWidget( + icon: icon, + labelText: labelText, + iconColor: iconColor, + iconSize: 12, + backgroundColor: backgroundColor, + // padding: EdgeInsets.zero, + ).toShimmer2(isShow: isLoading); + }, + ) + + // Consumer(builder: (context, insuranceVM, child) { + // if (insuranceVM.patientInsuranceList.isEmpty) { + // return const SizedBox(); + // } else if (profile.responseId != insuranceVM.patientInsuranceList.first.patientID) { + // return SizedBox(); + // } + // + // final isLoading = insuranceVM.isInsuranceLoading; + // final isExpired = !isLoading && DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo)); + // + // final String icon; + // final String labelText; + // final Color iconColor; + // final Color backgroundColor; + // + // if (isLoading) { + // icon = AppAssets.cancel_circle_icon; + // labelText = "Insurance"; + // iconColor = AppColors.primaryRedColor; + // backgroundColor = AppColors.primaryRedColor; + // } else if (isExpired) { + // icon = AppAssets.cancel_circle_icon; + // labelText = "Insurance Expired".needTranslation; + // iconColor = AppColors.primaryRedColor; + // backgroundColor = AppColors.primaryRedColor.withValues(alpha: 0.15); + // } else { + // icon = AppAssets.insurance_active_icon; + // labelText = "Insurance Active".needTranslation; + // iconColor = AppColors.successColor; + // backgroundColor = AppColors.successColor.withValues(alpha: 0.15); + // } + // + // return AppCustomChipWidget( + // icon: icon, + // labelText: labelText, + // iconColor: iconColor, + // iconSize: 12, + // backgroundColor: backgroundColor, + // // padding: EdgeInsets.zero, + // ).toShimmer2(isShow: isLoading); + // }) + + // Consumer(builder: (context, insuranceVM, child) { + // return insuranceVM.patientInsuranceList. isNotEmpty ? AppCustomChipWidget( + // icon: insuranceVM.isInsuranceLoading + // ? AppAssets.cancel_circle_icon + // : (DateTime.now().isAfter( + // DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), + // )) + // ? AppAssets.cancel_circle_icon + // : AppAssets.insurance_active_icon, + // labelText: insuranceVM.isInsuranceLoading + // ? "Insurance" + // : (DateTime.now().isAfter( + // DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), + // ) + // ? "Insurance Expired".needTranslation + // : "Insurance Active".needTranslation), + // iconColor: insuranceVM.isInsuranceLoading + // ? AppColors.primaryRedColor + // : (DateTime.now().isAfter( + // DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), + // )) + // ? AppColors.primaryRedColor + // : AppColors.successColor, + // iconSize: 14, + // backgroundColor: insuranceVM.isInsuranceLoading + // ? AppColors.primaryRedColor + // : (DateTime.now().isAfter( + // DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), + // )) + // ? AppColors.primaryRedColor.withValues(alpha: 0.15) + // : AppColors.successColor.withValues(alpha: 0.15), + // ).toShimmer2(isShow: insuranceVM.isInsuranceLoading) : SizedBox(); + // }), ], ), ), @@ -426,8 +523,7 @@ class FamilyCardWidget extends StatelessWidget { ).paddingOnly(top: 12, right: 16, left: 16, bottom: 16); } - - // //TODO: Add family file switch logic here +// //TODO: Add family file switch logic here // isRootUser // ? CustomButton(icon: AppAssets.add_family, text: "Add a new family member".needTranslation, height: 40.h, fontSize: 14, onPressed: () {}) // .paddingOnly(top: 12, right: 16, left: 16, bottom: 16) diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 4d332b3..7003a31 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -24,6 +24,8 @@ abstract class DialogService { Future showFamilyBottomSheetWithoutH( {String? label, required String message, required Function(FamilyFileResponseModelLists response) onSwitchPress, required List profiles}); + Future showFamilyBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}); + Future showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}); Future showAddFamilyFileSheet({String? label, String? message, required Function() onVerificationPress}); @@ -119,6 +121,18 @@ class DialogServiceImp implements DialogService { callBackFunc: () {}); } + @override + Future showFamilyBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}) async { + final context = navigationService.navigatorKey.currentContext; + if (context == null) return; + showCommonBottomSheetWithoutHeight( + context, + title: label ?? "", + child: child ?? SizedBox(), + callBackFunc: () {}, + ); + } + @override Future showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async { final context = navigationService.navigatorKey.currentContext; diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index 38552fb..10b98bc 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -24,8 +24,8 @@ class AppCustomChipWidget extends StatelessWidget { this.deleteIconColor = AppColors.textColor, this.deleteIconHasColor = false, this.padding = EdgeInsets.zero, - this.onChipTap - this.labelPadding , + this.onChipTap, + this.labelPadding, }); final String? labelText; @@ -47,54 +47,58 @@ class AppCustomChipWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return ChipTheme( - data: ChipThemeData( - padding: EdgeInsets.all(0.0), - shape: SmoothRectangleBorder( - side: BorderSide( - width: 10.0, - color: Colors.transparent, // Crucially, set color to transparent - style: BorderStyle.none, + return GestureDetector( + onTap: onChipTap, + child: ChipTheme( + data: ChipThemeData( + padding: EdgeInsets.all(0.0), + shape: SmoothRectangleBorder( + side: BorderSide( + width: 10.0, + color: Colors.transparent, // Crucially, set color to transparent + style: BorderStyle.none, + ), + borderRadius: BorderRadius.circular(8.0), // Apply a border radius of 16.0 ), - borderRadius: BorderRadius.circular(8.0), // Apply a border radius of 16.0 ), - ), - child: icon.isNotEmpty - ? Chip( - avatar: icon.isNotEmpty ? Utils.buildSvgWithAssets(icon: icon, width: iconSize.h, height: iconSize.h, iconColor: iconHasColor ? iconColor : null) : SizedBox.shrink(), - label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), - // padding: EdgeInsets.all(0.0), - padding: padding, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - labelPadding: labelPadding??EdgeInsetsDirectional.only(start: 0.h, end: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), - backgroundColor: backgroundColor, - shape: shape ?? - SmoothRectangleBorder( - borderRadius: BorderRadius.circular(8 ?? 0), - smoothness: 10, - side: BorderSide(color: AppColors.transparent, width: 1.5), - ), - deleteIcon: deleteIcon?.isNotEmpty == true - ? Utils.buildSvgWithAssets(icon: deleteIcon!, width: deleteIconSize!.width!.h, height: deleteIconSize!.height.h, iconColor: deleteIconHasColor ? deleteIconColor : null) - : null, - onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, - ) - : Chip( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), - padding: EdgeInsets.all(0.0), - backgroundColor: backgroundColor, - shape: shape ?? SmoothRectangleBorder( - borderRadius: BorderRadius.circular(8 ?? 0), - smoothness: 10, - side: BorderSide(color: AppColors.transparent, width: 1.5), + child: icon.isNotEmpty + ? Chip( + avatar: icon.isNotEmpty ? Utils.buildSvgWithAssets(icon: icon, width: iconSize.h, height: iconSize.h, iconColor: iconHasColor ? iconColor : null) : SizedBox.shrink(), + label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), + // padding: EdgeInsets.all(0.0), + padding: padding, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + labelPadding: labelPadding ?? EdgeInsetsDirectional.only(start: 0.h, end: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), + backgroundColor: backgroundColor, + shape: shape ?? + SmoothRectangleBorder( + borderRadius: BorderRadius.circular(8 ?? 0), + smoothness: 10, + side: BorderSide(color: AppColors.transparent, width: 1.5), + ), + deleteIcon: deleteIcon?.isNotEmpty == true + ? Utils.buildSvgWithAssets(icon: deleteIcon!, width: deleteIconSize!.width!.h, height: deleteIconSize!.height.h, iconColor: deleteIconHasColor ? deleteIconColor : null) + : null, + onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, + ) + : Chip( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), + padding: EdgeInsets.all(0.0), + backgroundColor: backgroundColor, + shape: shape ?? + SmoothRectangleBorder( + borderRadius: BorderRadius.circular(8 ?? 0), + smoothness: 10, + side: BorderSide(color: AppColors.transparent, width: 1.5), + ), + labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: deleteIcon?.isNotEmpty == true ? -2.h : 8.h), + deleteIcon: deleteIcon?.isNotEmpty == true + ? Utils.buildSvgWithAssets(icon: deleteIcon!, width: deleteIconSize!.width.h, height: deleteIconSize!.height.h, iconColor: deleteIconHasColor ? deleteIconColor : null) + : null, + onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, ), - labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: deleteIcon?.isNotEmpty == true ? -2.h : 8.h), - deleteIcon: deleteIcon?.isNotEmpty == true - ? Utils.buildSvgWithAssets(icon: deleteIcon!, width: deleteIconSize!.width.h, height: deleteIconSize!.height.h, iconColor: deleteIconHasColor ? deleteIconColor : null) - : null, - onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, - ), + ), ); } }