Merge branch 'refs/heads/master' into dev_sikander
# Conflicts: # lib/core/app_assets.dartpull/11/head
						commit
						6473ccecb5
					
				| @ -0,0 +1,3 @@ | ||||
| <svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
|     <path fill-rule="evenodd" clip-rule="evenodd" d="M6.99984 12.7702C3.53655 12.7702 0.729004 9.96263 0.729004 6.49935C0.729004 3.03606 3.53655 0.228516 6.99984 0.228516C10.4631 0.228516 13.2707 3.03606 13.2707 6.49935C13.2707 9.96263 10.4631 12.7702 6.99984 12.7702ZM9.1623 5.16184C9.39012 4.93404 9.39013 4.5647 9.16233 4.33688C8.93453 4.10907 8.56519 4.10906 8.33737 4.33686L6.99972 5.67442L5.6623 4.33709C5.43449 4.10929 5.06514 4.1093 4.83734 4.33711C4.60955 4.56493 4.60956 4.93427 4.83737 5.16207L6.17474 6.49935L4.83737 7.83663C4.60956 8.06442 4.60955 8.43377 4.83734 8.66158C5.06514 8.8894 5.43449 8.88941 5.6623 8.66161L6.99972 7.32428L8.33737 8.66184C8.56519 8.88964 8.93453 8.88963 9.16233 8.66181C9.39013 8.434 9.39012 8.06465 9.1623 7.83686L7.82471 6.49935L9.1623 5.16184Z" fill="#ED1C2B"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 909 B | 
| @ -0,0 +1,4 @@ | ||||
| <svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
|     <path d="M8.03033 6.96967C7.73744 6.67678 7.26256 6.67678 6.96967 6.96967C6.67678 7.26256 6.67678 7.73744 6.96967 8.03033L9.93934 11L6.96967 13.9697C6.67678 14.2626 6.67678 14.7374 6.96967 15.0303C7.26256 15.3232 7.73744 15.3232 8.03033 15.0303L11 12.0607L13.9697 15.0303C14.2626 15.3232 14.7374 15.3232 15.0303 15.0303C15.3232 14.7374 15.3232 14.2626 15.0303 13.9697L12.0607 11L15.0303 8.03033C15.3232 7.73744 15.3232 7.26256 15.0303 6.96967C14.7374 6.67678 14.2626 6.67678 13.9697 6.96967L11 9.93934L8.03033 6.96967Z" fill="#2B353E"/> | ||||
|     <path fill-rule="evenodd" clip-rule="evenodd" d="M11 0.25C5.06294 0.25 0.25 5.06294 0.25 11C0.25 16.9371 5.06294 21.75 11 21.75C16.9371 21.75 21.75 16.9371 21.75 11C21.75 5.06294 16.9371 0.25 11 0.25ZM1.75 11C1.75 5.89137 5.89137 1.75 11 1.75C16.1086 1.75 20.25 5.89137 20.25 11C20.25 16.1086 16.1086 20.25 11 20.25C5.89137 20.25 1.75 16.1086 1.75 11Z" fill="#2B353E"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1018 B | 
| @ -0,0 +1,4 @@ | ||||
| <svg width="22" height="23" viewBox="0 0 22 23" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
|     <path d="M19.4932 13.7808C20.7528 9.09639 17.955 4.27023 13.2297 3.00853C9.70851 2.06832 6.11906 3.37593 3.99728 6.04158C3.84468 6.2333 3.86798 6.50772 4.04125 6.68099L5.04846 7.6882C5.18151 7.82125 5.1938 8.04137 5.18151 8.24316C5.14872 8.78189 4.6854 9.19203 4.14668 9.15923L1.69362 9.00989C1.38358 8.99102 1.10097 8.82587 0.932324 8.56502C0.76368 8.30416 0.72909 7.97867 0.839142 7.6882C2.77489 2.57901 8.3266 -0.323654 13.7339 1.12017C19.4935 2.65805 22.9235 8.55039 21.3807 14.2883C19.8381 20.025 13.9137 23.4174 8.1554 21.8799C3.87709 20.7375 0.886935 17.1944 0.261338 13.0982C0.179854 12.5647 0.546317 12.0661 1.07986 11.9846C1.61339 11.9031 2.11197 12.2696 2.19345 12.8031C2.70523 16.1541 5.15307 19.0552 8.65962 19.9915C13.3861 21.2536 18.2332 18.4666 19.4932 13.7808Z" fill="#ED1C2B"/> | ||||
|     <path d="M12 7.5C12 6.94772 11.5523 6.5 11 6.5C10.4477 6.5 10 6.94772 10 7.5V11.5C10 11.7652 10.1054 12.0196 10.2929 12.2071L12.2929 14.2071C12.6834 14.5976 13.3166 14.5976 13.7071 14.2071C14.0976 13.8166 14.0976 13.1834 13.7071 12.7929L12 11.0858V7.5Z" fill="#ED1C2B"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.2 KiB | 
| @ -0,0 +1,4 @@ | ||||
| <svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
|     <path d="M6.16613 2.14867C6.59446 2.05148 7.04084 2 7.50016 2C10.8139 2 13.5002 4.68629 13.5002 8C13.5002 9.53082 12.9272 10.928 11.9827 11.9886L11.9827 11C11.9827 10.5858 11.6469 10.25 11.2327 10.25C10.8185 10.25 10.4827 10.5858 10.4827 11L10.4827 13.625C10.4827 14.0392 10.8185 14.375 11.2327 14.375H13.8752C14.2894 14.375 14.6252 14.0392 14.6252 13.625C14.6252 13.2108 14.2894 12.875 13.8752 12.875H13.1997C14.3217 11.5644 15.0002 9.86153 15.0002 8C15.0002 3.85786 11.6423 0.5 7.50016 0.5C6.9284 0.5 6.3707 0.564117 5.8342 0.685856C5.43025 0.777516 5.1771 1.17928 5.26876 1.58323C5.36042 1.98717 5.76218 2.24033 6.16613 2.14867Z" fill="#18C273"/> | ||||
|     <path d="M1.125 1.63672C0.710786 1.63672 0.375 1.97251 0.375 2.38672C0.375 2.80093 0.710786 3.13672 1.125 3.13672H1.79048C0.674497 4.44574 0 6.14405 0 8.00006C0 12.1422 3.35786 15.5001 7.5 15.5001C8.07176 15.5001 8.62946 15.4359 9.16597 15.3142C9.56991 15.2225 9.82307 14.8208 9.73141 14.4168C9.63975 14.0129 9.23798 13.7597 8.83404 13.8514C8.4057 13.9486 7.95932 14.0001 7.5 14.0001C4.18629 14.0001 1.5 11.3138 1.5 8.00006C1.5 6.4787 2.06588 5.08936 3 4.03118V5.00006C3 5.41427 3.33579 5.75006 3.75 5.75006C4.16421 5.75006 4.5 5.41427 4.5 5.00006V2.38672C4.5 1.97251 4.16421 1.63672 3.75 1.63672L1.125 1.63672Z" fill="#18C273"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.4 KiB | 
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								| @ -0,0 +1,29 @@ | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| class NationalityCountries { | ||||
|   String? id; | ||||
|   String? name; | ||||
|   String? nameN; | ||||
| 
 | ||||
|   NationalityCountries({ | ||||
|     this.id, | ||||
|     this.name, | ||||
|     this.nameN, | ||||
|   }); | ||||
| 
 | ||||
|   factory NationalityCountries.fromRawJson(String str) => NationalityCountries.fromJson(json.decode(str)); | ||||
| 
 | ||||
|   String toRawJson() => json.encode(toJson()); | ||||
| 
 | ||||
|   factory NationalityCountries.fromJson(Map<String, dynamic> json) => NationalityCountries( | ||||
|     id: json["ID"], | ||||
|     name: json["Name"], | ||||
|     nameN: json["NameN"], | ||||
|   ); | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() => { | ||||
|     "ID": id, | ||||
|     "Name": name, | ||||
|     "NameN": nameN, | ||||
|   }; | ||||
| } | ||||
| @ -0,0 +1,77 @@ | ||||
| import 'package:dartz/dartz.dart'; | ||||
| import 'package:hmg_patient_app_new/core/api/api_client.dart'; | ||||
| import 'package:hmg_patient_app_new/core/api_consts.dart'; | ||||
| import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; | ||||
| import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; | ||||
| import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; | ||||
| import 'package:hmg_patient_app_new/services/logger_service.dart'; | ||||
| 
 | ||||
| abstract class InsuranceRepo { | ||||
|   Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails({required String patientId}); | ||||
| } | ||||
| 
 | ||||
