diff --git a/assets/images/png/dummy_user.png b/assets/images/png/dummy_user.png new file mode 100644 index 0000000..b4bace4 Binary files /dev/null and b/assets/images/png/dummy_user.png differ 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/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/api/api_client.dart b/lib/core/api/api_client.dart index a36f554..24f682b 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -88,16 +88,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; @@ -173,8 +173,8 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - body['TokenID'] = "@dm!n"; - body['PatientID'] = 4772172; + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = 4772172; // body['PatientTypeID'] = 1; // // body['PatientOutSA'] = 0; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 98c7ec0..9591cfe 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 @@ -832,6 +832,13 @@ 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 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 static final double appVersionID = 18.7; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index d1b8b93..1e9eaa4 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -152,6 +152,9 @@ class AppAssets { static const String ic_filters = '$svgBasePath/filters.svg'; static const String ic_close = '$svgBasePath/ic_close.svg'; static const String ic_cross_circle = '$svgBasePath/cross_circle.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'; @@ -163,6 +166,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'; @@ -177,6 +182,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/app_state.dart b/lib/core/app_state.dart index c906374..404e22b 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -37,14 +37,16 @@ class AppState { AuthenticatedUser? _authenticatedRootUser; AuthenticatedUser? _authenticatedChildUser; - void setAuthenticatedUser(AuthenticatedUser authenticatedUser, {bool isFamily = false}) { + int? _superUserID; + bool isChildLoggedIn = false; + + void setAuthenticatedUser(AuthenticatedUser? authenticatedUser, {bool isFamily = false}) { if (isFamily) { _authenticatedChildUser = authenticatedUser; } else { setIsAuthenticated = true; _authenticatedRootUser = authenticatedUser; } - } AuthenticatedUser? getAuthenticatedUser({bool isFamily = false}) { @@ -55,6 +57,14 @@ 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; @@ -97,6 +107,14 @@ 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 = []; @@ -135,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 3ce8a19..42349ef 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -26,6 +26,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'; @@ -103,11 +104,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( @@ -173,6 +170,7 @@ class AppDependencies { () => AuthenticationViewModel( authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), ); + getIt.registerLazySingleton(() => ProfileSettingsViewModel()); getIt.registerLazySingleton( () => DateRangeSelectorRangeViewModel(), diff --git a/lib/core/enums.dart b/lib/core/enums.dart index bb6c6fe..4151bed 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, rejected } + extension CalenderExtension on CalenderEnum { int get toInt { switch (this) { @@ -138,6 +140,61 @@ 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 6; + case FamilyFileEnum.rejected: + 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'; + case FamilyFileEnum.rejected: + return isArabic ? 'مرفوض' : 'Rejected'; + } + } + + 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/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 8b993f1..a4ea936 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({ @@ -125,6 +126,9 @@ class RequestUtils { required bool isForRegister, required bool isFileNo, dynamic payload, + required bool isExcludedUser, + required bool isFormFamilyFile, + int? responseID, }) { AppState _appState = getIt.get(); var request = SendActivationRequest(); @@ -156,6 +160,15 @@ class RequestUtils { request.isRegister = false; } request.deviceTypeID = request.searchType; + + if (isFormFamilyFile) { + //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; } @@ -247,19 +260,29 @@ class RequestUtils { }; } - static dynamic getAddFamilyRequest({required String nationalIDorFile, required String mobileNo, required String countryCode, required int loginType}) { - var request = {}; + static Future getAddFamilyRequest({required String nationalIDorFile, required String mobileNo, required String countryCode}) async { + FamilyFileRequest request = FamilyFileRequest(); + int? loginType = 0; + + 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; + 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.searchType = loginType; + request.sharedPatientMobileNumber = mobileNo; + request.zipCode = countryCode; + request.isRegister = false; + request.patientStatus = 2; + request.isDentalAllowedBackend = false; + return request; } } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index c9eef9d..c4a2db8 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -629,8 +629,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/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 059e553..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(false); // TODO: @Aamir make it dynamic based on app language + 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 2eaeb80..bec5470 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -3,8 +3,10 @@ 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/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'; @@ -17,12 +19,18 @@ 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 isFormFamilyFile = false}); Future>> checkActivationCodeRepo( - {required dynamic newRequest, // could be CheckActivationCodeReq or CheckActivationCodeRegisterReq + {required dynamic newRequest, required String? activationCode, - required bool isRegister}); + required bool isRegister, + bool isFormFamilyFile = false, + int? patientShareRequestID, + int? responseID, + bool isSwitchUser = false, + int? patientID, + int? loginType}); Future>> checkIfUserAgreed({required dynamic commonAuthanticatedRequest}); @@ -134,18 +142,33 @@ class AuthenticationRepoImp implements AuthenticationRepo { required dynamic sendActivationCodeReq, String? languageID, bool isRegister = false, + bool isFormFamilyFile = false, }) async { 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; Failure? failure; await apiClient.post( - isRegister ? ApiConsts.sendActivationCodeRegister : ApiConsts.sendActivationCode, - body: sendActivationCodeReq.toJson(), + isFormFamilyFile + ? ApiConsts.sendFamilyFileActivation + : isRegister + ? ApiConsts.sendActivationCodeRegister + : ApiConsts.sendActivationCode, + body: isFormFamilyFile ? payload : sendActivationCodeReq.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, @@ -172,24 +195,122 @@ class AuthenticationRepoImp implements AuthenticationRepo { } @override - Future>> checkActivationCodeRepo({ - required dynamic newRequest, // could be CheckActivationCodeReq or CheckActivationCodeRegisterReq - required String? activationCode, - required bool isRegister, - }) 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 { + 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; } - final endpoint = isRegister ? ApiConsts.checkActivationCodeRegister : ApiConsts.checkActivationCode; + Map familyRequest = {}; + if (isFormFamilyFile) { + 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, + }); + + if (loginType != 0) { + switchRequest['SuperUser'] = patientID; + switchRequest['DeviceToken'] = null; + } else { + switchRequest['LoginType'] = 2; + } + + if (appState.getSuperUserID == responseID) { + switchRequest['PatientIdentificationID'] = ""; + switchRequest.removeWhere((key, value) => ['NationalID', 'isDentalAllowedBackend', 'ProjectOutSA', 'ForRegisteration'].contains(key)); + } + } + + final endpoint = isFormFamilyFile + ? ApiConsts.checkActivationCodeForFamily + : isRegister + ? ApiConsts.checkActivationCodeRegister + : ApiConsts.checkActivationCode; try { GenericApiModel? apiResponse; @@ -197,7 +318,13 @@ class AuthenticationRepoImp implements AuthenticationRepo { await apiClient.post( endpoint, - body: isRegister ? newRequest : newRequest.toJson(), + body: isFormFamilyFile + ? familyRequest + : isRegister + ? newRequest + : isSwitchUser + ? switchRequest + : newRequest.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, @@ -226,7 +353,6 @@ class AuthenticationRepoImp implements AuthenticationRepo { @override Future>> checkIfUserAgreed({required dynamic commonAuthanticatedRequest}) async { commonAuthanticatedRequest['Region'] = 1; - print("dsfsdd"); try { GenericApiModel? apiResponse; Failure? failure; @@ -261,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 b43858c..e643c5d 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/nationality_country_model import 'package:hmg_patient_app_new/core/common_models/privilege/HMCProjectListModel.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,9 @@ import 'package:hmg_patient_app_new/features/authentication/models/resp_models/a import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_activation_code_resp_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; +import 'package:hmg_patient_app_new/features/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/features/my_appointments/my_appointments_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'; @@ -243,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(); @@ -342,24 +346,33 @@ class AuthenticationViewModel extends ChangeNotifier { ); } - Future sendActivationCode( - {required OTPTypeEnum otpTypeEnum, required String nationalIdOrFileNumber, required String phoneNumber, required bool isForRegister, dynamic payload, bool isComingFromResendOTP = false}) async { + Future sendActivationCode({required OTPTypeEnum otpTypeEnum, + required String nationalIdOrFileNumber, + required String phoneNumber, + required bool isForRegister, + 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, - ); + 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.countryCode + ? false + : true, + payload: payload, + isExcludedUser: isExcludedUser, + isFormFamilyFile: isFormFamilyFile, + responseID: responseID); // TODO: GET APP SMS SIGNATURE HERE request.sMSSignature = await getSignature(); @@ -368,11 +381,17 @@ 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', isFormFamilyFile: isFormFamilyFile); resultEither.fold( (failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + int? patientShareRequestID = 0; + if (isFormFamilyFile) { + patientShareRequestID = apiResponse.data['PatientShareRequestID']; + _appState.setFamilyFileTokenID = apiResponse.data['LogInTokenID']; + } if (apiResponse.messageStatus == 2) { LoaderBottomSheet.hideLoader(); await _dialogService.showCommonBottomSheetWithoutH( @@ -383,7 +402,18 @@ 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, + isFormFamilyFile: isFormFamilyFile, + isExcludedUser: isExcludedUser, + responseID: responseID, + patientShareRequestID: patientShareRequestID, + ); + } } else { // TODO: Handle isSMSSent false // navigateToOTPScreen(otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber); @@ -401,8 +431,17 @@ class AuthenticationViewModel extends ChangeNotifier { return isUserComingForRegister; } - Future checkActivationCode( - {required String? activationCode, required OTPTypeEnum otpTypeEnum, required Function(String? message) onWrongActivationCode, Function()? onResendActivation}) 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( @@ -428,6 +467,7 @@ class AuthenticationViewModel extends ChangeNotifier { //TODO: Error Here IN Zip Code. loginType: loginTypeEnum.toInt) .toJson(); + LoaderBottomSheet.showLoader(); if (isForRegister) { if (_appState.getUserRegistrationPayload.patientOutSa == 0) request['DOB'] = _appState.getUserRegistrationPayload.dob; @@ -463,7 +503,17 @@ 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, + isFormFamilyFile: isFormFamilyFile, + patientShareRequestID: patientShareRequestID, + responseID: responseID, + isSwitchUser: isSwitchUser, + patientID: patientID, + loginType: _appState.getSuperUserID != null ? 0 : 2, + ); resultEither.fold( (failure) async => await _errorHandlerService.handleError( @@ -494,48 +544,86 @@ class AuthenticationViewModel extends ChangeNotifier { // Navigator.popUntil(context, (route) => Utils.route(route, equalsTo: RegisterNew)); return; } else { - 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(); + if (isFormFamilyFile) { + // await navigateToFamilyFilePage(); + MedicalFileViewModel medicalVm = getIt(); + if (!_appState.getIsChildLoggedIn) { + await medicalVm.getFamilyFiles(status: 0); + await medicalVm.getAllPendingRecordsByResponseId(); + _navigationService.popUntilNamed(AppRoutes.landingScreen); + } } 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) { + 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; + } + activation.list!.first.bloodGroup = activation.patientBlodType; + _appState.setAuthenticatedUser(activation.list!.first); + _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); + } + // _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); + _appState.setAppAuthToken = activation.authenticationTokenId; + 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 + if (_appState.getSelectDeviceByImeiRespModelElement != null) { + _appState.getSelectDeviceByImeiRespModelElement!.logInType = loginTypeEnum.toInt; + } + LoaderBottomSheet.hideLoader(); +// + if (!isSwitchUser && !_appState.getIsChildLoggedIn) { + MedicalFileViewModel medicalVm = getIt(); + await insertPatientIMEIData(loginTypeEnum.toInt); + await medicalVm.getFamilyFiles(status: 0); //TODO: Remove status: 1 by Aamir Need to Discuss With Sultan + await medicalVm.getAllPendingRecordsByResponseId(); + } + + await clearDefaultInputValues(); + if (isUserAgreedBefore) { + navigateToHomeScreen(); + } else { + MyAppointmentsViewModel myAppointmentsVM = getIt(); + myAppointmentsVM.setIsAppointmentDataToBeLoaded(true); + 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)) { @@ -573,13 +661,25 @@ 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 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(), + isFormFamilyFile: isFormFamilyFile, otpTypeEnum: otpTypeEnum, + responseID: responseID, + patientShareRequestID: patientShareRequestID, onWrongActivationCode: (String? value) { onWrongActivationCode(message: value); }, @@ -587,12 +687,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/models/resp_models/authenticated_user_resp_model.dart b/lib/features/authentication/models/resp_models/authenticated_user_resp_model.dart index 8848fde..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 @@ -69,6 +69,8 @@ class AuthenticatedUser { dynamic authenticatedUserPatientPayType; dynamic authenticatedUserPatientType; dynamic authenticatedUserStatus; + int? superUser; + bool? isParentUser; AuthenticatedUser({ this.setupId, @@ -139,6 +141,8 @@ class AuthenticatedUser { this.authenticatedUserPatientPayType, this.authenticatedUserPatientType, this.authenticatedUserStatus, + this.superUser, + this.isParentUser, }); factory AuthenticatedUser.fromRawJson(String str) => AuthenticatedUser.fromJson(json.decode(str)); @@ -146,146 +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"], - ); + 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, - }; + "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 { @@ -306,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/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/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 7077fd0..a460d9f 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -25,13 +25,19 @@ abstract class MedicalFileRepo { Future>> getPatientMedicalReportPDF(PatientMedicalReportResponseModel patientMedicalReportResponseModel, AuthenticatedUser authenticatedUser); - Future>>> getPatientFamilyFiles(); + Future>>> getPatientFamilyFiles(int? status, int patientId); - Future>>> addFamilyFile({required dynamic request}); + Future>>> getAllPendingRecordsByResponseId({required Map request}); + + Future>> addFamilyFile({required dynamic request}); Future>>> getPatientAppointmentsForMedicalReport(); Future>> insertRequestForMedicalReport({required PatientAppointmentHistoryResponseModel appointmentHistoryResponseModel}); + + Future>> removeFamilyFile({required int? id}); + + Future>> acceptRejectFamilyFile({required int? id, required int? status}); } class MedicalFileRepoImp implements MedicalFileRepo { @@ -279,13 +285,13 @@ class MedicalFileRepoImp implements MedicalFileRepo { } @override - Future>>> getPatientFamilyFiles() async { + Future>>> getPatientFamilyFiles(int? status, int patientID) async { try { GenericApiModel>? apiResponse; Failure? failure; await apiClient.post( - FAMILY_FILES, - body: {"Status": 3}, + ApiConsts.getAllSharedRecordsByStatus, + body: {if (status != null) "Status": status, "PatientID": patientID}, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, @@ -318,9 +324,44 @@ class MedicalFileRepoImp implements MedicalFileRepo { } @override - Future>>> addFamilyFile({dynamic request}) async { + Future>>> getAllPendingRecordsByResponseId({required Map request}) async { try { - GenericApiModel>? apiResponse; + 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['GetAllPendingRecordsList']; + final familyLists = list.map((item) => FamilyFileResponseModelLists.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: familyLists, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> addFamilyFile({dynamic request}) async { + try { + GenericApiModel? apiResponse; Failure? failure; await apiClient.post( ApiConsts.addFamilyFile, @@ -330,17 +371,82 @@ 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()); + } + }, + ); + 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>> 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()); } diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 3f0573f..b5d9d03 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -1,7 +1,15 @@ +import 'dart:convert'; +import 'dart:developer'; + 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/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'; @@ -10,6 +18,8 @@ import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_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'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; class MedicalFileViewModel extends ChangeNotifier { int selectedTabIndex = 0; @@ -34,13 +44,26 @@ class MedicalFileViewModel extends ChangeNotifier { PatientAppointmentHistoryResponseModel? patientMedicalReportSelectedAppointment; 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}); @@ -64,6 +87,11 @@ class MedicalFileViewModel extends ChangeNotifier { notifyListeners(); } + void onFamilyFileTabChange(int index) { + setSelectedFamilyFileTabIndex = index; + notifyListeners(); + } + setIsPatientVaccineListLoading(bool isLoading) { isPatientVaccineListLoading = isLoading; notifyListeners(); @@ -247,8 +275,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.getSuperUserID != null ? _appState.getSuperUserID! : _appState.getAuthenticatedUser()!.patientId!); result.fold( (failure) async => await errorHandlerService.handleError( @@ -261,16 +289,85 @@ class MedicalFileViewModel extends ChangeNotifier { 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), - ); + if (apiResponse.data != null) { + // 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, + ); + + // Clear and start fresh with current user + patientFamilyFiles.clear(); + patientFamilyFiles.add(currentUserFamilyFile); + + final List activeFamilyFiles = []; + final List pendingFamilyFiles = []; + + for (var element in apiResponse.data!) { + if (element.status == null) { + continue; + } + + final isPending = element.status == FamilyFileEnum.pending.toInt || element.status == FamilyFileEnum.rejected.toInt; + final isActive = element.status == FamilyFileEnum.active.toInt; + + if (!isPending && !isActive) { + continue; + } + + final familyFile = FamilyFileResponseModelLists( + id: element.id, + patientId: element.patientId, + patientName: element.patientName!, + isActive: element.isActive, + gender: element.gender!, + responseId: element.responseId, + 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) { + familyFile.isRequestFromMySide = true; + pendingFamilyFiles.add(familyFile); + } + if (isActive) { + activeFamilyFiles.add(familyFile); + } + } + + for (var activeFile in activeFamilyFiles) { + if (!patientFamilyFiles.any((e) => e.responseId == activeFile.responseId)) { + patientFamilyFiles.add(activeFile); + } + } + + this.pendingFamilyFiles.clear(); + this.pendingFamilyFiles.addAll(pendingFamilyFiles); + } + notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -280,39 +377,151 @@ class MedicalFileViewModel extends ChangeNotifier { ); } - Future switchFamilyFiles({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await medicalFileRepo.getPatientFamilyFiles(); + 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, - onOkPressed: () { - onError!(failure.message); - }, - ), + (failure) async => await errorHandlerService.handleError(failure: failure), (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); + if (apiResponse.data != null) { + final List tempPendingFamilyFiles = []; + for (var element in apiResponse.data!) { + 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, + 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.responseId == element.responseId))); + pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => !pendingFamilyFiles.any((e) => e.patientId == element.patientId))); } + notifyListeners(); } }, ); } + 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}) async { + LoaderBottomSheet.showLoader(); + 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); + + final resultEither = await medicalFileRepo.addFamilyFile(request: request.toJson()); + resultEither.fold((failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) async { + if (apiResponse.messageStatus == 2) { + LoaderBottomSheet.hideLoader(); + _dialogService.showErrorBottomSheet( + message: apiResponse.errorMessage!, + onOkPressed: () { + 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(); + 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 getPatientMedicalReportAppointmentsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { patientMedicalReportAppointmentHistoryList.clear(); notifyListeners(); @@ -357,8 +566,33 @@ 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 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/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 926f75d..60fbff2 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -92,6 +92,9 @@ class MyAppointmentsViewModel extends ChangeNotifier { } setIsAppointmentDataToBeLoaded(bool val) { + if (val) { + isMyAppointmentsLoading = true; + } isAppointmentDataToBeLoaded = val; notifyListeners(); } @@ -112,9 +115,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async { if (!isAppointmentDataToBeLoaded) return; + filteredAppointmentList.clear(); patientAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear(); patientArrivedAppointmentsHistoryList.clear(); + notifyListeners(); final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: isActiveAppointment, isArrivedAppointments: isArrivedAppointments); final resultArrived = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index d235f4d..7de153f 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -19,6 +19,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/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'; @@ -76,6 +77,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,13 +106,12 @@ class _LandingPageState extends State { @override Widget build(BuildContext context) { - appState = getIt.get(); - NavigationService navigationService = getIt.get(); bookAppointmentsViewModel = Provider.of(context, listen: false); myAppointmentsViewModel = Provider.of(context, listen: false); prescriptionsViewModel = Provider.of(context, listen: false); insuranceViewModel = Provider.of(context, listen: false); immediateLiveCareViewModel = Provider.of(context, listen: false); + appState = getIt.get(); 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 8ff081f..1eff927 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; @@ -32,6 +33,8 @@ import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_upd import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; +import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart'; @@ -75,6 +78,7 @@ class _MedicalFilePageState extends State { @override void initState() { + appState = getIt.get(); scheduleMicrotask(() { if (appState.isAuthenticated) { labViewModel.initLabProvider(); @@ -94,116 +98,140 @@ 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, - 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".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: [ - 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}", - ), - 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: AppColors.primaryRedColor, - activeBackgroundColor: AppColors.primaryRedColor.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, + ), + ], + ), + ], + ) ], - onTabChange: (index) { - medicalFileVM.onTabChanged(index); - }, - ).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 24.h), - getSelectedTabData(medicalFileVM.selectedTabIndex), + ), + 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( + children: [ + CustomTabBar( + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.primaryRedColor.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), + ], + ); + }), + ], ), ), ); diff --git a/lib/presentation/my_family/my_family.dart b/lib/presentation/my_family/my_family.dart index 25f5f3c..8a04cd5 100644 --- a/lib/presentation/my_family/my_family.dart +++ b/lib/presentation/my_family/my_family.dart @@ -1,23 +1,29 @@ +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'; 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'; +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'; 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; @@ -34,161 +40,95 @@ 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 = context.read(); + } @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.scaffoldBgColor, - appBar: CustomAppBar( - onBackPressed: () { - Navigator.of(context).pop(); - }, - onLanguageChanged: (lang) {}, - hideLogoAndLang: true, - ), - body: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.myMedicalFile.tr().toText26(color: AppColors.textColor, weight: FontWeight.w600, letterSpacing: -2), - SizedBox(height: 25.h), - CustomTabBar( - tabs: tabs, - onTabChange: (int index) {}, - ), - SizedBox(height: 25.h), - FamilyCards(profiles: widget.profiles, onSelect: widget.onSelect, isShowDetails: true), - SizedBox(height: 20.h), - ], - ), - ).paddingSymmetrical(20, 0), - bottomSheet: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), - ), - padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 20.h), - child: CustomButton( - text: "Add a new family member", - onPressed: () { - showModelSheet(); + 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)), + ), + 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); + }); + }, + icon: AppAssets.add_icon, + height: 56.h, + fontSize: 16, + borderRadius: 12.h, + fontWeight: FontWeight.w500)) + : SizedBox(), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomTabBar( + 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); }, - icon: AppAssets.add_icon, - height: 56.h, - fontWeight: FontWeight.w600, - )), + ), + SizedBox(height: 25.h), + Selector(selector: (_, model) => model.getSelectedFamilyFileTabIndex, builder: (context, selectedIndex, child) => getFamilyTabs(index: selectedIndex)), + SizedBox(height: 20.h), + ], + ).paddingSymmetrical(20, 0), ); } - Future showModelSheet() async { - AuthenticationViewModel authVm = getIt.get(); - return await showCommonBottomSheetWithoutHeight(context, - title: "Add Family Member", - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - "Please fill the below field to add a new family member to your profile".toText16(color: AppColors.textColor, weight: FontWeight.w500), - SizedBox(height: 20.h), - Container( - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)), - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - children: [ - CustomCountryDropdown( - countryList: CountryEnum.values, - onCountryChange: authVm.onCountryChange, - ).paddingOnly(top: 8.h, bottom: 16.h), - Divider( - height: 1.h, - color: AppColors.spacerLineColor, - ), - TextInputWidget( - labelText: LocaleKeys.nationalIdNumber.tr(), - hintText: "xxxxxxxxx", - controller: authVm.nationalIdController, - // focusNode: _nationalIdFocusNode, - isEnable: true, - prefix: null, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - autoFocus: true, - keyboardType: TextInputType.number, - padding: EdgeInsets.symmetric(vertical: 8.h), - leadingIcon: AppAssets.student_card, - ).paddingOnly(top: 8.h, bottom: 8.h), - Divider( - height: 1.h, - color: AppColors.spacerLineColor, - ), - TextInputWidget( - labelText: LocaleKeys.phoneNumber.tr(), - hintText: "574345434", - controller: authVm.phoneNumberController, - isEnable: true, - prefix: authVm.selectedCountrySignup.countryCode, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - autoFocus: true, - keyboardType: TextInputType.number, - padding: EdgeInsets.symmetric(vertical: 8.h), - leadingIcon: AppAssets.smart_phone, - ).paddingOnly(top: 8.h, bottom: 4), - - //TextInputWidget( - // labelText: widget.isForEmail ? LocaleKeys.email.tr() : LocaleKeys.phoneNumber.tr(), - // hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx", - // controller: widget.textController!, - // focusNode: _textFieldFocusNode, - // autoFocus: widget.autoFocus, - // padding: EdgeInsets.all(8.h), - // keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number, - // onChange: (value) { - // if (widget.onChange != null) { - // widget.onChange!(value); - // } - // }, - // onCountryChange: (value) { - // if (widget.onCountryChange != null) { - // widget.onCountryChange!(value); - // } - // }, - // isEnable: true, - // isReadOnly: widget.isFromSavedLogin, - // prefix: widget.isForEmail ? null : widget.countryCode, - // isBorderAllowed: false, - // isAllowLeadingIcon: true, - // fontSize: 13.h, - // isCountryDropDown: widget.isEnableCountryDropdown, - // leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone, - // ) - ], - ), - ), - SizedBox(height: 20.h), - CustomButton( - text: "Verify the member", - onPressed: () { - FocusScope.of(context).unfocus(); - if (ValidationUtils.isValidatedIdAndPhoneWithCountryValidation( - nationalId: authVm.nationalIdController.text, - selectedCountry: authVm.selectedCountrySignup, - phoneNumber: authVm.phoneNumberController.text, - onOkPress: () { - Navigator.of(context).pop(); - }, - )) {} - }, - icon: AppAssets.add_icon, - height: 56.h, - fontWeight: FontWeight.w600), - SizedBox(height: 20.h), - ], - ), - callBackFunc: () {}); + 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); + }, + onRemove: (FamilyFileResponseModelLists profile) { + medicalVM!.removeFileFromFamilyMembers(id: profile.id); + }, + isLeftAligned: true, + isShowDetails: true, + isShowRemoveButton: true, + ); + case 1: + return FamilyCards( + profiles: medicalVM!.pendingFamilyFiles, + isRequestDesign: true, + onSelect: (FamilyFileResponseModelLists profile) { + medicalVM!.acceptRejectFileFromFamilyMembers(id: profile.id, status: 3); + }, + onRemove: (FamilyFileResponseModelLists profile) { + medicalVM!.acceptRejectFileFromFamilyMembers(id: profile.id, status: 4); + }, + isShowDetails: true, + ); + 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 306692b..eeb82e5 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'; @@ -17,10 +19,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(); @@ -31,79 +46,294 @@ class _FamilyCardsState extends State { @override Widget build(BuildContext context) { - return GridView.builder( + DialogService dialogService = getIt.get(); + if (widget.isRequestDesign) { + return Column( + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.alertSquare), + SizedBox(width: 8.h), + "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), + ], + ), + 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), + 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)) + .toText12(isBold: false, isCenter: false, maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ]), + SizedBox(height: 8.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), + ], + ); + } 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.65.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").toText14(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), + SizedBox(height: 8.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( + height: 4.h, + ), + 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); + }), + ), + ], + ], + ), + ), + ); + }, + ); + } + } + + Widget manageFamily() { + NavigationService navigationService = getIt(); + return ListView.builder( shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: widget.profiles.length, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 10.h, - mainAxisSpacing: 10.h, - childAspectRatio: widget.isShowDetails ? 0.56.h : 0.74.h, - ), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsetsGeometry.zero, + itemCount: widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).length, itemBuilder: (context, index) { - final profile = widget.profiles[index]; - final isActive = (profile.responseId == appState.getAuthenticatedUser()?.patientId); + 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: isActive ? 0.4 : 1.0, // Fade all content if active + opacity: 1.0, child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ - SizedBox(height: 5.h), - Utils.buildImgWithAssets( - icon: profile.gender == 1 ? ((profile.age ?? 0) < 7 ? AppAssets.babyBoyImg : AppAssets.male_img) : (profile.age! < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg), - width: 80.h, - height: 78.h), + 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), - (profile.patientName ?? "Unknown").toText16(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), - SizedBox(height: 4.h), CustomChipWidget( - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: "Relation: ${profile.relationship ?? "N/A"}", - iconAsset: AppAssets.heart, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor), - widget.isShowDetails ? SizedBox(height: 4.h) : SizedBox(), - widget.isShowDetails - ? CustomChipWidget( - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: "Age: ${profile.age ?? "N/A"} Years", - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor, - ) - : SizedBox(), - widget.isShowDetails ? SizedBox(height: 8.h) : SizedBox(), - Spacer(), - if (isActive) - CustomButton( - height: 40.h, - onPressed: () {}, - text: LocaleKeys.active.tr(), - backgroundColor: Colors.grey.shade200, - borderColor: Colors.grey.shade200, - textColor: AppColors.greyTextColor, - fontSize: 13.h, - ).paddingOnly(top: 0, bottom: 0) - else - CustomButton( - height: 40.h, - onPressed: () => widget.onSelect(profile), - text: LocaleKeys.select.tr(), - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 13.h, - icon: widget.isBottomSheet ? null : AppAssets.heart, - iconColor: AppColors.primaryRedColor, - padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), - ).paddingOnly(top: 0, bottom: 0), + 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, + ), + ), + ], + ), ], ), ), @@ -111,4 +341,31 @@ class _FamilyCardsState extends State { }, ); } + + 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 da822b1..d469ab2 100644 --- a/lib/presentation/my_family/widget/my_family_sheet.dart +++ b/lib/presentation/my_family/widget/my_family_sheet.dart @@ -1,27 +1,31 @@ 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 { - return await showCommonBottomSheetWithoutHeight( + 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), callBackFunc: () {}, ); diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index e5ec389..efc02ad 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -1,3 +1,6 @@ +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'; @@ -5,16 +8,22 @@ 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/utils/date_util.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/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; +import 'package:hmg_patient_app_new/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'; @@ -50,8 +59,6 @@ class _ProfileSettingsState extends State { int length = 3; final SwiperController _controller = SwiperController(); - int _index = 0; - @override Widget build(BuildContext context) { return CollapsingListView( @@ -61,27 +68,41 @@ class _ProfileSettingsState extends State { child: SingleChildScrollView( padding: EdgeInsets.only(top: 24, bottom: 24), physics: NeverScrollableScrollPhysics(), - child: Consumer( - builder: (context, model, child) { + child: Consumer2( + builder: (context, profileVm, medicalVm, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Swiper( - itemCount: 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: 200.h, 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( @@ -230,115 +251,300 @@ 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; + + FamilyCardWidget({super.key, required this.onAddFamilyMemberPress, required this.profile, required this.onFamilySwitchPress(FamilyFileResponseModelLists member)}); @override Widget build(BuildContext context) { - appState = getIt.get(); + 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( + crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ Row( 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.responseId}", iconSize: 12, ), ], ).expanded, Icon(Icons.qr_code, size: 56) ], - ).expanded, + ), + SizedBox(height: 4.h), SizedBox( - width: double.infinity, child: Wrap( alignment: WrapAlignment.start, - spacing: 8.h, + spacing: 4.h, + runSpacing: 4.h, children: [ - AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"), + AppCustomChipWidget(labelText: "${profile.age} Years Old".needTranslation), 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, + labelPadding: EdgeInsetsDirectional.only(start: -6.h, end: 8.h), + labelText: "Blood: ${appState.getAuthenticatedUser()!.bloodGroup ?? "."}", + 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, + labelPadding: EdgeInsetsDirectional.only(start: -6.h, end: 8.h), + // 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(); + // }), ], ), ), ], - ).paddingOnly(top: 16, right: 16, left: 16, bottom: 12).expanded, + ).paddingOnly(top: 16.h, right: 16.h, left: 16.h, bottom: 12.h).expanded, 1.divider, - //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) - : CustomButton( - icon: AppAssets.add_family, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - iconColor: AppColors.primaryRedColor, - text: "Switch to this medical file".needTranslation, - height: 40.h, - fontSize: 14, - 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, + height: 40.h, + fontSize: 14, + ).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) : () {}, + height: 40.h, + fontSize: 14, + ).paddingOnly(top: 12, right: 16, left: 16, bottom: 16); + } + +// //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) +// : CustomButton( +// icon: AppAssets.add_family, +// backgroundColor: AppColors.secondaryLightRedColor, +// borderColor: AppColors.secondaryLightRedColor, +// textColor: AppColors.primaryRedColor, +// iconColor: AppColors.primaryRedColor, +// text: "Switch to this medical file".needTranslation, +// height: 40.h, +// fontSize: 14, +// onPressed: () {}) +// .paddingOnly(top: 12, right: 16, left: 16, bottom: 16), +// } 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(), }; } diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 6ee0ddc..7003a31 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,14 @@ 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 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}); // TODO : Need to be Fixed showPhoneNumberPickerSheet ( From Login ADn Signup Bottom Sheet Move Here } @@ -93,6 +103,36 @@ 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 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; @@ -100,6 +140,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 +268,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/services/navigation_service.dart b/lib/services/navigation_service.dart index bfdedef..b5c5e03 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -34,9 +34,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)), ); } diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 951732b..2687944 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 a4bec4c..05ec83f 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/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index 783a83f..2cfd5ce 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -24,7 +24,8 @@ class AppCustomChipWidget extends StatelessWidget { this.deleteIconColor = AppColors.textColor, this.deleteIconHasColor = false, this.padding = EdgeInsets.zero, - this.labelPadding , + this.onChipTap, + this.labelPadding, }); final String? labelText; @@ -42,57 +43,61 @@ class AppCustomChipWidget extends StatelessWidget { final OutlinedBorder? shape; final EdgeInsets? padding; final EdgeInsetsDirectional? labelPadding; + final void Function()? onChipTap; @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, fit: BoxFit.contain) : 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, fit: BoxFit.contain) : SizedBox.shrink(), + label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), + 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, - ), + ), ); } } 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/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 29df19b..c6db474 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -104,55 +104,137 @@ class ButtonSheetContent extends StatelessWidget { } } -Future showCommonBottomSheetWithoutHeight( - BuildContext context, { - required Widget child, - required VoidCallback callBackFunc, - String title = "", - bool isCloseButtonVisible = true, - bool isFullScreen = true, - bool isDismissible = true, - Widget? titleWidget, -}) async { +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, - 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(); +// }); +// } 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), + ], + ); + } +}