| class InsuranceRepoImp implements InsuranceRepo { | ||||
|   final ApiClient apiClient; | ||||
|   final LoggerService loggerService; | ||||
| 
 | ||||
|   InsuranceRepoImp({required this.loggerService, required this.apiClient}); | ||||
| 
 | ||||
|   @override | ||||
|   Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails({required String patientId}) async { | ||||
|     final mapDevice = { | ||||
|       "isDentalAllowedBackend": false, | ||||
|       "VersionID": 50.0, | ||||
|       "Channel": 3, | ||||
|       "LanguageID": 2, | ||||
|       "IPAdress": "10.20.10.20", | ||||
|       "generalid": "Cs2020@2016\$2958", | ||||
|       "Latitude": 0.0, | ||||
|       "Longitude": 0.0, | ||||
|       "DeviceTypeID": 1, | ||||
|       "PatientType": 1, | ||||
|       "PatientTypeID": 1, | ||||
|       "TokenID": "@dm!n", | ||||
|       "PatientID": "3628599", | ||||
|       "PatientOutSA": "0", | ||||
|       "SessionID": "03478TYC02N80874CTYN04883475!?" | ||||
|     }; | ||||
| 
 | ||||
|     try { | ||||
|       GenericApiModel<List<PatientInsuranceDetailsResponseModel>>? apiResponse; | ||||
|       Failure? failure; | ||||
|       await apiClient.post( | ||||
|         GET_PAtIENTS_INSURANCE, | ||||
|         body: mapDevice, | ||||
|         onFailure: (error, statusCode, {messageStatus, failureType}) { | ||||
|           failure = failureType; | ||||
|         }, | ||||
|         onSuccess: (response, statusCode, {messageStatus}) { | ||||
|           try { | ||||
|             final list = response['List_PatientInsuranceCard']; | ||||
|             if (list == null || list.isEmpty) { | ||||
|               throw Exception("insurance list is empty"); | ||||
|             } | ||||
| 
 | ||||
|             final labOrders = list.map((item) => PatientInsuranceDetailsResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PatientInsuranceDetailsResponseModel>(); | ||||
| 
 | ||||
|             apiResponse = GenericApiModel<List<PatientInsuranceDetailsResponseModel>>( | ||||
|               messageStatus: messageStatus, | ||||
|               statusCode: statusCode, | ||||
|               errorMessage: null, | ||||
|               data: labOrders, | ||||
|             ); | ||||
|           } catch (e) { | ||||
|             failure = DataParsingFailure(e.toString()); | ||||
|           } | ||||
|         }, | ||||
|       ); | ||||
|       if (failure != null) return Left(failure!); | ||||
|       if (apiResponse == null) return Left(ServerFailure("Unknown error")); | ||||
|       return Right(apiResponse!); | ||||
|     } catch (e) { | ||||
|       return Left(UnknownFailure(e.toString())); | ||||
|     } | ||||
| 
 | ||||
|     throw UnimplementedError(); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,50 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; | ||||
| import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; | ||||
| import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; | ||||
| import 'package:hmg_patient_app_new/services/error_handler_service.dart'; | ||||
| 
 | ||||
| class InsuranceViewModel extends ChangeNotifier { | ||||
|   bool isInsuranceLoading = false; | ||||
|   bool isInsuranceHistoryLoading = false; | ||||
| 
 | ||||
|   InsuranceRepo insuranceRepo; | ||||
|   ErrorHandlerService errorHandlerService; | ||||
| 
 | ||||
|   List<PatientInsuranceDetailsResponseModel> patientInsuranceList = []; | ||||
| 
 | ||||
|   InsuranceViewModel({required this.insuranceRepo, required this.errorHandlerService}); | ||||
| 
 | ||||
|   initInsuranceProvider() { | ||||
|     patientInsuranceList.clear(); | ||||
|     isInsuranceLoading = true; | ||||
|     isInsuranceHistoryLoading = true; | ||||
|     getPatientInsuranceDetails(); | ||||
|     notifyListeners(); | ||||
|   } | ||||
| 
 | ||||
|   setIsInsuranceHistoryLoading(bool val) { | ||||
|     isInsuranceHistoryLoading = val; | ||||
|     notifyListeners(); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { | ||||
|     final result = await insuranceRepo.getPatientInsuranceDetails(patientId: "1231755"); | ||||
| 
 | ||||
|     result.fold( | ||||
|       (failure) async => await errorHandlerService.handleError(failure: failure), | ||||
|       (apiResponse) { | ||||
|         if (apiResponse.messageStatus == 2) { | ||||
|           // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); | ||||
|         } else if (apiResponse.messageStatus == 1) { | ||||
|           patientInsuranceList = apiResponse.data!; | ||||
|           isInsuranceLoading = false; | ||||
|           notifyListeners(); | ||||
|           if (onSuccess != null) { | ||||
|             onSuccess(apiResponse); | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,96 @@ | ||||
| class PatientInsuranceDetailsResponseModel { | ||||
|   String? setupID; | ||||
|   int? projectID; | ||||
|   bool? isActive; | ||||
|   int? patientID; | ||||
|   int? companyID; | ||||
|   int? subCategoryID; | ||||
|   dynamic companyType; | ||||
|   String? patientCardID; | ||||
|   String? cardValidTo; | ||||
|   int? patientCreditLimit; | ||||
|   String? subPolicyNo; | ||||
|   String? companyName; | ||||
|   String? companyNameN; | ||||
|   String? subCategoryDesc; | ||||
|   dynamic subCategoryDescN; | ||||
|   bool? isElectronicClaim; | ||||
|   String? subCategoryValidTo; | ||||
|   dynamic groupID; | ||||
|   String? groupName; | ||||
|   dynamic groupNameN; | ||||
|   String? insurancePolicyNo; | ||||
| 
 | ||||
|   PatientInsuranceDetailsResponseModel( | ||||
|       {this.setupID, | ||||
|       this.projectID, | ||||
|       this.isActive, | ||||
|       this.patientID, | ||||
|       this.companyID, | ||||
|       this.subCategoryID, | ||||
|       this.companyType, | ||||
|       this.patientCardID, | ||||
|       this.cardValidTo, | ||||
|       this.patientCreditLimit, | ||||
|       this.subPolicyNo, | ||||
|       this.companyName, | ||||
|       this.companyNameN, | ||||
|       this.subCategoryDesc, | ||||
|       this.subCategoryDescN, | ||||
|       this.isElectronicClaim, | ||||
|       this.subCategoryValidTo, | ||||
|       this.groupID, | ||||
|       this.groupName, | ||||
|       this.groupNameN, | ||||
|       this.insurancePolicyNo}); | ||||
| 
 | ||||
|   PatientInsuranceDetailsResponseModel.fromJson(Map<String, dynamic> json) { | ||||
|     setupID = json['SetupID']; | ||||
|     projectID = json['ProjectID']; | ||||
|     isActive = json['IsActive']; | ||||
|     patientID = json['PatientID']; | ||||
|     companyID = json['CompanyID']; | ||||
|     subCategoryID = json['SubCategoryID']; | ||||
|     companyType = json['CompanyType']; | ||||
|     patientCardID = json['PatientCardID']; | ||||
|     cardValidTo = json['CardValidTo']; | ||||
|     patientCreditLimit = json['PatientCreditLimit']; | ||||
|     subPolicyNo = json['SubPolicyNo']; | ||||
|     companyName = json['CompanyName']; | ||||
|     companyNameN = json['CompanyNameN']; | ||||
|     subCategoryDesc = json['SubCategoryDesc']; | ||||
|     subCategoryDescN = json['SubCategoryDescN']; | ||||
|     isElectronicClaim = json['IsElectronicClaim']; | ||||
|     subCategoryValidTo = json['SubCategoryValidTo']; | ||||
|     groupID = json['GroupID']; | ||||
|     groupName = json['GroupName']; | ||||
|     groupNameN = json['GroupNameN']; | ||||
|     insurancePolicyNo = json['InsurancePolicyNo']; | ||||
|   } | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final Map<String, dynamic> data = new Map<String, dynamic>(); | ||||
|     data['SetupID'] = this.setupID; | ||||
|     data['ProjectID'] = this.projectID; | ||||
|     data['IsActive'] = this.isActive; | ||||
|     data['PatientID'] = this.patientID; | ||||
|     data['CompanyID'] = this.companyID; | ||||
|     data['SubCategoryID'] = this.subCategoryID; | ||||
|     data['CompanyType'] = this.companyType; | ||||
|     data['PatientCardID'] = this.patientCardID; | ||||
|     data['CardValidTo'] = this.cardValidTo; | ||||
|     data['PatientCreditLimit'] = this.patientCreditLimit; | ||||
|     data['SubPolicyNo'] = this.subPolicyNo; | ||||
|     data['CompanyName'] = this.companyName; | ||||
|     data['CompanyNameN'] = this.companyNameN; | ||||
|     data['SubCategoryDesc'] = this.subCategoryDesc; | ||||
|     data['SubCategoryDescN'] = this.subCategoryDescN; | ||||
|     data['IsElectronicClaim'] = this.isElectronicClaim; | ||||
|     data['SubCategoryValidTo'] = this.subCategoryValidTo; | ||||
|     data['GroupID'] = this.groupID; | ||||
|     data['GroupName'] = this.groupName; | ||||
|     data['GroupNameN'] = this.groupNameN; | ||||
|     data['InsurancePolicyNo'] = this.insurancePolicyNo; | ||||
|     return data; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,316 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/gestures.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_state.dart'; | ||||
| import 'package:hmg_patient_app_new/core/dependencies.dart'; | ||||
| import 'package:hmg_patient_app_new/core/enums.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; | ||||
| import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart' show CustomButton; | ||||
| import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/input_widget.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/otp/otp.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| 
 | ||||
| class RegisterNew extends StatefulWidget { | ||||
|   @override | ||||
|   _RegisterNew createState() => _RegisterNew(); | ||||
| } | ||||
| 
 | ||||
| class _RegisterNew extends State<RegisterNew> { | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void dispose() { | ||||
|     super.dispose(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     AppState appState = getIt.get<AppState>(); | ||||
|     AuthenticationViewModel authVm = context.read<AuthenticationViewModel>(); | ||||
| 
 | ||||
|     return Scaffold( | ||||
|         backgroundColor: AppColors.bgScaffoldColor, | ||||
|         appBar: CustomAppBar( | ||||
|           onBackPressed: () { | ||||
|             Navigator.of(context).pop(); | ||||
|           }, | ||||
|           onLanguageChanged: (String value) { | ||||
|             // context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US')); | ||||
|           }, | ||||
|         ), | ||||
|         body: GestureDetector( | ||||
|           onTap: () { | ||||
|             FocusScope.of(context).unfocus(); | ||||
|           }, | ||||
|           child: ScrollConfiguration( | ||||
|             behavior: ScrollConfiguration.of(context).copyWith(overscroll: false, physics: const ClampingScrollPhysics()), | ||||
|             child: NotificationListener<OverscrollIndicatorNotification>( | ||||
|               onNotification: (notification) { | ||||
|                 notification.disallowIndicator(); | ||||
|                 return true; | ||||
|               }, | ||||
|               child: SingleChildScrollView( | ||||
|                 physics: ClampingScrollPhysics(), | ||||
|                 padding: EdgeInsets.symmetric(horizontal: 24.h), | ||||
|                 child: Column( | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     Utils.showLottie(context: context, assetPath: 'assets/animations/lottie/register.json', width: 200.h, height: 200.h, fit: BoxFit.cover, repeat: true), | ||||
|                     SizedBox(height: 16.h), | ||||
|                     LocaleKeys.prepareToElevate.tr().toText32(isBold: true), | ||||
|                     SizedBox(height: 24.h), | ||||
|                     Directionality( | ||||
|                       textDirection: Directionality.of(context), | ||||
|                       child: Container( | ||||
|                         decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)), | ||||
|                         padding: EdgeInsets.symmetric(horizontal: 16.h), | ||||
|                         child: Column( | ||||
|                           children: [ | ||||
|                             CustomCountryDropdown( | ||||
|                               countryList: CountryEnum.values, | ||||
|                               onCountryChange: authVm.onCountryChange, | ||||
|                               isRtl: Directionality.of(context) == TextDirection.LTR, | ||||
|                             ).withVerticalPadding(8.h), | ||||
|                             Divider(height: 1.h), | ||||
|                             TextInputWidget( | ||||
|                               labelText: LocaleKeys.nationalIdNumber.tr(), | ||||
|                               hintText: "xxxxxxxxx", | ||||
|                               controller: authVm.nationalIdController, | ||||
|                               isEnable: true, | ||||
|                               prefix: null, | ||||
|                               isAllowRadius: true, | ||||
|                               isBorderAllowed: false, | ||||
|                               isAllowLeadingIcon: true, | ||||
|                               autoFocus: true, | ||||
|                               padding: EdgeInsets.symmetric(vertical: 8.h), | ||||
|                               leadingIcon: AppAssets.student_card, | ||||
|                               // onChange: (value) { | ||||
|                               //   print(value); | ||||
|                               // } | ||||
|                             ).withVerticalPadding(8), | ||||
|                             Divider(height: 1), | ||||
|                             TextInputWidget( | ||||
|                               labelText: LocaleKeys.dob.tr(), | ||||
|                               hintText: "11 July, 1994", | ||||
|                               controller: authVm.dobController, | ||||
|                               isEnable: true, | ||||
|                               prefix: null, | ||||
|                               isAllowRadius: true, | ||||
|                               isBorderAllowed: false, | ||||
|                               isAllowLeadingIcon: true, | ||||
|                               padding: EdgeInsets.symmetric(vertical: 8.h), | ||||
|                               leadingIcon: AppAssets.birthday_cake, | ||||
|                               selectionType: SelectionTypeEnum.calendar, | ||||
|                             ).withVerticalPadding(8), | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                     SizedBox(height: 25.h), | ||||
|                     GestureDetector( | ||||
|                       onTap: authVm.onTermAccepted, | ||||
|                       child: Row( | ||||
|                         children: [ | ||||
|                           Selector<AuthenticationViewModel, bool>( | ||||
|                             selector: (_, viewModel) => viewModel.isTermsAccepted, | ||||
|                             shouldRebuild: (previous, next) => previous != next, | ||||
|                             builder: (context, isTermsAccepted, child) { | ||||
|                               return AnimatedContainer( | ||||
|                                 duration: const Duration(milliseconds: 200), | ||||
|                                 height: 24.h, | ||||
|                                 width: 24.h, | ||||
|                                 decoration: BoxDecoration( | ||||
|                                   color: isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent, | ||||
|                                   borderRadius: BorderRadius.circular(6), | ||||
|                                   border: Border.all(color: isTermsAccepted ? AppColors.primaryRedBorderColor : AppColors.greyColor, width: 2.h), | ||||
|                                 ), | ||||
|                                 child: isTermsAccepted ? Icon(Icons.check, size: 16.fSize, color: Colors.white) : null, | ||||
|                               ); | ||||
|                             }, | ||||
|                           ), | ||||
|                           SizedBox(width: 12.h), | ||||
|                           Expanded( | ||||
|                             child: Text( | ||||
|                               LocaleKeys.iAcceptTermsConditions.tr(), | ||||
|                               style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), | ||||
|                             ), | ||||
|                           ), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|                     SizedBox(height: 25.h), | ||||
|                     CustomButton( | ||||
|                       text: "Register", | ||||
|                       icon: AppAssets.note_edit, | ||||
|                       onPressed: () { | ||||
|                         showRegisterModel(context: context, authVM: authVm); | ||||
|                       }, | ||||
|                     ), | ||||
|                     SizedBox(height: 14), | ||||
|                     Center( | ||||
|                       child: RichText( | ||||
|                         textAlign: TextAlign.center, | ||||
|                         text: TextSpan( | ||||
|                           style: context.dynamicTextStyle( | ||||
|                             color: Colors.black, | ||||
|                             fontSize: 16.fSize, | ||||
|                             height: 26 / 16, | ||||
|                             fontWeight: FontWeight.w500, | ||||
|                           ), | ||||
|                           children: <TextSpan>[ | ||||
|                             TextSpan(text: LocaleKeys.alreadyHaveAccount.tr(), style: context.dynamicTextStyle()), | ||||
|                             TextSpan(text: "  "), | ||||
|                             TextSpan( | ||||
|                               text: LocaleKeys.loginNow.tr(), | ||||
|                               style: context.dynamicTextStyle( | ||||
|                                 color: AppColors.primaryRedColor, | ||||
|                                 fontSize: 16.fSize, | ||||
|                                 height: 26 / 16, | ||||
|                                 fontWeight: FontWeight.w500, | ||||
|                               ), | ||||
|                               recognizer: TapGestureRecognizer() | ||||
|                                 ..onTap = () { | ||||
|                                   Navigator.of(context).pop(); | ||||
|                                 }, | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                     SizedBox(height: 30), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         )); | ||||
|   } | ||||
| 
 | ||||
|   void showRegisterModel({required BuildContext context, required AuthenticationViewModel authVM}) { | ||||
|     showModalBottomSheet( | ||||
|       context: context, | ||||
|       isScrollControlled: true, | ||||
|       isDismissible: false, | ||||
|       useSafeArea: true, | ||||
|       backgroundColor: Colors.transparent, | ||||
|       builder: (bottomSheetContext) => Padding( | ||||
|         padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), | ||||
|         child: SingleChildScrollView( | ||||
|           child: GenericBottomSheet( | ||||
|             countryCode: authVM.selectedCountrySignup.countryCode, | ||||
|             initialPhoneNumber: "", | ||||
|             textController: authVM.phoneNumberController, | ||||
|             isEnableCountryDropdown: true, | ||||
|             onCountryChange: authVM.onCountryChange, | ||||
|             onChange: authVM.onPhoneNumberChange, | ||||
|             buttons: [ | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.only(bottom: 10), | ||||
|                 child: CustomButton( | ||||
|                   text: LocaleKeys.sendOTPSMS.tr(), | ||||
|                   onPressed: () { | ||||
|                     Navigator.of(context).push(MaterialPageRoute( | ||||
|                         builder: (BuildContext context) => OTPVerificationPage( | ||||
|                               phoneNumber: '12234567', | ||||
|                             ))); | ||||
| 
 | ||||
|                     // if (mobileNo.isEmpty) { | ||||
|                     //   context.showBottomSheet( | ||||
|                     //     child: ExceptionBottomSheet( | ||||
|                     //       message: TranslationBase.of(context).pleaseEnterMobile, | ||||
|                     //       showCancel: false, | ||||
|                     //       onOkPressed: () { | ||||
|                     //         Navigator.of(context).pop(); | ||||
|                     //       }, | ||||
|                     //     ), | ||||
|                     //   ); | ||||
|                     // } else if (!Utils.validateMobileNumber(mobileNo)) { | ||||
|                     //   context.showBottomSheet( | ||||
|                     //     child: ExceptionBottomSheet( | ||||
|                     //       message: TranslationBase.of(context).pleaseEnterValidMobile, | ||||
|                     //       showCancel: false, | ||||
|                     //       onOkPressed: () { | ||||
|                     //         Navigator.of(context).pop(); | ||||
|                     //       }, | ||||
|                     //     ), | ||||
|                     //   ); | ||||
|                     // } else { | ||||
|                     //   registerUser(1); | ||||
|                     // } | ||||
|                     Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => OTPVerificationPage(phoneNumber: '12234567'))); | ||||
|                   }, | ||||
|                   backgroundColor: AppColors.primaryRedColor, | ||||
|                   borderColor: AppColors.primaryRedBorderColor, | ||||
|                   textColor: AppColors.whiteColor, | ||||
|                   icon: AppAssets.message, | ||||
|                 ), | ||||
|               ), | ||||
|               Row( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                 mainAxisAlignment: MainAxisAlignment.center, | ||||
|                 children: [ | ||||
|                   Padding( | ||||
|                     padding: EdgeInsets.symmetric(horizontal: 8.h), | ||||
|                     child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor), | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|               Padding( | ||||
|                 padding: EdgeInsets.only(bottom: 10.h, top: 10.h), | ||||
|                 child: CustomButton( | ||||
|                   text: LocaleKeys.sendOTPWHATSAPP.tr(), | ||||
|                   onPressed: () { | ||||
|                     // if (mobileNo.isEmpty) { | ||||
|                     //   context.showBottomSheet( | ||||
|                     //     child: ExceptionBottomSheet( | ||||
|                     //       message: TranslationBase.of(context).pleaseEnterMobile, | ||||
|                     //       showCancel: false, | ||||
|                     //       onOkPressed: () { | ||||
|                     //         Navigator.of(context).pop(); | ||||
|                     //       }, | ||||
|                     //     ), | ||||
|                     //   ); | ||||
|                     // } else if (!Utils.validateMobileNumber(mobileNo)) { | ||||
|                     //   context.showBottomSheet( | ||||
|                     //     child: ExceptionBottomSheet( | ||||
|                     //       message: TranslationBase.of(context).pleaseEnterValidMobile, | ||||
|                     //       showCancel: false, | ||||
|                     //       onOkPressed: () { | ||||
|                     //         Navigator.of(context).pop(); | ||||
|                     //       }, | ||||
|                     //     ), | ||||
|                     //   ); | ||||
|                     // } else { | ||||
|                     //   registerUser(4); | ||||
|                     // } | ||||
|                     // int? val = Utils.onOtpBtnPressed(OTPType.whatsapp, mobileNo, context); | ||||
|                     // registerUser(val); | ||||
|                   }, | ||||
|                   backgroundColor: AppColors.whiteColor, | ||||
|                   borderColor: AppColors.borderOnlyColor, | ||||
|                   textColor: AppColors.textColor, | ||||
|                   icon: AppAssets.whatsapp, | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,353 @@ | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:get_it/get_it.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_state.dart'; | ||||
| import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; | ||||
| 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/extensions/context_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; | ||||
| import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/input_widget.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| 
 | ||||
| class RegisterNewStep2 extends StatefulWidget { | ||||
|   var nHICData; | ||||
|   var payload; | ||||
| 
 | ||||
|   RegisterNewStep2(this.nHICData, this.payload, {Key? key}) : super(key: key); | ||||
| 
 | ||||
|   @override | ||||
|   _RegisterNew createState() => _RegisterNew(); | ||||
| } | ||||
| 
 | ||||
| class _RegisterNew extends State<RegisterNewStep2> { | ||||
|   bool isFromDubai = true; | ||||
|   AuthenticationViewModel? authVM; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     authVM = context.read<AuthenticationViewModel>(); | ||||
|     authVM!.loadCountriesData(context: context); | ||||
|     // isFromDubai = widget.payload.zipCode!.contains("971") || widget.payload.zipCode!.contains("+971"); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void dispose() { | ||||
|     super.dispose(); | ||||
|     authVM!.clearDefaults(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     AppState appState = getIt.get<AppState>(); | ||||
|     return Scaffold( | ||||
|       appBar: CustomAppBar( | ||||
|         onBackPressed: () { | ||||
|           Navigator.of(context).pop(); | ||||
|           authVM!.clearDefaults(); | ||||
|         }, | ||||
|         onLanguageChanged: (lang) {}, | ||||
|         hideLogoAndLang: true, | ||||
|       ), | ||||
|       body: SingleChildScrollView( | ||||
|         reverse: false, | ||||
|         padding: EdgeInsets.only(left: 24.h, right: 24.h, top: 24.h), | ||||
|         child: Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: <Widget>[ | ||||
|             Directionality( | ||||
|               textDirection: Directionality.of(context), | ||||
|               child: Container( | ||||
|                 decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)), | ||||
|                 padding: EdgeInsets.only(left: 16.h, right: 16.h), | ||||
|                 child: Column( | ||||
|                   children: [ | ||||
|                     TextInputWidget( | ||||
|                             labelText: isFromDubai ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), | ||||
|                             hintText: isFromDubai ? "name" ?? "" : (widget.nHICData!.firstNameEn!.toUpperCase() + " " + widget.nHICData!.lastNameEn!.toUpperCase()), | ||||
|                             controller: null, | ||||
|                             isEnable: true, | ||||
|                             prefix: null, | ||||
|                             isAllowRadius: false, | ||||
|                             isBorderAllowed: false, | ||||
|                             keyboardType: TextInputType.text, | ||||
|                             isAllowLeadingIcon: true, | ||||
|                             isReadOnly: isFromDubai ? false : true, | ||||
|                             leadingIcon: AppAssets.user_circle) | ||||
|                         .paddingSymmetrical(0.h, 16.h), | ||||
|                     Divider(height: 1, color: AppColors.greyColor), | ||||
|                     TextInputWidget( | ||||
|                             labelText: LocaleKeys.nationalIdNumber.tr(), | ||||
|                             hintText: isFromDubai ? "widget.payload.nationalID!" : (widget.nHICData!.idNumber ?? ""), | ||||
|                             controller: null, | ||||
|                             isEnable: true, | ||||
|                             prefix: null, | ||||
|                             isAllowRadius: false, | ||||
|                             isBorderAllowed: false, | ||||
|                             isAllowLeadingIcon: true, | ||||
|                             isReadOnly: true, | ||||
|                             leadingIcon: AppAssets.student_card) | ||||
|                         .paddingSymmetrical(0.h, 16.h), | ||||
|                     Divider(height: 1, color: AppColors.greyColor), | ||||
|                     isFromDubai | ||||
|                         ? Selector<AuthenticationViewModel, GenderTypeEnum?>( | ||||
|                             selector: (_, authViewModel) => authViewModel.genderType, | ||||
|                             shouldRebuild: (previous, next) => previous != next, | ||||
|                             builder: (context, genderType, child) { | ||||
|                               final authVM = context.read<AuthenticationViewModel>(); | ||||
|                               return DropdownWidget( | ||||
|                                 labelText: LocaleKeys.gender.tr(), | ||||
|                                 hintText: LocaleKeys.malE.tr(), | ||||
|                                 isEnable: true, | ||||
|                                 dropdownItems: GenderTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), | ||||
|                                 selectedValue: genderType != null ? (appState!.isArabic() ? genderType!.typeAr : genderType!.type) : "", | ||||
|                                 onChange: authVM.onGenderChange, | ||||
|                                 isBorderAllowed: false, | ||||
|                                 hasSelectionCustomIcon: true, | ||||
|                                 isAllowRadius: false, | ||||
|                                 padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), | ||||
|                                 selectionCustomIcon: AppAssets.arrow_down, | ||||
|                                 leadingIcon: AppAssets.user_full, | ||||
|                               ).withVerticalPadding(8); | ||||
|                             }) | ||||
|                         : TextInputWidget( | ||||
|                                 labelText: LocaleKeys.gender.tr(), | ||||
|                                 hintText: (widget.nHICData!.gender ?? ""), | ||||
|                                 controller: null, | ||||
|                                 isEnable: true, | ||||
|                                 prefix: null, | ||||
|                                 isAllowRadius: false, | ||||
|                                 isBorderAllowed: false, | ||||
|                                 isAllowLeadingIcon: true, | ||||
|                                 isReadOnly: isFromDubai ? false : true, | ||||
|                                 leadingIcon: AppAssets.user_full, | ||||
|                                 onChange: (value) {}) | ||||
|                             .paddingSymmetrical(0.h, 16.h), | ||||
|                     Divider(height: 1, color: AppColors.greyColor), | ||||
|                     isFromDubai | ||||
|                         ? Selector<AuthenticationViewModel, MaritalStatusTypeEnum?>( | ||||
|                             selector: (_, authViewModel) => authViewModel.maritalStatus, | ||||
|                             shouldRebuild: (previous, next) => previous != next, | ||||
|                             builder: (context, maritalStatus, child) { | ||||
|                               final authVM = context.read<AuthenticationViewModel>(); // For onChange | ||||
|                               return DropdownWidget( | ||||
|                                 labelText: LocaleKeys.maritalStatus.tr(), | ||||
|                                 hintText: LocaleKeys.married.tr(), | ||||
|                                 isEnable: true, | ||||
|                                 dropdownItems: MaritalStatusTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), | ||||
|                                 selectedValue: maritalStatus != null ? (appState!.isArabic() ? maritalStatus.typeAr : maritalStatus.type) : "", | ||||
|                                 onChange: authVM.onMaritalStatusChange, | ||||
|                                 isBorderAllowed: false, | ||||
|                                 hasSelectionCustomIcon: true, | ||||
|                                 isAllowRadius: false, | ||||
|                                 padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), | ||||
|                                 selectionCustomIcon: AppAssets.arrow_down, | ||||
|                                 leadingIcon: AppAssets.smart_phone, | ||||
|                               ).withVerticalPadding(8); | ||||
|                             }, | ||||
|                           ) | ||||
|                         : TextInputWidget( | ||||
|                                 labelText: LocaleKeys.maritalStatus.tr(), | ||||
|                                 hintText: appState!.isArabic() | ||||
|                                     ? (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.typeAr) | ||||
|                                     : (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.type), | ||||
|                                 isEnable: true, | ||||
|                                 prefix: null, | ||||
|                                 isAllowRadius: false, | ||||
|                                 isBorderAllowed: false, | ||||
|                                 isAllowLeadingIcon: true, | ||||
|                                 isReadOnly: true, | ||||
|                                 leadingIcon: AppAssets.smart_phone, | ||||
|                                 onChange: (value) {}) | ||||
|                             .paddingSymmetrical(0.h, 16.h), | ||||
|                     Divider(height: 1, color: AppColors.greyColor), | ||||
|                     isFromDubai | ||||
|                         ? Selector<AuthenticationViewModel, ({List<NationalityCountries>? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( | ||||
|                             selector: (context, authViewModel) { | ||||
|                               final appState = getIt.get<AppState>(); | ||||
|                               return (countriesList: authViewModel.countriesList, selectedCountry: authViewModel.pickedCountryByUAEUser, isArabic: appState.isArabic()); | ||||
|                             }, | ||||
|                             shouldRebuild: (previous, next) => previous.countriesList != next.countriesList || previous.selectedCountry != next.selectedCountry || previous.isArabic != next.isArabic, | ||||
|                             builder: (context, data, child) { | ||||
|                               final authVM = context.read<AuthenticationViewModel>(); | ||||
|                               return DropdownWidget( | ||||
|                                 labelText: LocaleKeys.country.tr(), | ||||
|                                 hintText: LocaleKeys.uae.tr(), | ||||
|                                 isEnable: true, | ||||
|                                 dropdownItems: (data.countriesList ?? []).map((e) => data.isArabic ? e.nameN ?? "" : e.name ?? "").toList(), | ||||
|                                 selectedValue: data.selectedCountry != null | ||||
|                                     ? data.isArabic | ||||
|                                         ? data.selectedCountry!.nameN ?? "" | ||||
|                                         : data.selectedCountry!.name ?? "" | ||||
|                                     : "", | ||||
|                                 onChange: authVM.onUAEUserCountrySelection, | ||||
|                                 isBorderAllowed: false, | ||||
|                                 hasSelectionCustomIcon: true, | ||||
|                                 isAllowRadius: false, | ||||
|                                 padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), | ||||
|                                 selectionCustomIcon: AppAssets.arrow_down, | ||||
|                                 leadingIcon: AppAssets.globe, | ||||
|                               ).withVerticalPadding(8); | ||||
|                             }, | ||||
|                           ) | ||||
|                         : TextInputWidget( | ||||
|                                 labelText: LocaleKeys.nationality.tr(), | ||||
|                                 hintText: appState.isArabic() | ||||
|                                     ? (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") | ||||
|                                     : (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), | ||||
|                                 isEnable: true, | ||||
|                                 prefix: null, | ||||
|                                 isAllowRadius: false, | ||||
|                                 isBorderAllowed: false, | ||||
|                                 isAllowLeadingIcon: true, | ||||
|                                 isReadOnly: true, | ||||
|                                 leadingIcon: AppAssets.globe, | ||||
|                                 onChange: (value) {}) | ||||
|                             .paddingSymmetrical(0.h, 16.h), | ||||
|                     Divider( | ||||
|                       height: 1, | ||||
|                       color: AppColors.greyColor, | ||||
|                     ), | ||||
|                     TextInputWidget( | ||||
|                             labelText: LocaleKeys.mobileNumber.tr(), | ||||
|                             hintText: ("widget.payload.mobileNo" ?? ""), | ||||
|                             controller: authVM!.phoneNumberController, | ||||
|                             isEnable: true, | ||||
|                             prefix: null, | ||||
|                             isAllowRadius: false, | ||||
|                             isBorderAllowed: false, | ||||
|                             isAllowLeadingIcon: true, | ||||
|                             isReadOnly: true, | ||||
|                             leadingIcon: AppAssets.call, | ||||
|                             onChange: (value) {}) | ||||
|                         .paddingSymmetrical(0.h, 16.h), | ||||
|                     Divider( | ||||
|                       height: 1, | ||||
|                       color: AppColors.greyColor, | ||||
|                     ), | ||||
|                     TextInputWidget( | ||||
|                       labelText: LocaleKeys.dob.tr(), | ||||
|                       hintText: isFromDubai ? "widget.payload.dob!" : (widget.nHICData!.dateOfBirth ?? ""), | ||||
|                       controller: authVM!.dobController, | ||||
|                       isEnable: true, | ||||
|                       prefix: null, | ||||
|                       isBorderAllowed: false, | ||||
|                       isAllowLeadingIcon: true, | ||||
|                       isReadOnly: true, | ||||
|                       leadingIcon: AppAssets.birthday_cake, | ||||
|                       selectionType: SelectionTypeEnum.calendar, | ||||
|                     ).paddingSymmetrical(0.h, 16.h), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|             SizedBox(height: 50.h), | ||||
|             Row( | ||||
|               children: [ | ||||
|                 Expanded( | ||||
|                   child: CustomButton( | ||||
|                     text: LocaleKeys.cancel.tr(), | ||||
|                     icon: AppAssets.cancel, | ||||
|                     onPressed: () { | ||||
|                       Navigator.of(context).pop(); | ||||
|                       authVM!.clearDefaults(); | ||||
|                     }, | ||||
|                     backgroundColor: AppColors.secondaryLightRedColor, | ||||
|                     borderColor: AppColors.secondaryLightRedColor, | ||||
|                     textColor: AppColors.primaryRedColor, | ||||
|                     iconColor: AppColors.primaryRedColor, | ||||
|                   ), | ||||
|                 ), | ||||
|                 SizedBox( | ||||
|                   width: 16, | ||||
|                 ), | ||||
|                 Expanded( | ||||
|                   child: CustomButton( | ||||
|                     backgroundColor: AppColors.lightGreenColor, | ||||
|                     borderColor: AppColors.lightGreenColor, | ||||
|                     textColor: AppColors.textGreenColor, | ||||
|                     text: LocaleKeys.confirm.tr(), | ||||
|                     icon: AppAssets.confirm, | ||||
|                     iconColor: AppColors.textGreenColor, | ||||
|                     onPressed: () { | ||||
|                       // if (isFromDubai) { | ||||
|                       //   if (name == null) { | ||||
|                       //     AppToast.showErrorToast(message: LocaleKeys.enterFullName); | ||||
|                       //     return; | ||||
|                       //   } | ||||
|                       //   if (!name!.contains(" ")) if (selectedGenderType == null) { | ||||
|                       //     AppToast.showErrorToast(message: TranslationBase.of(context).enterFullName); | ||||
|                       //     return; | ||||
|                       //   } | ||||
|                       //   if (selectedMaritalStatusType == null) { | ||||
|                       //     AppToast.showErrorToast(message: TranslationBase.of(context).chooseMaritalStatus); | ||||
|                       //     return; | ||||
|                       //   } | ||||
|                       //   if (selectedCountry == null) { | ||||
|                       //     AppToast.showErrorToast(message: TranslationBase.of(context).chooseCountry); | ||||
|                       //     return; | ||||
|                       //   } | ||||
|                       // } | ||||
| 
 | ||||
|                       showModel(context: context); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ) | ||||
|               ], | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   void showModel({required BuildContext context}) { | ||||
|     showModalBottomSheet( | ||||
|       context: context, | ||||
|       isScrollControlled: true, | ||||
|       isDismissible: false, | ||||
|       backgroundColor: Colors.transparent, | ||||
|       builder: (bottomSheetContext) => Padding( | ||||
|         padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), | ||||
|         child: SingleChildScrollView( | ||||
|           child: GenericBottomSheet( | ||||
|             textController: TextEditingController(), | ||||
|             isForEmail: true, | ||||
|             buttons: [ | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.only(bottom: 10), | ||||
|                 child: CustomButton( | ||||
|                     text: LocaleKeys.submit, | ||||
|                     onPressed: () { | ||||
|                       // if (emailAddress.text.isEmpty) { | ||||
|                       //   Utils.showErrorToast(TranslationBase.of(context).enterEmailAddress); | ||||
|                       //   return; | ||||
|                       // } else { | ||||
|                       //   Navigator.of(context).pop(); | ||||
|                       //   registerNow(); | ||||
|                       // } | ||||
|                     }, | ||||
|                     backgroundColor: AppColors.bgGreenColor, | ||||
|                     borderColor: AppColors.bgGreenColor, | ||||
|                     textColor: AppColors.whiteColor), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,289 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/enums.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; | ||||
| import 'package:hmg_patient_app_new/presentation/authentication/login.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/bottomsheet/generic_bottom_sheet.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; | ||||
| 
 | ||||
| class SavedLogin extends StatefulWidget { | ||||
|   // final SelectDeviceIMEIRES savedLoginData; | ||||
| 
 | ||||
|   // const SavedLogin(this.savedLoginData, {Key? key}) : super(key: key); | ||||
|   const SavedLogin(); | ||||
| 
 | ||||
|   @override | ||||
|   _SavedLogin createState() => _SavedLogin(); | ||||
| } | ||||
| 
 | ||||
| class _SavedLogin extends State<SavedLogin> { | ||||
|   LoginTypeEnum loginType = LoginTypeEnum.sms; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Scaffold( | ||||
|       appBar: CustomAppBar( | ||||
|         onBackPressed: () {}, | ||||
|         onLanguageChanged: (lang) {}, | ||||
|       ), | ||||
|       body: SafeArea( | ||||
|         child: Padding( | ||||
|           padding: EdgeInsets.symmetric(horizontal: 24.h), | ||||
|           child: Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.center, | ||||
|             children: [ | ||||
|               const Spacer(flex: 2), | ||||
|               // Welcome back text | ||||
|               LocaleKeys.welcomeBack.tr().toText16(color: AppColors.inputLabelTextColor), | ||||
|               SizedBox(height: 16.h), | ||||
|               ("widget.savedLoginData.name!.toLowerCase().capitalizeFirstofEach").toText26(isBold: true, height: 26 / 36, color: AppColors.textColor), | ||||
|               SizedBox(height: 24.h), | ||||
|               Container( | ||||
|                 padding: EdgeInsets.all(16.h), | ||||
|                 decoration: BoxDecoration( | ||||
|                   color: AppColors.whiteColor, | ||||
|                   border: Border.all(color: AppColors.whiteColor), | ||||
|                   borderRadius: BorderRadius.circular(24.h), | ||||
|                   boxShadow: [ | ||||
|                     BoxShadow(color: Color(0x0D000000), blurRadius: 16, offset: Offset(0, 0), spreadRadius: 5), | ||||
|                   ], | ||||
|                 ), | ||||
|                 child: Column( | ||||
|                   children: [ | ||||
|                     // Last login info | ||||
|                     ('LocaleKeys.lastloginBy.tr()' + ' {getType(widget.savedLoginData.logInType!, context)}').toText14(isBold: true, color: AppColors.greyTextColor), | ||||
|                     ('widget.savedLoginData.createdOn != null ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(widget.savedLoginData.createdOn!), "d MMMM, y at HH:mm") : --') | ||||
|                         .toText16(isBold: true, color: AppColors.textColor), | ||||
| 
 | ||||
|                     Container(margin: EdgeInsets.all(16.h), child: Utils.buildSvgWithAssets(icon: getTypeIcons(loginType.toInt), iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), | ||||
|                     // Face ID login button | ||||
|                     Container( | ||||
|                       height: 45, | ||||
|                       child: CustomButton( | ||||
|                         text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", | ||||
|                         onPressed: () { | ||||
|                           if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { | ||||
|                             // loginWithFingerPrintFace(loginType.toInt, widget.savedLoginData.iMEI!); | ||||
|                           } else { | ||||
|                             int? val = loginType.toInt; | ||||
|                             //checkUserAuthentication(val); | ||||
|                           } | ||||
|                         }, | ||||
|                         backgroundColor: Color(0xffED1C2B), | ||||
|                         borderColor: Color(0xffFEE9EA), | ||||
|                         textColor: Colors.white, | ||||
|                         fontSize: 12, | ||||
|                         fontWeight: FontWeight.w500, | ||||
|                         borderRadius: 12, | ||||
|                         padding: EdgeInsets.fromLTRB(0, 10, 0, 10), | ||||
|                         icon: AppAssets.apple_finder, | ||||
|                       ), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|               const SizedBox(height: 24), | ||||
|               Padding( | ||||
|                 padding: EdgeInsets.symmetric(horizontal: 16.0), | ||||
|                 child: Text( | ||||
|                   LocaleKeys.oR.tr(), | ||||
|                   style: context.dynamicTextStyle(fontSize: 16, fontWeight: FontWeight.w500), | ||||
|                 ), | ||||
|               ), | ||||
|               const SizedBox(height: 24), | ||||
|               // OTP login button | ||||
|               loginType != null && loginType.toInt != 1 | ||||
|                   ? Column( | ||||
|                       children: [ | ||||
|                         loginType.toInt != 1 | ||||
|                             ? CustomButton( | ||||
|                                 text: LocaleKeys.loginByOTP.tr(), | ||||
|                                 onPressed: () { | ||||
|                                   showModalBottomSheet( | ||||
|                                     context: context, | ||||
|                                     isScrollControlled: true, | ||||
|                                     isDismissible: false, | ||||
|                                     useSafeArea: true, | ||||
|                                     backgroundColor: Colors.transparent, | ||||
|                                     enableDrag: false, | ||||
|                                     // Prevent dragging to avoid focus conflicts | ||||
|                                     builder: (bottomSheetContext) => StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { | ||||
|                                       return Padding( | ||||
|                                         padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), | ||||
|                                         child: SingleChildScrollView( | ||||
|                                           child: GenericBottomSheet( | ||||
|                                             countryCode: "966", | ||||
|                                             initialPhoneNumber: "", | ||||
|                                             textController: TextEditingController(), | ||||
|                                             isFromSavedLogin: true, | ||||
|                                             isEnableCountryDropdown: true, | ||||
|                                             onCountryChange: (value) {}, | ||||
|                                             onChange: (String? value) {}, | ||||
|                                             buttons: [ | ||||
|                                               Padding( | ||||
|                                                 padding: EdgeInsets.only(bottom: 10.h), | ||||
|                                                 child: CustomButton( | ||||
|                                                   text: LocaleKeys.sendOTPSMS.tr(), | ||||
|                                                   onPressed: () { | ||||
|                                                     Navigator.of(context).pop(); | ||||
|                                                     loginType = LoginTypeEnum.sms; | ||||
|                                                     int? val = loginType.toInt; | ||||
|                                                     // checkUserAuthentication(val); | ||||
|                                                   }, | ||||
|                                                   backgroundColor: AppColors.primaryRedColor, | ||||
|                                                   borderColor: AppColors.primaryRedBorderColor, | ||||
|                                                   textColor: AppColors.whiteColor, | ||||
|                                                   icon: AppAssets.message, | ||||
|                                                 ), | ||||
|                                               ), | ||||
|                                               Row( | ||||
|                                                 crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                                                 mainAxisAlignment: MainAxisAlignment.center, | ||||
|                                                 children: [ | ||||
|                                                   Padding(padding: EdgeInsets.symmetric(horizontal: 8.h), child: (LocaleKeys.oR.tr()).toText16(color: AppColors.textColor)), | ||||
|                                                 ], | ||||
|                                               ), | ||||
|                                               Padding( | ||||
|                                                 padding: const EdgeInsets.only(bottom: 10, top: 10), | ||||
|                                                 child: CustomButton( | ||||
|                                                   text: LocaleKeys.sendOTPWHATSAPP.tr(), | ||||
|                                                   onPressed: () { | ||||
|                                                     Navigator.of(context).pop(); | ||||
|                                                     loginType = LoginTypeEnum.whatsapp; | ||||
|                                                     int? val = loginType.toInt; | ||||
|                                                     // checkUserAuthentication(val); | ||||
|                                                   }, | ||||
|                                                   backgroundColor: AppColors.transparent, | ||||
|                                                   borderColor: AppColors.textColor, | ||||
|                                                   textColor: AppColors.textColor, | ||||
|                                                   icon: AppAssets.whatsapp, | ||||
|                                                 ), | ||||
|                                               ), | ||||
|                                             ], | ||||
|                                           ), | ||||
|                                         ), | ||||
|                                       ); | ||||
|                                     }), | ||||
|                                   ); | ||||
|                                 }, | ||||
|                                 backgroundColor: AppColors.whiteColor, | ||||
|                                 borderColor: AppColors.borderOnlyColor, | ||||
|                                 textColor: AppColors.textColor, | ||||
|                                 borderWidth: 2, | ||||
|                                 padding: EdgeInsets.fromLTRB(0, 14, 0, 14), | ||||
|                                 icon: AppAssets.password_validation, | ||||
|                               ) | ||||
|                             : Container(), | ||||
|                         SizedBox( | ||||
|                           height: 20, | ||||
|                         ), | ||||
|                       ], | ||||
|                     ) | ||||
|                   : CustomButton( | ||||
|                       text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}", | ||||
|                       onPressed: () { | ||||
|                         if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { | ||||
|                           // loginWithFingerPrintFace(loginType.toInt, "iMEI"); | ||||
|                         } else { | ||||
|                           loginType = LoginTypeEnum.whatsapp; | ||||
|                           int? val = loginType.toInt; | ||||
|                           // checkUserAuthentication(val); | ||||
|                         } | ||||
|                       }, | ||||
|                       backgroundColor: AppColors.whiteColor, | ||||
|                       borderColor: Color(0xFF2E3039), | ||||
|                       textColor: Color(0xFF2E3039), | ||||
|                       borderWidth: 2, | ||||
|                       padding: EdgeInsets.fromLTRB(0, 14, 0, 14), | ||||
|                       icon: AppAssets.whatsapp, | ||||
|                     ), | ||||
|               const Spacer(flex: 2), | ||||
|               // OR divider | ||||
| 
 | ||||
|               const SizedBox(height: 24), | ||||
|               // Guest and Switch account | ||||
|               Row( | ||||
|                 mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                 children: [ | ||||
|                   Expanded( | ||||
|                     child: Container( | ||||
|                       height: 56, | ||||
|                       child: CustomButton( | ||||
|                         text: LocaleKeys.guest.tr(), | ||||
|                         onPressed: () { | ||||
|                           Navigator.of(context).pushReplacement( | ||||
|                             MaterialPageRoute(builder: (BuildContext context) => LoginScreen()), | ||||
|                           ); | ||||
|                         }, | ||||
|                         backgroundColor: Color(0xffFEE9EA), | ||||
|                         borderColor: Color(0xffFEE9EA), | ||||
|                         textColor: Color(0xffED1C2B), | ||||
|                         fontSize: 16, | ||||
|                         fontWeight: FontWeight.w500, | ||||
|                         borderRadius: 12, | ||||
|                         padding: EdgeInsets.fromLTRB(0, 10, 0, 10), | ||||
|                         // icon: "assets/images/svg/apple-finder.svg", | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                   SizedBox( | ||||
|                     width: MediaQuery.of(context).size.width * 0.05, | ||||
|                   ), | ||||
|                   Expanded( | ||||
|                     child: Container( | ||||
|                       height: 56, | ||||
|                       child: CustomButton( | ||||
|                         text: LocaleKeys.switchAccount.tr(), | ||||
|                         onPressed: () { | ||||
|                           Navigator.of(context).pushReplacement( | ||||
|                             MaterialPageRoute(builder: (BuildContext context) => LoginScreen()), | ||||
|                           ); | ||||
|                         }, | ||||
|                         backgroundColor: Color(0xffFEE9EA), | ||||
|                         borderColor: Color(0xffFEE9EA), | ||||
|                         textColor: Color(0xffED1C2B), | ||||
|                         fontSize: 15, | ||||
|                         fontWeight: FontWeight.w500, | ||||
|                         borderRadius: 12, | ||||
|                         padding: EdgeInsets.fromLTRB(0, 10, 0, 10), | ||||
|                         // icon: "assets/images/svg/apple-finder.svg", | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|               const SizedBox(height: 20), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   String getTypeIcons(int type) { | ||||
|     final types = { | ||||
|       1: AppAssets.sms, | ||||
|       2: AppAssets.fingerprint, | ||||
|       3: AppAssets.fingerprint, | ||||
|       4: AppAssets.whatsapp, | ||||
|     }; | ||||
| 
 | ||||
|     if (types.containsKey(type)) { | ||||
|       return types[type]!; | ||||
|     } else { | ||||
|       throw Exception('Invalid login type: $type'); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,90 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/date_util.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; | ||||
| import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; | ||||
| import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; | ||||
| import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| 
 | ||||
| import 'widgets/insurance_history.dart'; | ||||
| 
 | ||||
| class InsuranceHomePage extends StatefulWidget { | ||||
|   const InsuranceHomePage({super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   State<InsuranceHomePage> createState() => _InsuranceHomePageState(); | ||||
| } | ||||
| 
 | ||||
| class _InsuranceHomePageState extends State<InsuranceHomePage> { | ||||
|   late InsuranceViewModel insuranceViewModel; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     scheduleMicrotask(() { | ||||
|       insuranceViewModel.initInsuranceProvider(); | ||||
|     }); | ||||
|     super.initState(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     insuranceViewModel = Provider.of<InsuranceViewModel>(context); | ||||
|     return Scaffold( | ||||
|       backgroundColor: AppColors.bgScaffoldColor, | ||||
|       appBar: AppBar( | ||||
|         title: LocaleKeys.insurance.tr(context: context).toText18(), | ||||
|         backgroundColor: AppColors.bgScaffoldColor, | ||||
|       ), | ||||
|       body: SingleChildScrollView( | ||||
|         child: Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) { | ||||
|           return Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             children: [ | ||||
|               Row( | ||||
|                 mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                 children: [ | ||||
|                   "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}".toText24(isBold: true), | ||||
|                   CustomButton( | ||||
|                     icon: AppAssets.insurance_history_icon, | ||||
|                     iconColor: AppColors.primaryRedColor, | ||||
|                     iconSize: 21.h, | ||||
|                     text: LocaleKeys.history.tr(context: context), | ||||
|                     onPressed: () { | ||||
|                       insuranceVM.setIsInsuranceHistoryLoading(true); | ||||
|                       showCommonBottomSheet(context, | ||||
|                           child: InsuranceHistory(), callBackFunc: () {}, title: "", height: ResponsiveExtension.screenHeight * 0.5, isCloseButtonVisible: false, isFullScreen: false); | ||||
|                     }, | ||||
|                     backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), | ||||
|                     borderColor: AppColors.primaryRedColor.withOpacity(0.0), | ||||
|                     textColor: AppColors.primaryRedColor, | ||||
|                     fontSize: 14, | ||||
|                     fontWeight: FontWeight.w600, | ||||
|                     borderRadius: 12, | ||||
|                     padding: EdgeInsets.fromLTRB(10, 0, 10, 0), | ||||
|                     height: 40.h, | ||||
|                   ), | ||||
|                 ], | ||||
|               ).paddingSymmetrical(24.h, 24.h), | ||||
|               insuranceVM.isInsuranceLoading | ||||
|                   ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0) | ||||
|                   : PatientInsuranceCard( | ||||
|                       insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, | ||||
|                       isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), | ||||
|             ], | ||||
|           ); | ||||
|         }), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; | ||||
| import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| 
 | ||||
| class InsuranceHistory extends StatelessWidget { | ||||
|   InsuranceHistory({super.key}); | ||||
| 
 | ||||
|   late InsuranceViewModel insuranceViewModel; | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     insuranceViewModel = Provider.of<InsuranceViewModel>(context); | ||||
|     return Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) { | ||||
|       return Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Row( | ||||
|             mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|             children: [ | ||||
|               LocaleKeys.history.tr(context: context).toText24(isBold: true), | ||||
|               Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon).onPress(() { | ||||
|                 Navigator.of(context).pop(); | ||||
|               }), | ||||
|             ], | ||||
|           ).paddingSymmetrical(24.h, 24.h), | ||||
|           insuranceVM.isInsuranceHistoryLoading ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 24.h) : Container() | ||||
|         ], | ||||
|       ); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,133 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/date_util.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; | ||||
| import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; | ||||
| import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| 
 | ||||
| class PatientInsuranceCard extends StatelessWidget { | ||||
|   PatientInsuranceCard({super.key, required this.insuranceCardDetailsModel, required this.isInsuranceExpired}); | ||||
| 
 | ||||
|   PatientInsuranceDetailsResponseModel insuranceCardDetailsModel; | ||||
|   bool isInsuranceExpired = false; | ||||
| 
 | ||||
|   late InsuranceViewModel insuranceViewModel; | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     insuranceViewModel = Provider.of<InsuranceViewModel>(context); | ||||
|     return Container( | ||||
|       width: double.infinity, | ||||
|       decoration: RoundedRectangleBorder().toSmoothCornerDecoration( | ||||
|         color: AppColors.whiteColor, | ||||
|         borderRadius: 24, | ||||
|       ), | ||||
|       child: Padding( | ||||
|         padding: EdgeInsets.all(16.h), | ||||
|         child: Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             Row( | ||||
|               mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|               children: [ | ||||
|                 Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     "Haroon Amjad".toText18(isBold: true), | ||||
|                     "Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".toText12(isBold: true, color: AppColors.lightGrayColor), | ||||
|                   ], | ||||
|                 ), | ||||
|                 CustomButton( | ||||
|                   icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, | ||||
|                   iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, | ||||
|                   iconSize: 13.h, | ||||
|                   text: isInsuranceExpired ? "Insurance Expired" : "Insurance Active", | ||||
|                   onPressed: () {}, | ||||
|                   backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.15) : AppColors.successColor.withOpacity(0.15), | ||||
|                   borderColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.01) : AppColors.successColor.withOpacity(0.01), | ||||
|                   textColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, | ||||
|                   fontSize: 10, | ||||
|                   fontWeight: FontWeight.w500, | ||||
|                   borderRadius: 12, | ||||
|                   padding: EdgeInsets.fromLTRB(10, 0, 10, 0), | ||||
|                   height: 30.h, | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|             SizedBox(height: 12.h), | ||||
|             insuranceCardDetailsModel.groupName!.toText12(isBold: true), | ||||
|             insuranceCardDetailsModel.companyName!.toText12(isBold: true), | ||||
|             SizedBox(height: 8.h), | ||||
|             Wrap( | ||||
|               direction: Axis.horizontal, | ||||
|               spacing: 6.h, | ||||
|               runSpacing: 6.h, | ||||
|               children: [ | ||||
|                 Row( | ||||
|                   children: [ | ||||
|                     CustomButton( | ||||
|                       icon: AppAssets.doctor_calendar_icon, | ||||
|                       iconColor: AppColors.blackColor, | ||||
|                       iconSize: 13.h, | ||||
|                       text: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", | ||||
|                       onPressed: () {}, | ||||
|                       backgroundColor: AppColors.greyColor, | ||||
|                       borderColor: AppColors.greyColor, | ||||
|                       textColor: AppColors.blackColor, | ||||
|                       fontSize: 10, | ||||
|                       fontWeight: FontWeight.w500, | ||||
|                       borderRadius: 12, | ||||
|                       padding: EdgeInsets.fromLTRB(10, 0, 10, 0), | ||||
|                       height: 30.h, | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|                 Row( | ||||
|                   children: [ | ||||
|                     CustomButton( | ||||
|                       text: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}", | ||||
|                       onPressed: () {}, | ||||
|                       backgroundColor: AppColors.greyColor, | ||||
|                       borderColor: AppColors.greyColor, | ||||
|                       textColor: AppColors.blackColor, | ||||
|                       fontSize: 10, | ||||
|                       fontWeight: FontWeight.w500, | ||||
|                       borderRadius: 12, | ||||
|                       padding: EdgeInsets.fromLTRB(10, 0, 10, 0), | ||||
|                       height: 30.h, | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|             SizedBox(height: 10.h), | ||||
|             isInsuranceExpired | ||||
|                 ? CustomButton( | ||||
|                     icon: AppAssets.update_insurance_card_icon, | ||||
|                     iconColor: AppColors.successColor, | ||||
|                     iconSize: 15.h, | ||||
|                     text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", | ||||
|                     onPressed: () {}, | ||||
|                     backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), | ||||
|                     borderColor: AppColors.bgGreenColor.withOpacity(0.0), | ||||
|                     textColor: AppColors.bgGreenColor, | ||||
|                     fontSize: 14, | ||||
|                     fontWeight: FontWeight.w500, | ||||
|                     borderRadius: 12, | ||||
|                     padding: EdgeInsets.fromLTRB(10, 0, 10, 0), | ||||
|                     height: 40.h, | ||||
|                   ) | ||||
|                 : Container(), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ).paddingSymmetrical(24.h, 0.h); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,116 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_export.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/input_widget.dart'; | ||||
| import 'package:sizer/sizer.dart'; | ||||
| 
 | ||||
| class SearchLabResultsContent extends StatelessWidget { | ||||
|   const SearchLabResultsContent({super.key}); | ||||
| 
 | ||||
|   final List<String> _chipLabels = const [ | ||||
|     "Blood Test", | ||||
|     "X-Ray", | ||||
|     "MRI Scan", | ||||
|     "CT Scan", | ||||
|     "Ultrasound", | ||||
|     "Urine Test", | ||||
|     "Allergy Test", | ||||
|     "Cholesterol Test", | ||||
|     "Diabetes Test", | ||||
|     "Thyroid Test", | ||||
|   ]; | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
| 
 | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         Padding( | ||||
|           padding: const EdgeInsets.symmetric(horizontal: 16), | ||||
|           child: Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             children: [ | ||||
|               TextInputWidget( | ||||
|                 labelText:"Search lab results", | ||||
|                 hintText: "Type test name", | ||||
|                 controller: TextEditingController(), | ||||
|                 isEnable: true, | ||||
|                 prefix: null, | ||||
|                 autoFocus: true, | ||||
|                 isBorderAllowed: false, | ||||
|                 padding: EdgeInsets.symmetric(vertical:ResponsiveExtension(10).h, horizontal: ResponsiveExtension(15).h), | ||||
| 
 | ||||
|               ), | ||||
|                SizedBox(height: ResponsiveExtension(20).h), | ||||
|               "Suggestions".toText16(isBold: true), | ||||
|               const SizedBox(height: 12), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|         Expanded( | ||||
|           child: SingleChildScrollView( | ||||
|             padding: const EdgeInsets.symmetric(horizontal: 16), | ||||
|             child: Wrap( | ||||
|               alignment: WrapAlignment.start, | ||||
|               spacing: 10, | ||||
|               runSpacing: 10, | ||||
|               children: _chipLabels | ||||
|                   .map((label) => SuggestionChip( | ||||
|                         label: label, | ||||
|                         onTap: () {}, | ||||
|                       )) | ||||
|                   .toList(), | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|         Container( | ||||
|           color: Colors.white, | ||||
|           padding: EdgeInsets.all(ResponsiveExtension(20).h), | ||||
|           child: CustomButton( | ||||
|             text: LocaleKeys.search.tr(), | ||||
|             icon: AppAssets.search_icon, | ||||
|             iconColor: Colors.white, | ||||
|             onPressed: () => Navigator.pop(context), | ||||
|           ), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class SuggestionChip extends StatelessWidget { | ||||
|   final String label; | ||||
|   final bool isSelected; | ||||
|   final VoidCallback? onTap; | ||||
| 
 | ||||
|   const SuggestionChip({ | ||||
|     super.key, | ||||
|     required this.label, | ||||
|     this.isSelected = false, | ||||
|     this.onTap, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return GestureDetector( | ||||
|       onTap: onTap, // optional tap callback | ||||
|       child: Container( | ||||
|         padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), | ||||
|         decoration: BoxDecoration( | ||||
|           color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor, | ||||
|           borderRadius: BorderRadius.circular(8), | ||||
|         ), | ||||
|         child: label.toText12( | ||||
|           color: isSelected ? Colors.white : Colors.black87, | ||||
|           fontWeight: FontWeight.w500, | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,111 @@ | ||||
| import 'dart:io'; | ||||
| 
 | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; | ||||
| 
 | ||||
| class ExceptionBottomSheet extends StatelessWidget { | ||||
|   String message; | ||||
|   bool showOKButton; | ||||
|   bool showCancel; | ||||
|   Function() onOkPressed; | ||||
|   Function()? onCancelPressed; | ||||
| 
 | ||||
|   ExceptionBottomSheet({Key? key, required this.message, this.showOKButton = true, this.showCancel = false, required this.onOkPressed, this.onCancelPressed}) : super(key: key); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SafeArea( | ||||
|       bottom: Platform.isIOS ? false : true, // Adjust for iOS to avoid bottom padding | ||||
|       child: GestureDetector( | ||||
|         onTap: () { | ||||
|           FocusScope.of(context).unfocus(); // Dismiss the keyboard when tapping outside | ||||
|         }, | ||||
|         child: Builder(builder: (context) { | ||||
|           return Directionality( | ||||
|             textDirection: Directionality.of(context), | ||||
|             child: Container( | ||||
|               padding: EdgeInsets.all(24.h), | ||||
|               decoration: BoxDecoration( | ||||
|                 color: Color(0xFFF8F8FA), | ||||
|                 borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), | ||||
|               ), | ||||
|               child: Column( | ||||
|                 mainAxisSize: MainAxisSize.min, | ||||
|                 mainAxisAlignment: MainAxisAlignment.start, | ||||
|                 crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                 children: [ | ||||
|                   Row( | ||||
|                     mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                     children: [ | ||||
|                       LocaleKeys.notice.tr().toText28(), | ||||
|                       InkWell( | ||||
|                         onTap: () { | ||||
|                           Navigator.of(context).pop(); | ||||
|                         }, | ||||
|                         child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle), | ||||
|                       ) | ||||
|                     ], | ||||
|                   ), | ||||
|                   SizedBox(height: 10.h), | ||||
|                   (message ?? "").toText16(isBold: false, color: AppColors.textColor), | ||||
|                   SizedBox(height: 10.h), | ||||
|                   SizedBox(height: 24.h), | ||||
|                   if (showOKButton && showCancel) | ||||
|                     Row( | ||||
|                       children: [ | ||||
|                         Expanded( | ||||
|                           child: CustomButton( | ||||
|                             text: LocaleKeys.cancel.tr(), | ||||
|                             onPressed: onCancelPressed != null | ||||
|                                 ? onCancelPressed! | ||||
|                                 : () { | ||||
|                                     Navigator.of(context).pop(); | ||||
|                                   }, | ||||
|                             backgroundColor: AppColors.secondaryLightRedColor, | ||||
|                             borderColor: AppColors.secondaryLightRedColor, | ||||
|                             textColor: AppColors.primaryRedColor, | ||||
|                             icon: AppAssets.cancel, | ||||
|                             iconColor: AppColors.primaryRedColor, | ||||
|                           ), | ||||
|                         ), | ||||
|                         SizedBox(width: 5.h), | ||||
|                         Expanded( | ||||
|                           child: CustomButton( | ||||
|                             text: showCancel ? LocaleKeys.confirm.tr() : LocaleKeys.ok.tr(), | ||||
|                             onPressed: onOkPressed, | ||||
|                             backgroundColor: AppColors.bgGreenColor, | ||||
|                             borderColor: AppColors.bgGreenColor, | ||||
|                             textColor: Colors.white, | ||||
|                             icon: AppAssets.confirm, | ||||
|                           ), | ||||
|                         ), | ||||
|                       ], | ||||
|                     ), | ||||
|                   if (showOKButton && !showCancel) | ||||
|                     Padding( | ||||
|                       padding: EdgeInsets.only(bottom: 10.h), | ||||
|                       child: CustomButton( | ||||
|                         text: LocaleKeys.ok.tr(), | ||||
|                         onPressed: onOkPressed, | ||||
|                         backgroundColor: AppColors.primaryRedColor, | ||||
|                         borderColor: AppColors.primaryRedBorderColor, | ||||
|                         textColor: Colors.white, | ||||
|                         icon: AppAssets.confirm, | ||||
|                       ), | ||||
|                     ), | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           ); | ||||
|         }), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,148 @@ | ||||
| import 'dart:io'; | ||||
| 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/enums.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| 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/input_widget.dart'; | ||||
| 
 | ||||
| class GenericBottomSheet extends StatefulWidget { | ||||
|   String? countryCode; | ||||
|   String? initialPhoneNumber; | ||||
|   final List<Widget> buttons; | ||||
|   TextEditingController? textController; | ||||
|   final bool isForEmail; | ||||
|   Function(CountryEnum)? onCountryChange; | ||||
|   final bool isEnableCountryDropdown; | ||||
|   final bool isFromSavedLogin; | ||||
|   Function(String?)? onChange; | ||||
| 
 | ||||
|   // FocusNode myFocusNode; | ||||
| 
 | ||||
|   GenericBottomSheet({ | ||||
|     this.countryCode = "", | ||||
|     this.initialPhoneNumber = "", | ||||
|     required this.buttons, | ||||
|     this.textController, | ||||
|     this.isForEmail = false, | ||||
|     this.onCountryChange, | ||||
|     this.isEnableCountryDropdown = false, | ||||
|     this.isFromSavedLogin = false, | ||||
|     this.onChange, | ||||
|     // required this.myFocusNode | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   _GenericBottomSheetState createState() => _GenericBottomSheetState(); | ||||
| } | ||||
| 
 | ||||
| class _GenericBottomSheetState extends State<GenericBottomSheet> { | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
| 
 | ||||
|     if (!widget.isForEmail && widget.textController != null) { | ||||
|       widget.textController!.text = widget.initialPhoneNumber!; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void dispose() { | ||||
|     super.dispose(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SafeArea( | ||||
|       top: false, | ||||
|       bottom: Platform.isIOS ? false : true, | ||||
|       child: GestureDetector( | ||||
|         onTap: () { | ||||
|           FocusScope.of(context).unfocus(); | ||||
|         }, | ||||
|         child: Directionality( | ||||
|           textDirection: Directionality.of(context), | ||||
|           child: Container( | ||||
|             padding: EdgeInsets.all(24.h), | ||||
|             decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.bgScaffoldColor, borderRadius: 16), | ||||
|             child: Column( | ||||
|               mainAxisSize: MainAxisSize.min, | ||||
|               mainAxisAlignment: MainAxisAlignment.start, | ||||
|               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|               children: [ | ||||
|                 // Title | ||||
|                 Row( | ||||
|                   mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     Flexible( | ||||
|                         child: widget.isFromSavedLogin | ||||
|                             ? LocaleKeys.receiveOtpToast.tr().toText24() | ||||
|                             : widget.isForEmail | ||||
|                                 ? LocaleKeys.enterEmail.tr().toText24() | ||||
|                                 : LocaleKeys.enterPhoneNumber.tr().toText24()), | ||||
|                     InkWell( | ||||
|                       onTap: () { | ||||
|                         Navigator.of(context).pop(); | ||||
|                       }, | ||||
|                       child: Padding( | ||||
|                         padding: EdgeInsets.only(top: 10.h), | ||||
|                         child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|                 SizedBox(height: 8.h), | ||||
|                 // Subtitle | ||||
|                 widget.isFromSavedLogin | ||||
|                     ? LocaleKeys.pleaseChooseOption.tr().toText16() | ||||
|                     : widget.isForEmail | ||||
|                         ? LocaleKeys.enterEmailDesc.tr().toText16() | ||||
|                         : LocaleKeys.enterPhoneDesc.tr().toText16(), | ||||
| 
 | ||||
|                 if (widget.isFromSavedLogin) | ||||
|                   ...[] | ||||
|                 else ...[ | ||||
|                   widget.textController != null | ||||
|                       ? TextInputWidget( | ||||
|                           labelText: widget.isForEmail ? LocaleKeys.email : LocaleKeys.phoneNumber, | ||||
|                           hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx", | ||||
|                           controller: widget.textController!, | ||||
|                           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, | ||||
|                           isCountryDropDown: widget.isEnableCountryDropdown, | ||||
|                           leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone, | ||||
|                         ) | ||||
|                       : SizedBox(), | ||||
|                 ], | ||||
| 
 | ||||
|                 SizedBox(height: 24.h), | ||||
|                 ...widget.buttons, | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,88 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_assets.dart'; | ||||
| import 'package:hmg_patient_app_new/core/app_export.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| 
 | ||||
| void showCommonBottomSheet(BuildContext context, | ||||
|     {required Widget child, required VoidCallback callBackFunc, String? title, required double height, bool isCloseButtonVisible = true, bool isFullScreen = true}) { | ||||
|   showModalBottomSheet<String>( | ||||
|       sheetAnimationStyle: AnimationStyle( | ||||
|         duration: Duration(milliseconds: 500), // Custom animation duration | ||||
|         reverseDuration: Duration(milliseconds: 300), // Custom reverse animation duration | ||||
|       ), | ||||
|       context: context, | ||||
|       isScrollControlled: true, | ||||
|       showDragHandle: false, | ||||
|       backgroundColor: AppColors.scaffoldBgColor, | ||||
|       builder: (BuildContext context) { | ||||
|         return Container( | ||||
|           height: height, | ||||
|           decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.scaffoldBgColor, borderRadius: 24.h), | ||||
|           child: ButtonSheetContent( | ||||
|             title: title!, | ||||
|             isCloseButtonVisible: isCloseButtonVisible, | ||||
|             isFullScreen: isFullScreen, | ||||
|             child: child, | ||||
|           ), | ||||
|         ); | ||||
|       }).then((value) { | ||||
|     callBackFunc(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| class ButtonSheetContent extends StatelessWidget { | ||||
|   final Widget child; | ||||
|   final String title; | ||||
|   final bool isCloseButtonVisible; | ||||
|   final bool isFullScreen; | ||||
| 
 | ||||
|   const ButtonSheetContent({super.key, required this.child, required this.isCloseButtonVisible, required this.title, required this.isFullScreen}); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Column( | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [ | ||||
|         // SizedBox( | ||||
|         //   height: 20.h, | ||||
|         // ), | ||||
|         // Center( | ||||
|         //   child: Container( | ||||
|         //     margin: const EdgeInsets.only(top: 18, bottom: 12), | ||||
|         //     height: 4, | ||||
|         //     width: 40.h, | ||||
|         //     decoration: BoxDecoration( | ||||
|         //       color: Colors.grey[400], | ||||
|         //       borderRadius: BorderRadius.circular(2), | ||||
|         //     ), | ||||
|         //   ), | ||||
|         // ), | ||||
| 
 | ||||
|         // Close button | ||||
|         isCloseButtonVisible && isFullScreen | ||||
|             ? Padding( | ||||
|                 padding: EdgeInsets.symmetric(horizontal: 16), | ||||
|                 child: Utils.buildSvgWithAssets(icon: AppAssets.closeBottomNav, width: 32, height: 32).onPress(() { | ||||
|                   Navigator.of(context).pop(); | ||||
|                 }), | ||||
|               ) | ||||
|             : SizedBox(), | ||||
| 
 | ||||
|         isFullScreen | ||||
|             ? Column( | ||||
|                 children: [ | ||||
|                   SizedBox(height: 20.h), | ||||
|                   Padding(padding: EdgeInsets.symmetric(horizontal: 16.h), child: title.toText24(isBold: true)), | ||||
|                   SizedBox(height: 16.h), | ||||
|                 ], | ||||
|               ) | ||||
|             : SizedBox(), | ||||
| 
 | ||||
|         Expanded(child: child) | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,210 @@ | ||||
| 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/enums.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; | ||||
| 
 | ||||
| class CustomCountryDropdown extends StatefulWidget { | ||||
|   final List<CountryEnum> countryList; | ||||
|   final Function(CountryEnum)? onCountryChange; | ||||
|   final Function(String)? onPhoneNumberChanged; | ||||
|   final bool isRtl; | ||||
|   final bool isFromBottomSheet; | ||||
|   final bool isEnableTextField; | ||||
|   Widget? textField; | ||||
| 
 | ||||
|   CustomCountryDropdown({ | ||||
|     Key? key, | ||||
|     required this.countryList, | ||||
|     this.onCountryChange, | ||||
|     this.onPhoneNumberChanged, | ||||
|     required this.isRtl, | ||||
|     this.isFromBottomSheet = false, | ||||
|     this.isEnableTextField = false, | ||||
|     this.textField, | ||||
|   }) : super(key: key); | ||||
| 
 | ||||
|   @override | ||||
|   _CustomCountryDropdownState createState() => _CustomCountryDropdownState(); | ||||
| } | ||||
| 
 | ||||
| class _CustomCountryDropdownState extends State<CustomCountryDropdown> { | ||||
|   CountryEnum? selectedCountry; | ||||
|   late OverlayEntry _overlayEntry; | ||||
|   bool _isDropdownOpen = false; | ||||
|   FocusNode textFocusNode = new FocusNode(); | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     selectedCountry = CountryEnum.saudiArabia; | ||||
| 
 | ||||
|     if (widget.isEnableTextField && widget.isFromBottomSheet) { | ||||
|       WidgetsBinding.instance.addPostFrameCallback((_) { | ||||
|         if (mounted && textFocusNode.canRequestFocus) { | ||||
|           FocusScope.of(context).requestFocus(textFocusNode); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void dispose() { | ||||
|     textFocusNode.dispose(); | ||||
|     super.dispose(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Container( | ||||
|       height: 40.h, | ||||
|       decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h), | ||||
|       child: Row( | ||||
|         children: [ | ||||
|           GestureDetector( | ||||
|               onTap: () { | ||||
|                 if (_isDropdownOpen) { | ||||
|                   _closeDropdown(); | ||||
|                 } else { | ||||
|                   _openDropdown(); | ||||
|                 } | ||||
|               }, | ||||
|               child: Utils.buildSvgWithAssets(icon: selectedCountry != null ? selectedCountry!.iconPath : AppAssets.ksa, width: 40.h, height: 40.h)), | ||||
|           SizedBox(width: 8.h), | ||||
|           Utils.buildSvgWithAssets(icon: _isDropdownOpen ? AppAssets.dropdow_icon : AppAssets.dropdow_icon), | ||||
|           SizedBox(width: 4.h), | ||||
|           if (widget.isFromBottomSheet) | ||||
|             GestureDetector( | ||||
|               onTap: () { | ||||
|                 if (widget.isEnableTextField && textFocusNode.canRequestFocus) { | ||||
|                   FocusScope.of(context).requestFocus(textFocusNode); | ||||
|                 } | ||||
|               }, | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 mainAxisAlignment: MainAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   Row( | ||||
|                     children: [ | ||||
|                       Text( | ||||
|                         LocaleKeys.phoneNumber.tr(), | ||||
|                         style: TextStyle(fontSize: 12.fSize, height: 21 / 12, fontWeight: FontWeight.w500, letterSpacing: -1), | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                   Row( | ||||
|                     children: [ | ||||
|                       Text( | ||||
|                         selectedCountry!.countryCode, | ||||
|                         style: TextStyle(fontSize: 12.fSize, height: 21 / 18, fontWeight: FontWeight.w600, letterSpacing: -0.2), | ||||
|                       ), | ||||
|                       SizedBox(width: 4.h), | ||||
|                       if (widget.isEnableTextField) | ||||
|                         SizedBox( | ||||
|                           height: 20, | ||||
|                           width: 200, | ||||
|                           child: TextField( | ||||
|                               focusNode: textFocusNode, | ||||
|                               decoration: InputDecoration(hintText: "", isDense: true, border: InputBorder.none), | ||||
|                               keyboardType: TextInputType.phone, | ||||
|                               onChanged: widget.onPhoneNumberChanged), | ||||
|                         ), | ||||
|                     ], | ||||
|                   ) | ||||
|                 ], | ||||
|               ), | ||||
|             ), | ||||
|           if (!widget.isFromBottomSheet) | ||||
|             Text( | ||||
|               selectedCountry != null ? selectedCountry!.displayName : "Select Country", | ||||
|               style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2), | ||||
|             ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   void _openDropdown() { | ||||
|     if (textFocusNode.hasFocus) { | ||||
|       textFocusNode.unfocus(); | ||||
|     } | ||||
| 
 | ||||
|     RenderBox renderBox = context.findRenderObject() as RenderBox; | ||||
|     Offset offset = renderBox.localToGlobal(Offset.zero); | ||||
| 
 | ||||
|     _overlayEntry = OverlayEntry( | ||||
|       builder: (context) => Stack( | ||||
|         children: [ | ||||
|           // Dismiss dropdown when tapping outside | ||||
|           Positioned.fill( | ||||
|             child: GestureDetector( | ||||
|               onTap: _closeDropdown, | ||||
|               behavior: HitTestBehavior.translucent, | ||||
|               child: Container(), | ||||
|             ), | ||||
|           ), | ||||
|           Positioned( | ||||
|             top: offset.dy + renderBox.size.height, | ||||
|             left: widget.isRtl ? offset.dx + 6.h : offset.dx - 6.h, | ||||
|             width: renderBox.size.width, | ||||
|             child: Material( | ||||
|               child: Container( | ||||
|                 decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 12), | ||||
|                 child: Column( | ||||
|                   children: widget.countryList | ||||
|                       .map( | ||||
|                         (country) => GestureDetector( | ||||
|                           onTap: () { | ||||
|                             setState(() { | ||||
|                               selectedCountry = country; | ||||
|                             }); | ||||
|                             widget.onCountryChange?.call(country); | ||||
|                             _closeDropdown(); | ||||
|                           }, | ||||
|                           child: Container( | ||||
|                             padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), | ||||
|                             decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 16.h), | ||||
|                             child: Row( | ||||
|                               children: [ | ||||
|                                 Utils.buildSvgWithAssets(icon: country.iconPath, width: 38.h, height: 38.h), | ||||
|                                 if (!widget.isFromBottomSheet) SizedBox(width: 12.h), | ||||
|                                 if (!widget.isFromBottomSheet) Text(country.displayName, style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2)), | ||||
|                               ], | ||||
|                             ), | ||||
|                           ), | ||||
|                         ), | ||||
|                       ) | ||||
|                       .toList(), | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
|     Overlay.of(context)?.insert(_overlayEntry); | ||||
|     setState(() { | ||||
|       _isDropdownOpen = true; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   void _closeDropdown() { | ||||
|     _overlayEntry.remove(); | ||||
|     setState(() { | ||||
|       _isDropdownOpen = false; | ||||
|     }); | ||||
| 
 | ||||
|     if (widget.isEnableTextField && widget.isFromBottomSheet) { | ||||
|       Future.delayed(Duration(milliseconds: 100), () { | ||||
|         if (mounted && textFocusNode.canRequestFocus) { | ||||
|           FocusScope.of(context).requestFocus(textFocusNode); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,154 @@ | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart' show Icons, PopupMenuItem, showMenu, Colors; | ||||
| import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| 
 | ||||
| class DropdownWidget extends StatelessWidget { | ||||
|   final String labelText; | ||||
|   final String hintText; | ||||
|   final List<String> dropdownItems; | ||||
|   final String? selectedValue; | ||||
|   final Function(String?)? onChange; | ||||
|   final bool isEnable; | ||||
|   final bool isBorderAllowed; | ||||
|   final bool isAllowRadius; | ||||
|   final EdgeInsetsGeometry? padding; | ||||
|   final bool hasSelectionCustomIcon; | ||||
|   final String? selectionCustomIcon; | ||||
|   final String? leadingIcon; | ||||
| 
 | ||||
|   const DropdownWidget({ | ||||
|     Key? key, | ||||
|     required this.labelText, | ||||
|     required this.hintText, | ||||
|     required this.dropdownItems, | ||||
|     this.selectedValue, | ||||
|     this.onChange, | ||||
|     this.isEnable = true, | ||||
|     this.isBorderAllowed = true, | ||||
|     this.isAllowRadius = true, | ||||
|     this.padding, | ||||
|     this.hasSelectionCustomIcon = false, | ||||
|     this.selectionCustomIcon, | ||||
|     this.leadingIcon, | ||||
|   }) : super(key: key); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     Widget content = Column( | ||||
|       mainAxisSize: MainAxisSize.min, | ||||
|       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|       children: [_buildLabelText(), _buildDropdown(context)], | ||||
|     ); | ||||
| 
 | ||||
|     return Container( | ||||
|       padding: padding, | ||||
|       alignment: Alignment.center, // This might need adjustment based on layout | ||||
|       decoration: RoundedRectangleBorder().toSmoothCornerDecoration( | ||||
|         color: Colors.white, | ||||
|         borderRadius: isAllowRadius ? 15.h : null, | ||||
|         side: isBorderAllowed ? BorderSide(color: const Color(0xffefefef), width: 1) : null, | ||||
|       ), | ||||
|       child: Row( | ||||
|         // Wrap with a Row | ||||
|         crossAxisAlignment: CrossAxisAlignment.center, // Align items vertically in the center | ||||
|         children: [ | ||||
|           if (leadingIcon != null) ...[ | ||||
|             _buildLeadingIcon(), | ||||
|             SizedBox(width: 3.h), | ||||
|           ], | ||||
|           Expanded(child: content), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Widget _buildLeadingIcon() { | ||||
|     return Container( | ||||
|         height: 40.h, | ||||
|         width: 40.h, | ||||
|         margin: EdgeInsets.only(right: 10.h), | ||||
|         padding: EdgeInsets.all(8.h), | ||||
|         decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h, color: AppColors.greyColor), | ||||
|         child: Utils.buildSvgWithAssets(icon: leadingIcon!)); | ||||
|   } | ||||
| 
 | ||||
|   Widget _buildLabelText() { | ||||
|     return Text( | ||||
|       labelText, | ||||
|       style: TextStyle( | ||||
|         fontSize: 12.fSize, | ||||
|         fontWeight: FontWeight.w500, | ||||
|         color: Color(0xff898A8D), | ||||
|         letterSpacing: -0.2, | ||||
|         height: 18 / 12, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Widget _buildDropdown(BuildContext context) { | ||||
|     return GestureDetector( | ||||
|       onTap: isEnable | ||||
|           ? () async { | ||||
|               final renderBox = context.findRenderObject() as RenderBox; | ||||
|               final offset = renderBox.localToGlobal(Offset.zero); | ||||
|               final selected = await showMenu<String>( | ||||
|                 context: context, | ||||
|                 position: RelativeRect.fromLTRB( | ||||
|                   offset.dx, | ||||
|                   offset.dy + renderBox.size.height, | ||||
|                   offset.dx + renderBox.size.width, | ||||
|                   0, | ||||
|                 ), | ||||
|                 items: dropdownItems | ||||
|                     .map( | ||||
|                       (e) => PopupMenuItem<String>( | ||||
|                         value: e, | ||||
|                         child: Text( | ||||
|                           e, | ||||
|                           style: TextStyle( | ||||
|                             fontSize: 14.fSize, | ||||
|                             height: 21 / 14, | ||||
|                             fontWeight: FontWeight.w500, | ||||
|                             letterSpacing: -0.2, | ||||
|                           ), | ||||
|                         ), | ||||
|                       ), | ||||
|                     ) | ||||
|                     .toList(), | ||||
|                 shape: RoundedRectangleBorder( | ||||
|                   borderRadius: BorderRadius.circular(12), | ||||
|                 ), | ||||
|               ); | ||||
| 
 | ||||
|               if (selected != null && onChange != null) { | ||||
|                 onChange!(selected); | ||||
|               } | ||||
|             } | ||||
|           : null, | ||||
|       child: Row( | ||||
|         mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|         crossAxisAlignment: CrossAxisAlignment.center, | ||||
|         children: [ | ||||
|           Expanded( | ||||
|             child: Text( | ||||
|               (selectedValue == null || selectedValue!.isEmpty) ? hintText : selectedValue!, | ||||
|               textAlign: TextAlign.left, | ||||
|               textDirection: TextDirection.ltr, | ||||
|               style: TextStyle( | ||||
|                 fontSize: 14.fSize, | ||||
|                 height: 21 / 14, | ||||
|                 fontWeight: FontWeight.w500, | ||||
|                 color: (selectedValue != null && selectedValue!.isNotEmpty) ? const Color(0xff2E3039) : const Color(0xffB0B0B0), | ||||
|                 letterSpacing: -0.2, | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|           if (hasSelectionCustomIcon && selectionCustomIcon != null) Utils.buildSvgWithAssets(icon: selectionCustomIcon!) else const Icon(Icons.keyboard_arrow_down_outlined), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -1,132 +0,0 @@ | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart' show Icons, PopupMenuItem, showMenu, Colors; | ||||
| import 'package:hmg_patient_app_new/core/utils/utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| 
 | ||||
| class DropdownWidget extends StatelessWidget { | ||||
|   final String labelText; | ||||
|   final String hintText; | ||||
|   final List<String> dropdownItems; | ||||
|   final String? selectedValue; | ||||
|   final Function(String?)? onChange; | ||||
|   final bool isEnable; | ||||
|   final bool isBorderAllowed; | ||||
|   final bool isAllowRadius; | ||||
|   final EdgeInsetsGeometry? padding; | ||||
|   final bool hasSelectionCustomIcon; | ||||
|   final String? selectionCustomIcon; | ||||
| 
 | ||||
|   const DropdownWidget({ | ||||
|     Key? key, | ||||
|     required this.labelText, | ||||
|     required this.hintText, | ||||
|     required this.dropdownItems, | ||||
|     this.selectedValue, | ||||
|     this.onChange, | ||||
|     this.isEnable = true, | ||||
|     this.isBorderAllowed = true, | ||||
|     this.isAllowRadius = true, | ||||
|     this.padding, | ||||
|     this.hasSelectionCustomIcon = false, | ||||
|     this.selectionCustomIcon, | ||||
|   }) : super(key: key); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Container( | ||||
|       padding: padding, | ||||
|       alignment: Alignment.center, | ||||
|       decoration: RoundedRectangleBorder().toSmoothCornerDecoration( | ||||
|         color: Colors.white, | ||||
|         borderRadius: isAllowRadius ? 15 : null, | ||||
|         side: isBorderAllowed | ||||
|             ? BorderSide(color: const Color(0xffefefef), width: 1) | ||||
|             : null, | ||||
|       ), | ||||
|       child: Column( | ||||
|         mainAxisSize: MainAxisSize.min, | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           _buildLabelText(), | ||||
|           _buildDropdown(context), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Widget _buildLabelText() { | ||||
|     return Text( | ||||
|       labelText, | ||||
|       style: const TextStyle( | ||||
|         fontSize: 12, | ||||
|         fontWeight: FontWeight.w500, | ||||
|         color: Color(0xff898A8D), | ||||
|         letterSpacing: -0.2, | ||||
|         height: 18 / 12, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Widget _buildDropdown(BuildContext context) { | ||||
|     return GestureDetector( | ||||
|       onTap: isEnable | ||||
|           ? () async { | ||||
|         final renderBox = context.findRenderObject() as RenderBox; | ||||
|         final offset = renderBox.localToGlobal(Offset.zero); | ||||
|         final selected = await showMenu<String>( | ||||
|           context: context, | ||||
|           position: RelativeRect.fromLTRB( | ||||
|             offset.dx, | ||||
|             offset.dy + renderBox.size.height, | ||||
|             offset.dx + renderBox.size.width, | ||||
|             0, | ||||
|           ), | ||||
|           items: dropdownItems | ||||
|               .map( | ||||
|                 (e) => PopupMenuItem<String>( | ||||
|               value: e, | ||||
|               child: Text(e), | ||||
|             ), | ||||
|           ) | ||||
|               .toList(), | ||||
|           shape: RoundedRectangleBorder( | ||||
|             borderRadius: BorderRadius.circular(12), | ||||
|           ), | ||||
|         ); | ||||
| 
 | ||||
|         if (selected != null && onChange != null) { | ||||
|           onChange!(selected); | ||||
|         } | ||||
|       } | ||||
|           : null, | ||||
|       child: Row( | ||||
|         mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|         crossAxisAlignment: CrossAxisAlignment.center, | ||||
|         children: [ | ||||
|           Expanded( | ||||
|             child: Text( | ||||
|               (selectedValue == null || selectedValue!.isEmpty) | ||||
|                   ? hintText | ||||
|                   : selectedValue!, | ||||
|               textAlign: TextAlign.left, | ||||
|               textDirection: TextDirection.ltr, | ||||
|               style: TextStyle( | ||||
|                 fontSize: 14, | ||||
|                 height: 21 / 14, | ||||
|                 fontWeight: FontWeight.w500, | ||||
|                 color: (selectedValue != null && selectedValue!.isNotEmpty) | ||||
|                     ? const Color(0xff2E3039) | ||||
|                     : const Color(0xffB0B0B0), | ||||
|                 letterSpacing: -0.2, | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|           if (hasSelectionCustomIcon && selectionCustomIcon != null) | ||||
|             Utils.buildSvgWithAssets(icon: selectionCustomIcon!) | ||||
|           else | ||||
|             const Icon(Icons.keyboard_arrow_down_outlined), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,230 @@ | ||||
| import 'dart:async'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | ||||
| import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | ||||
| import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart'; | ||||
| import 'package:hmg_patient_app_new/theme/colors.dart'; | ||||
| import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; | ||||
| 
 | ||||
| class OTPVerificationPage extends StatefulWidget { | ||||
|   final String phoneNumber; | ||||
| 
 | ||||
|   const OTPVerificationPage({Key? key, required this.phoneNumber}) : super(key: key); | ||||
| 
 | ||||
|   @override | ||||
|   State<OTPVerificationPage> createState() => _OTPVerificationPageState(); | ||||
| } | ||||
| 
 | ||||
| class _OTPVerificationPageState extends State<OTPVerificationPage> { | ||||
|   final int _otpLength = 4; | ||||
|   late final List<TextEditingController> _controllers; | ||||
|   late final List<FocusNode> _focusNodes; | ||||
| 
 | ||||
|   Timer? _resendTimer; | ||||
|   int _resendTime = 60; | ||||
|   bool _isOtpComplete = false; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _controllers = List.generate(_otpLength, (_) => TextEditingController()); | ||||
|     _focusNodes = List.generate(_otpLength, (_) => FocusNode()); | ||||
|     _startResendTimer(); | ||||
| 
 | ||||
|     // Focus the first field once the screen is built | ||||
|     WidgetsBinding.instance.addPostFrameCallback((_) { | ||||
|       if (_focusNodes.isNotEmpty) { | ||||
|         FocusScope.of(context).requestFocus(_focusNodes[0]); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void dispose() { | ||||
|     for (final c in _controllers) c.dispose(); | ||||
|     for (final f in _focusNodes) f.dispose(); | ||||
|     _resendTimer?.cancel(); | ||||
|     super.dispose(); | ||||
|   } | ||||
| 
 | ||||
|   void _startResendTimer() { | ||||
|     _resendTimer = Timer.periodic(const Duration(seconds: 1), (timer) { | ||||
|       if (_resendTime > 0) { | ||||
|         setState(() => _resendTime--); | ||||
|       } else { | ||||
|         timer.cancel(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   void _onOtpChanged(int index, String value) { | ||||
|     if (value.length == 1 && index < _otpLength - 1) { | ||||
|       _focusNodes[index + 1].requestFocus(); | ||||
|     } else if (value.isEmpty && index > 0) { | ||||
|       _focusNodes[index - 1].requestFocus(); | ||||
|     } | ||||
|     _checkOtpCompletion(); | ||||
|   } | ||||
| 
 | ||||
|   void _checkOtpCompletion() { | ||||
|     final isComplete = _controllers.every((c) => c.text.isNotEmpty); | ||||
| 
 | ||||
|     if (isComplete != _isOtpComplete) { | ||||
|       setState(() => _isOtpComplete = isComplete); | ||||
| 
 | ||||
|       if (isComplete) { | ||||
|         _verifyOtp(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void _resendOtp() { | ||||
|     if (_resendTime == 0) { | ||||
|       setState(() => _resendTime = 60); | ||||
|       _startResendTimer(); | ||||
|       autoFillOtp("1234"); | ||||
| 
 | ||||
|       // call resend API here | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   String _getMaskedPhoneNumber() { | ||||
|     final phone = widget.phoneNumber; | ||||
|     return phone.length > 4 ? '05xxxxxx${phone.substring(phone.length - 2)}' : phone; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Scaffold( | ||||
|       appBar: CustomAppBar( | ||||
|         hideLogoAndLang: true, | ||||
|         onBackPressed: () { | ||||
|           Navigator.of(context).pop(); | ||||
|         }, | ||||
|         onLanguageChanged: (lang) {}, | ||||
|       ), | ||||
|       body: SingleChildScrollView( | ||||
|         child: Padding( | ||||
|           padding: EdgeInsets.symmetric(horizontal: 24.h), | ||||
|           child: Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             children: [ | ||||
|               SizedBox(height: 40.h), | ||||
|               Text( | ||||
|                 'OTP Verification', | ||||
|                 style: TextStyle(fontSize: 24.fSize, fontWeight: FontWeight.bold), | ||||
|               ), | ||||
|               SizedBox(height: 16.h), | ||||
|               Text( | ||||
|                 'We have sent you the OTP code on ${_getMaskedPhoneNumber()} via SMS for registration verification', | ||||
|                 style: TextStyle(fontSize: 16.fSize, color: Colors.grey), | ||||
|               ), | ||||
|               SizedBox(height: 40.h), | ||||
| 
 | ||||
|               // OTP Input Fields | ||||
|               SizedBox( | ||||
|                 height: 100, | ||||
|                 child: Row( | ||||
|                   mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||||
|                   children: List.generate(_otpLength, (index) { | ||||
|                     return ValueListenableBuilder<TextEditingValue>( | ||||
|                       valueListenable: _controllers[index], | ||||
|                       builder: (context, value, _) { | ||||
|                         final hasText = value.text.isNotEmpty; | ||||
| 
 | ||||
|                         return AnimatedContainer( | ||||
|                           duration: const Duration(milliseconds: 200), | ||||
|                           curve: Curves.easeInOut, | ||||
|                           width: 70.h, | ||||
|                           margin: EdgeInsets.symmetric(horizontal: 4.h), | ||||
|                           decoration: RoundedRectangleBorder() | ||||
|                               .toSmoothCornerDecoration(color: _isOtpComplete ? AppColors.successColor : (hasText ? AppColors.blackBgColor : AppColors.whiteColor), borderRadius: 16), | ||||
|                           child: Center( | ||||
|                             child: TextField( | ||||
|                               controller: _controllers[index], | ||||
|                               focusNode: _focusNodes[index], | ||||
|                               textAlign: TextAlign.center, | ||||
|                               keyboardType: TextInputType.number, | ||||
|                               maxLength: 1, | ||||
|                               style: TextStyle( | ||||
|                                 fontSize: 40.fSize, | ||||
|                                 fontWeight: FontWeight.bold, | ||||
|                                 color: AppColors.whiteColor, | ||||
|                               ), | ||||
|                               decoration: InputDecoration( | ||||
|                                 counterText: '', | ||||
|                                 filled: true, | ||||
|                                 fillColor: Colors.transparent, | ||||
|                                 border: OutlineInputBorder( | ||||
|                                   borderRadius: BorderRadius.circular(18), | ||||
|                                   borderSide: BorderSide.none, | ||||
|                                 ), | ||||
|                               ), | ||||
|                               onChanged: (v) => _onOtpChanged(index, v), | ||||
|                             ), | ||||
|                           ), | ||||
|                         ); | ||||
|                       }, | ||||
|                     ); | ||||
|                   }), | ||||
|                 ), | ||||
|               ), | ||||
| 
 | ||||
|               const SizedBox(height: 32), | ||||
| 
 | ||||
|               // Resend OTP | ||||
|               Row( | ||||
|                 mainAxisAlignment: MainAxisAlignment.center, | ||||
|                 children: [ | ||||
|                   const Text("Didn't receive it? "), | ||||
|                   if (_resendTime > 0) | ||||
|                     Text( | ||||
|                       'resend in (${_resendTime.toString().padLeft(2, '0')}:00). ', | ||||
|                       style: const TextStyle(color: Colors.grey), | ||||
|                     ) | ||||
|                   else | ||||
|                     GestureDetector( | ||||
|                       onTap: _resendOtp, | ||||
|                       child: const Text( | ||||
|                         'Resend', | ||||
|                         style: TextStyle( | ||||
|                           color: AppColors.primaryRedColor, | ||||
|                           fontWeight: FontWeight.bold, | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                 ], | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   void _verifyOtp() { | ||||
|     final otp = _controllers.map((c) => c.text).join(); | ||||
|     debugPrint('Verifying OTP: $otp'); | ||||
| 
 | ||||
|     ScaffoldMessenger.of(context).showSnackBar( | ||||
|       SnackBar(content: Text('Verifying OTP: $otp')), | ||||
|     ); | ||||
| 
 | ||||
|     Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => RegisterNewStep2(null, {"nationalID": "12345678654321"}))); | ||||
|   } | ||||
| 
 | ||||
|   /// Auto fill OTP into text fields | ||||
|   void autoFillOtp(String otp) { | ||||
|     if (otp.length != _otpLength) return; | ||||
| 
 | ||||
|     for (int i = 0; i < _otpLength; i++) { | ||||
|       _controllers[i].text = otp[i]; | ||||
|     } | ||||
| 
 | ||||
|     // Move focus to the last field | ||||
|     _focusNodes[_otpLength - 1].requestFocus(); | ||||
| 
 | ||||
|     // Trigger completion check and color update | ||||
|     _checkOtpCompletion(); | ||||
|   } | ||||
| } | ||||
					Loading…
					
					
				
		Reference in New Issue