diff --git a/assets/images/svg/location_red.svg b/assets/images/svg/location_red.svg new file mode 100644 index 0000000..ba8c131 --- /dev/null +++ b/assets/images/svg/location_red.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/location_unavailable.svg b/assets/images/svg/location_unavailable.svg new file mode 100644 index 0000000..0ba34eb --- /dev/null +++ b/assets/images/svg/location_unavailable.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index fd34e55..08ef65c 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -817,5 +817,14 @@ "news": "أخبار", "ready": "جاهز", "enterValidNationalId": "الرجاء إدخال رقم الهوية الوطنية أو رقم الملف الصحيح", - "enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح" + "enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح", + "medicalCentersWithCount": "{count} مراكز طبية", + "medicalCenters": "مراكز طبية", + "hospitalsWithCount": "{count} مستشفيات", + "selectRegion": "اختر المنطقة", + "selectFacility": "اختر المرافق", + "selectFacilitiesSubTitle": "يرجى اختيار المرفق للموعد", + "selectHospitalSubTitle": "يرجى اختيار المستشفى للموعد", + "iAcceptThe" : "أوافق على", + "personalDetailsVerification": "التحقق من التفاصيل الشخصية", } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index b4cd340..b8506f4 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -813,5 +813,15 @@ "enterValidNationalId": "Please enter a valid national ID or file number", "enterValidPhoneNumber": "Please enter a valid phone number", "ready": "Ready", - "news": "News" + "medicalCentersWithCount" : "{count} Medical Centers", + "medicalCenters" : " Medical Centers", + "hospitalsWithCount" : "{count} Hospitals", + "selectRegion": "Select Region", + "selectFacility": "Select Facilities", + "selectFacilitiesSubTitle": "Please select the facility for the appointment", + "selectHospitalSubTitle": "Please select the hospital for the appointment", + "news": "News", + "iAcceptThe" : "I Accept the", + "personalDetailsVerification": "Personal Details Verification" + } \ No newline at end of file diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index bad2e4c..d31679c 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -56,7 +56,7 @@ var RC_BASE_URL = 'https://rc.hmg.com/'; var PING_SERVICE = 'Services/Weather.svc/REST/CheckConnectivity'; -var GET_PROJECT = 'Services/Lists.svc/REST/GetProject'; +var GET_PROJECT_LIST = 'Services/Lists.svc/REST/GetProject'; ///Geofencing var GET_GEO_ZONES = 'Services/Patients.svc/REST/GeoF_GetAllPoints'; @@ -212,14 +212,11 @@ var GET_QR_PARKING = 'Services/SWP.svc/REST/GetQRParkingByID'; //URL to get clinic list var GET_CLINICS_LIST_URL = "Services/lists.svc/REST/GetClinicCentralized"; -var GET_CLINICS_LIST_WRT_HOSPITAL_URL = "Services/Lists.svc/REST/GetClinicFromDoctorSchedule"; +var GET_CLINICS_LIST_WRT_HOSPITAL_ID_URL = "Services/Lists.svc/REST/GetClinicFromDoctorSchedule"; //URL to get active appointment list var GET_ACTIVE_APPOINTMENTS_LIST_URL = "Services/Doctors.svc/Rest/Dr_GetAppointmentActiveNumber"; -//URL to get projects list -var GET_PROJECTS_LIST = 'Services/Lists.svc/REST/GetProject'; - //URL to get doctors list var GET_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/SearchDoctorsByTime"; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index fbd4945..0a414d0 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -110,6 +110,8 @@ class AppAssets { static const String search_by_clinic_icon = '$svgBasePath/search_by_clinic_icon.svg'; static const String search_by_doctor_icon = '$svgBasePath/search_by_doctor_icon.svg'; static const String search_by_region_icon = '$svgBasePath/search_by_region_icon.svg'; + static const String location_red = '$svgBasePath/location_red.svg'; + static const String location_unavailable = '$svgBasePath/location_unavailable.svg'; static const String livecare_clinic_icon = '$svgBasePath/livecare_clinic_icon.svg'; static const String immediate_service_icon = '$svgBasePath/immediate_service_icon.svg'; static const String no_visit_icon = '$svgBasePath/no_visit_icon.svg'; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 52500bf..f4c0fd8 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -62,7 +62,7 @@ class AppState { SelectDeviceByImeiRespModelElement? _selectDeviceByImeiRespModelElement; - void setSelectDeviceByImeiRespModelElement(SelectDeviceByImeiRespModelElement value) { + void setSelectDeviceByImeiRespModelElement(SelectDeviceByImeiRespModelElement? value) { _selectDeviceByImeiRespModelElement = value; } @@ -132,4 +132,6 @@ class AppState { set setUserRegistrationPayload(RegistrationDataModelPayload value) { _userRegistrationPayload = value; } + + } diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 90e3702..f5b3d05 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -128,6 +128,7 @@ class AppDependencies { () => MyAppointmentsViewModel( myAppointmentsRepo: getIt(), errorHandlerService: getIt(), + appState: getIt() ), ); diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 2f0b078..05a6f43 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -29,7 +29,7 @@ enum CountryEnum { saudiArabia, unitedArabEmirates } enum CalenderEnum { gregorian, hijri } -enum SelectionTypeEnum { dropdown, calendar } +enum SelectionTypeEnum { dropdown, calendar, search } enum GenderTypeEnum { male, female } diff --git a/lib/core/utils/doctor_response_mapper.dart b/lib/core/utils/doctor_response_mapper.dart new file mode 100644 index 0000000..fd0cb79 --- /dev/null +++ b/lib/core/utils/doctor_response_mapper.dart @@ -0,0 +1,207 @@ +import 'dart:math'; + +import 'package:hmg_patient_app_new/core/cache_consts.dart' show CacheConst; +import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart' show RegionList, PatientDoctorAppointmentList, DoctorList, PatientDoctorAppointmentListByRegion; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel; + +class DoctorMapper{ + static Future getMappedDoctor(List doctorList, + {bool isArabic = false}) async { + RegionList regionList = RegionList(); + final lat = await Utils.getNumFromPrefs(CacheConst.userLat); + final long = await Utils.getNumFromPrefs(CacheConst.userLat); + + for (var element in doctorList) { + String? region = element.getRegionName(isArabic); + if (region == null) continue; + + var regionDoctorList = regionList.registeredDoctorMap?.putIfAbsent(region, () => PatientDoctorAppointmentListByRegion()); + + List? targetList = element.isHMC == true + ? regionDoctorList?.hmcDoctorList + : regionDoctorList?.hmgDoctorList; + + var doctorByHospital = targetList + ?.where((clinic) => + clinic.filterName == + element.getProjectCompleteNameWithLocale(isArabic: isArabic)) + .toList() ?? + []; + + if (doctorByHospital.isNotEmpty) { + doctorByHospital.first.patientDoctorAppointmentList?.add(element); + } else { + var newAppointment = PatientDoctorAppointmentList( + filterName: + element.getProjectCompleteNameWithLocale(isArabic: isArabic), + distanceInKMs: element.projectDistanceInKiloMeters.toString(), + projectTopName: element.projectTopName, + projectBottomName: element.projectBottomName, + patientDoctorAppointment: element, + isHMC: element.isHMC + ); + if(element.projectDistanceInKiloMeters!= null ){ + if(regionDoctorList!.distance>element.projectDistanceInKiloMeters){ + regionDoctorList.distance = element.projectDistanceInKiloMeters; + } + if (element.isHMC == true && + element.projectDistanceInKiloMeters < + regionDoctorList.hmcDistance) { + regionDoctorList.hmcDistance = element.projectDistanceInKiloMeters; + } else if (element.projectDistanceInKiloMeters < + regionDoctorList.hmgDistance) { + regionDoctorList.hmgDistance = element.projectDistanceInKiloMeters; + } + }else + if (lat != 0&& + long != 0 && element.latitude != null && element.longitude != null) { + + + double distance = calculateDistance(lat.toDouble(), long.toDouble(), double.parse(element.latitude!), double.parse(element.longitude!)); + if(distance<0){ + distance *= -1; + } + if(regionDoctorList!.distance>distance){ + regionDoctorList.distance = distance; + } + if (element.isHMC == true && + element.projectDistanceInKiloMeters < + regionDoctorList.hmcDistance) { + regionDoctorList.hmcDistance = element.projectDistanceInKiloMeters; + } else if (element.projectDistanceInKiloMeters < + regionDoctorList.hmgDistance) { + regionDoctorList.hmgDistance = element.projectDistanceInKiloMeters; + } + } + targetList?.add(newAppointment); + } + + + + + regionDoctorList?.hmcSize = regionDoctorList.hmcDoctorList?.length ?? 0; + regionDoctorList?.hmgSize = regionDoctorList.hmgDoctorList?.length ?? 0; + + regionList.registeredDoctorMap?[region] = regionDoctorList; + } + + return regionList; + } + static double calculateDistance(double lat1, double lon1, double lat2, double lon2) { + var pi = 3.142; + const double R = 6371; + double dLat = (lat2 - lat1) * pi / 180; + double dLon = (lon2 - lon1) * pi / 180; + double a = sin(dLat / 2) * sin(dLat / 2) + + cos(lat1 * pi / 180) * cos(lat2 * pi / 180) * sin(dLon / 2) * sin(dLon / 2); + double c = 2 * atan2(sqrt(a), sqrt(1 - a)); + return R * c; + } + + static Future sortList(bool isGPSEnabled, RegionList unsorted, ) async { + if(isGPSEnabled){ + if(unsorted.registeredDoctorMap == null) return unsorted; + var sortedMap = Map.fromEntries( + unsorted.registeredDoctorMap!.entries.toList() + ..sort((a, b) => a.value!.distance.compareTo(b.value!.distance)), + ); + + unsorted.registeredDoctorMap = sortedMap; + return unsorted; + + } + + List? keys = unsorted.registeredDoctorMap?.keys.toList(); + keys?.sort(); + + if (keys == null) return unsorted; + Map sortedMap = {}; + for (var key in keys) { + sortedMap[key] = unsorted.registeredDoctorMap![key]!; + } + unsorted.registeredDoctorMap = sortedMap; + return unsorted; + } + + static Future getMappedHospitals( + List hospitalList, { + bool isArabic = false, + }) async { + final regionList = RegionList(); + final lat = await Utils.getNumFromPrefs(CacheConst.userLat); + final long = await Utils.getNumFromPrefs(CacheConst.userLat); + for (final hospital in hospitalList) { + final region = hospital.getRegionName(isArabic); + if (region == null) continue; + + final regionData = regionList.registeredDoctorMap?.putIfAbsent( + region, + () => PatientDoctorAppointmentListByRegion(), + ); + + List? targetList = hospital.isHMC == true + ? regionData?.hmcDoctorList + : regionData?.hmgDoctorList; + + List existingEntry = targetList + ?.where( + (entry) => entry.filterName == hospital.getName(isArabic), + ) + .toList() ?? + []; + + if (existingEntry.isNotEmpty) { + existingEntry.first.hospitalList.add(hospital); + } else { + final newEntry = PatientDoctorAppointmentList( + filterName: hospital.name, + distanceInKMs: hospital.distanceInKilometers?.toString(), + projectTopName: hospital.name, + projectBottomName: hospital.name, + model: hospital, + isHMC: hospital.isHMC); + + final distance = hospital.distanceInKilometers; + + if (distance != null) { + if (regionData!.distance > distance) { + regionData.distance = distance; + } + if (hospital.isHMC == true && distance < regionData.hmcDistance) { + regionData.hmcDistance = distance; + } else if (distance < regionData.hmgDistance) { + regionData.hmgDistance = distance; + } + } else if ( lat != 0&& + long != 0 && + hospital.latitude != null && + hospital.longitude != null) { + double calculatedDistance = calculateDistance( + lat.toDouble(), + long.toDouble(), + double.parse(hospital.latitude!), + double.parse(hospital.longitude!), + ).abs(); + + if (regionData!.distance > calculatedDistance) { + regionData.distance = calculatedDistance; + } + if (hospital.isHMC == true && + calculatedDistance < regionData.hmcDistance) { + regionData.hmcDistance = calculatedDistance; + } else if (calculatedDistance < regionData.hmgDistance) { + regionData.hmgDistance = calculatedDistance; + } + } + targetList?.add(newEntry); + } + + regionData?.hmcSize = regionData.hmcDoctorList?.length ?? 0; + regionData?.hmgSize = regionData.hmgDoctorList?.length ?? 0; + regionList.registeredDoctorMap?[region] = regionData; + } + + return regionList; + } +} \ No newline at end of file diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 713350f..db3ff79 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -237,10 +237,15 @@ extension EmailValidator on String { style: TextStyle(height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); - Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text( + Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false, FontWeight? weight, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle( + height: height ?? 23 / 26, + color: color ?? AppColors.blackColor, + fontSize: 26.fSize, + letterSpacing: letterSpacing ?? -1, + fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); Widget toText28({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text( diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index bb41af6..cd550c7 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:developer'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:flutter/material.dart'; @@ -36,6 +37,7 @@ import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/bottomsheet/exception_bottom_sheet.dart'; +import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart'; import 'models/request_models/get_user_mobile_device_data.dart'; import 'models/request_models/insert_patient_mobile_deviceinfo.dart'; @@ -48,6 +50,7 @@ class AuthenticationViewModel extends ChangeNotifier { final DialogService _dialogService; final NavigationService _navigationService; final LocalAuthService _localAuthService; + AuthenticationViewModel({ required AppState appState, required AuthenticationRepo authenticationRepo, @@ -79,6 +82,8 @@ class AuthenticationViewModel extends ChangeNotifier { CalenderEnum calenderType = CalenderEnum.gregorian; LoginTypeEnum loginTypeEnum = LoginTypeEnum.sms; + final ValueNotifier otpScreenNotifier = ValueNotifier(false); + //================== String errorMsg = ''; @@ -106,7 +111,6 @@ class AuthenticationViewModel extends ChangeNotifier { } Future clearDefaultInputValues() async { - nationalIdController.clear(); phoneNumberController.clear(); emailController.clear(); @@ -159,6 +163,10 @@ class AuthenticationViewModel extends ChangeNotifier { notifyListeners(); } + void clearEmailInput() { + emailController.text = ""; + } + void onUAEUserCountrySelection(String? value) { pickedCountryByUAEUser = countriesList!.firstWhere((element) => element.name == value); notifyListeners(); @@ -355,7 +363,7 @@ class AuthenticationViewModel extends ChangeNotifier { ); // TODO: GET APP SMS SIGNATURE HERE - request.sMSSignature = "enKTDcqbOVd"; + request.sMSSignature =await getSignature(); if (checkIsUserComingForRegister(request: payload)) { _appState.setUserRegistrationPayload = RegistrationDataModelPayload.fromJson(payload); @@ -394,12 +402,8 @@ class AuthenticationViewModel extends ChangeNotifier { return isUserComingForRegister; } - Future checkActivationCode({ - required String? activationCode, - required OTPTypeEnum otpTypeEnum, - required Function(String? message) onWrongActivationCode, - Function()? onResendActivation, - }) async { + Future checkActivationCode( + {required String? activationCode, required OTPTypeEnum otpTypeEnum, required Function(String? message) onWrongActivationCode, Function()? onResendActivation}) async { bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1); final request = RequestUtils.getCommonRequestWelcome( @@ -422,6 +426,7 @@ class AuthenticationViewModel extends ChangeNotifier { countryCode: _appState.getSelectDeviceByImeiRespModelElement != null && _appState.getSelectDeviceByImeiRespModelElement!.outSa == true ? CountryEnum.unitedArabEmirates.countryCode : selectedCountrySignup.countryCode, + //TODO: Error Here IN Zip Code. loginType: loginTypeEnum.toInt) .toJson(); LoaderBottomSheet.showLoader(); @@ -469,6 +474,7 @@ class AuthenticationViewModel extends ChangeNotifier { failure: failure, onUnHandledFailure: (failure) async { LoaderBottomSheet.hideLoader(); + otpScreenNotifier.value = true; await _dialogService.showCommonBottomSheetWithoutH(message: failure.message, label: LocaleKeys.notice.tr(), onOkPressed: () {}); }, onMessageStatusFailure: (failure) async { @@ -595,6 +601,7 @@ class AuthenticationViewModel extends ChangeNotifier { } Future onWrongActivationCode({String? message}) async { + otpScreenNotifier.value = true; await _dialogService.showErrorBottomSheet(message: message ?? "Something went wrong. ", onOkPressed: () {}); } @@ -622,19 +629,20 @@ class AuthenticationViewModel extends ChangeNotifier { } checkLastLoginStatus(Function() onSuccess) async { - Future.delayed(Duration(seconds: 1), () async { - if(cacheService.getBool(key: CacheConst.quickLoginEnabled) == null){ + if (cacheService.getBool(key: CacheConst.quickLoginEnabled) == null) { if (_appState.getSelectDeviceByImeiRespModelElement != null && (_appState.getSelectDeviceByImeiRespModelElement!.logInType == 1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType == 4)) { - phoneNumberController.text = - (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; + phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") + ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") + : _appState.getAuthenticatedUser()!.mobileNumber)!; nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; onSuccess(); } else if ((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && _appState.getSelectDeviceByImeiRespModelElement == null) && _appState.getAuthenticatedUser() != null) { - phoneNumberController.text = - (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; + phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") + ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") + : _appState.getAuthenticatedUser()!.mobileNumber)!; nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; onSuccess(); } @@ -674,21 +682,27 @@ class AuthenticationViewModel extends ChangeNotifier { } Future onRegistrationComplete() async { - LoaderBottomSheet.showLoader(); + // LoaderBottomSheet.showLoader(); + LoadingUtils.showFullScreenLoader(loadingText: "Setting up your medical file.\nMay take a moment."); + var request = RequestUtils.getUserSignupCompletionRequest(fullName: nameController.text, emailAddress: emailController.text, gender: genderType, maritalStatus: maritalStatus); final resultEither = await _authenticationRepo.registerUser(registrationPayloadDataModelRequest: request); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.data is String) { //TODO: This Section Need to Be Testing. + LoadingUtils.hideFullScreenLoader(); _dialogService.showExceptionBottomSheet(message: apiResponse.data, onOkPressed: () {}, onCancelPressed: () {}); //TODO: Here We Need to Show a Dialog Of Something in the case of Fail With OK and Cancel and the Display Variable WIll be result. } else { - print(apiResponse.data as Map); + LoadingUtils.hideFullScreenLoader(); if (apiResponse.data["MessageStatus"] == 1) { - LoaderBottomSheet.hideLoader(); + LoadingUtils.showFullScreenLoader(isSuccessDialog: true); //TODO: Here We Need to Show a Dialog Of Something in the case of Success. await clearDefaultInputValues(); // This will Clear All Default Values Of User. - _navigationService.pushAndReplace(AppRoutes.loginScreen); + Future.delayed(Duration(seconds: 1), () { + LoadingUtils.hideFullScreenLoader(); + _navigationService.pushAndReplace(AppRoutes.loginScreen); + }); } } }); @@ -736,6 +750,8 @@ class AuthenticationViewModel extends ChangeNotifier { } else { //TODO: Here Hide Loader And Show TOAST //TODO: if (response['ErrorCode'] == '-986') Toast With OK, And Show response as Output. + LoaderBottomSheet.hideLoader(); + _dialogService.showErrorBottomSheet(message: response['ErrorMessage']); } } @@ -921,4 +937,12 @@ class AuthenticationViewModel extends ChangeNotifier { }, ); } + Future getSignature() async { + if (Platform.isAndroid) { + return await SmsVerification.getAppSignature(); + } else { + return null; + } + } + } diff --git a/lib/features/authentication/widgets/otp_verification_screen.dart b/lib/features/authentication/widgets/otp_verification_screen.dart index db7b1eb..6d66061 100644 --- a/lib/features/authentication/widgets/otp_verification_screen.dart +++ b/lib/features/authentication/widgets/otp_verification_screen.dart @@ -7,8 +7,11 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.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/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; +import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart'; +import 'package:provider/provider.dart'; typedef OnDone = void Function(String text); @@ -90,6 +93,7 @@ class OTPWidgetState extends State with SingleTickerProviderStateMixi int currentIndex = 0; List strList = []; bool hasFocus = false; + AuthenticationViewModel? authVm; @override void didUpdateWidget(OTPWidget oldWidget) { @@ -124,6 +128,7 @@ class OTPWidgetState extends State with SingleTickerProviderStateMixi @override void initState() { super.initState(); + authVm = context.read(); focusNode = widget.focusNode ?? FocusNode(); _highlightAnimationController = AnimationController(vsync: this); _initTextController(); @@ -132,12 +137,15 @@ class OTPWidgetState extends State with SingleTickerProviderStateMixi widget.controller!.addListener(_controllerListener); } focusNode.addListener(_focusListener); + authVm?.otpScreenNotifier.addListener(_onOtpScreenNotifierChanged); } void _controllerListener() { if (mounted == true) { setState(() { _initTextController(); + text = widget.controller?.text ?? ""; + currentIndex = text.length; }); var onTextChanged = widget.onTextChanged; if (onTextChanged != null) { @@ -154,6 +162,41 @@ class OTPWidgetState extends State with SingleTickerProviderStateMixi } } + void onWrongOtpClear() { + if (mounted) { + setState(() { + text = ""; + currentIndex = 0; + strList.clear(); + _calculateStrList(); + }); + + // Clear the controller if it exists + if (widget.controller != null) { + widget.controller!.clear(); + } + + // Remove focus from the input + if (focusNode.hasFocus) { + focusNode.unfocus(); + } + + // Optionally refocus after a short delay to allow user to re-enter OTP + Future.delayed(const Duration(milliseconds: 100), () { + if (mounted && widget.autoFocus) { + FocusScope.of(context).requestFocus(focusNode); + } + }); + } + } + + void _onOtpScreenNotifierChanged() { + if (authVm?.otpScreenNotifier.value == true) { + onWrongOtpClear(); + authVm?.otpScreenNotifier.value = false; + } + } + void _initTextController() { if (widget.controller == null) { return; @@ -304,19 +347,17 @@ class OTPWidgetState extends State with SingleTickerProviderStateMixi ); } - Widget _buildPinCode(int i, BuildContext context) { Color pinBoxColor; if (widget.hasError) { pinBoxColor = widget.errorBorderColor; } else if (text.length == widget.maxLength) { - // Check for completion first, before individual box logic pinBoxColor = AppColors.successColor; } else if (i < text.length) { - pinBoxColor = AppColors.blackBgColor; // Custom color for filled boxes + pinBoxColor = AppColors.blackBgColor; } else { - pinBoxColor = widget.pinBoxColor; // Default white color + pinBoxColor = widget.pinBoxColor; } EdgeInsets insets; @@ -355,7 +396,6 @@ class OTPWidgetState extends State with SingleTickerProviderStateMixi ); } - // Widget _buildPinCode(int i, BuildContext context) { // Color pinBoxColor = widget.pinBoxColor; // @@ -455,13 +495,14 @@ class _OTPVerificationScreenState extends State { Timer? _resendTimer; int _resendTime = 120; bool _isOtpComplete = false; - bool _isVerifying = false; // Flag to prevent multiple verification calls + bool _isVerifying = false; @override void initState() { super.initState(); _otpController = TextEditingController(); _startResendTimer(); + checkSignature(); } @override @@ -495,6 +536,30 @@ class _OTPVerificationScreenState extends State { } } + void checkSignature() async { + SmsVerification.startListeningSms().then((message) { + final intRegex = RegExp(r'\d+', multiLine: true); + var otp = SmsVerification.getCode(message, intRegex); + if (otp != null && otp.length == _otpLength) { + autoFillOtp(otp); // Use autoFillOtp to update controller and UI + } + SmsVerification.stopListening(); + }); + } + + void _onAutoOtpChanged(String value) { + setState(() { + _isOtpComplete = value.length == _otpLength; + }); + + if (_isOtpComplete && !_isVerifying) { + _isVerifying = true; + _verifyOtp(value); + } else if (!_isOtpComplete) { + _isVerifying = false; + } + } + void _resendOtp() { if (_resendTime == 0) { setState(() { @@ -504,7 +569,6 @@ class _OTPVerificationScreenState extends State { }); _otpController.clear(); _startResendTimer(); - // autoFillOtp("1234"); widget.onResendOTPPressed(widget.phoneNumber); } } @@ -618,5 +682,7 @@ class _OTPVerificationScreenState extends State { if (otp.length != _otpLength) return; _isVerifying = false; _otpController.text = otp; + setState(() {}); + _onOtpChanged(otp); // Ensure verification and color update } } diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index 5a93879..7454b80 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -40,6 +41,12 @@ abstract class BookAppointmentsRepo { String? invoiceNoVP, Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>>> + getProjectList(); + + Future>>> getClinicsWithRespectToClinicId(String projectID); + } class BookAppointmentsRepoImp implements BookAppointmentsRepo { @@ -362,4 +369,85 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> + getProjectList() async { + Map request = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PROJECT_LIST, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListProject']; + + final appointmentsList = list + .map((item) => + HospitalsModel.fromJson(item as Map)) + .toList() + .cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> getClinicsWithRespectToClinicId(String projectID) async { + Map mapDevice = {"ProjectID": projectID}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_CLINICS_LIST_WRT_HOSPITAL_ID_URL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListClinic']; + + final clinicsList = list.map((item) => GetClinicsListResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: clinicsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 854b4fc..c2b9639 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -1,8 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/doctor_response_mapper.dart'; import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -13,6 +15,8 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/timeslots.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -21,6 +25,7 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; +import 'package:location/location.dart' show Location; class BookAppointmentsViewModel extends ChangeNotifier { int selectedTabIndex = 0; @@ -60,6 +65,14 @@ class BookAppointmentsViewModel extends ChangeNotifier { MyAppointmentsViewModel myAppointmentsViewModel; late AppState _appState; + RegionList? hospitalList; + RegionList? filteredHospitalList; + FacilitySelection currentlySelectedFacility = FacilitySelection.ALL; + bool isRegionListLoading = false; + + ///this will be used to call the clinic call when navigating from my REGION SELECTION to book appointment screen + bool shouldLoadSpecificClinic = false; + String? currentlySelectedHospitalFromRegionFlow; BookAppointmentsViewModel({required this.bookAppointmentsRepo, required this.errorHandlerService, required this.navigationService, required this.myAppointmentsViewModel}); @@ -100,7 +113,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { clinicsList.clear(); } isClinicsListLoading = value; - notifyListeners(); + // notifyListeners(); } setSelectedClinic(GetClinicsListResponseModel clinic) { @@ -139,7 +152,18 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } - Future getClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async { + /// this function will decide which clinic api to be called + /// either api for region flow or the select clinic api + Future getClinics() async + { + if(shouldLoadSpecificClinic) { + getRegionSelectedClinics(); + } else { + getAllClinics(); + } + } + + Future getAllClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await bookAppointmentsRepo.getClinics(); result.fold( @@ -164,6 +188,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { Future getDoctorsList( {int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { doctorsList.clear(); + projectID = currentlySelectedHospitalFromRegionFlow != null?int.parse(currentlySelectedHospitalFromRegionFlow!):projectID; final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName); result.fold( @@ -380,4 +405,114 @@ class BookAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future getRegionMappedProjectList() async { + //todo handle the case in the location is switch on + if(hospitalList != null && hospitalList!.registeredDoctorMap != null && hospitalList!.registeredDoctorMap!.isNotEmpty){ + filteredHospitalList = hospitalList; + return; + } + isRegionListLoading = true; + notifyListeners(); + final result = await bookAppointmentsRepo.getProjectList(); + + result.fold( + (failure) async => + await errorHandlerService.handleError(failure: failure), + (apiResponse) async { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + var projectList = apiResponse.data!; + hospitalList = await DoctorMapper.getMappedHospitals(projectList, + isArabic: false); + var lat = await Utils.getNumFromPrefs(CacheConst.userLat); + + var lng = await Utils.getNumFromPrefs(CacheConst.userLong); + var isLocationEnabled = (lat != 0) && (lng != 0); + hospitalList = + await DoctorMapper.sortList(isLocationEnabled, hospitalList!); + + isRegionListLoading = false; + filteredHospitalList = hospitalList; + notifyListeners(); + } + }, + ); + } + + void setSelectedFacility(FacilitySelection selection) { + currentlySelectedFacility = selection; + notifyListeners(); + } + + void filterHospitalListByString(String? value, String? selectedRegionId, bool isHMG) { + if(value ==null || value.isEmpty){ + filteredHospitalList = hospitalList; + } else { + filteredHospitalList = RegionList(); + + var list = isHMG + ? hospitalList?.registeredDoctorMap![selectedRegionId]!.hmgDoctorList + : hospitalList?.registeredDoctorMap![selectedRegionId]!.hmcDoctorList; + + if(list != null && list.isEmpty){ notifyListeners(); return;} + + var filteredList = list!.where((element) => + element.filterName!.toLowerCase().contains(value.toLowerCase()) + ).toList(); + var regionData = PatientDoctorAppointmentListByRegion(); + if(isHMG){ + regionData.hmgDoctorList = filteredList; + regionData.hmgSize = filteredList.length; + } else { + regionData.hmcDoctorList = filteredList; + regionData.hmcSize = filteredList.length; + } + + filteredHospitalList?.registeredDoctorMap = { + selectedRegionId! : regionData + }; + } + notifyListeners(); + } + + Future isLocationEnabled() async{ + return await Location().serviceEnabled(); + } + + bool getLocationStatus() { + bool isLocationAvaiable = false; + isLocationEnabled().then((value) => isLocationAvaiable = value); + return isLocationAvaiable; + } + + void setLoadSpecificClinic(bool status) { + shouldLoadSpecificClinic = status; + } + + void setProjectID(String? mainProjectID) { + currentlySelectedHospitalFromRegionFlow = mainProjectID; + } + + Future getRegionSelectedClinics() async{ + final result = await bookAppointmentsRepo.getClinicsWithRespectToClinicId(currentlySelectedHospitalFromRegionFlow??""); + + 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) { + clinicsList = apiResponse.data!; + isClinicsListLoading = false; + initializeFilteredList(); + notifyListeners(); + } + }, + ); + } + void resetFilterList(){ + filteredHospitalList = hospitalList; + } } diff --git a/lib/features/insurance/insurance_repo.dart b/lib/features/insurance/insurance_repo.dart index 874cc28..65f97ae 100644 --- a/lib/features/insurance/insurance_repo.dart +++ b/lib/features/insurance/insurance_repo.dart @@ -9,7 +9,7 @@ import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patien import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class InsuranceRepo { - Future>>> getPatientInsuranceDetails({required String patientId}); + Future>>> getPatientInsuranceDetails(); Future>>> getPatientInsuranceCardHistory({required String patientId}); @@ -23,7 +23,7 @@ class InsuranceRepoImp implements InsuranceRepo { InsuranceRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getPatientInsuranceDetails({required String patientId}) async { + Future>>> getPatientInsuranceDetails() async { Map mapDevice = {}; try { diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart index 527c180..8319634 100644 --- a/lib/features/insurance/insurance_view_model.dart +++ b/lib/features/insurance/insurance_view_model.dart @@ -49,10 +49,13 @@ class InsuranceViewModel extends ChangeNotifier { } Future getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await insuranceRepo.getPatientInsuranceDetails(patientId: "1231755"); + final result = await insuranceRepo.getPatientInsuranceDetails(); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + isInsuranceLoading = false; + }, (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index affe564..bbb4fcd 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -91,13 +91,13 @@ class MedicalFileRepoImp implements MedicalFileRepo { // throw Exception("lab list is empty"); // } - final vaccinesList = list.map((item) => PatientSickLeavesResponseModel.fromJson(item as Map)).toList().cast(); + final sickLeavesList = list.map((item) => PatientSickLeavesResponseModel.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, statusCode: statusCode, errorMessage: null, - data: vaccinesList, + data: sickLeavesList, ); } catch (e) { failure = DataParsingFailure(e.toString()); diff --git a/lib/features/medical_file/models/patient_sickleave_response_model.dart b/lib/features/medical_file/models/patient_sickleave_response_model.dart index b0381a8..3bf732c 100644 --- a/lib/features/medical_file/models/patient_sickleave_response_model.dart +++ b/lib/features/medical_file/models/patient_sickleave_response_model.dart @@ -14,7 +14,7 @@ class PatientSickLeavesResponseModel { num? actualDoctorRate; String? appointmentDate; String? clinicName; - double? decimalDoctorRate; + num? decimalDoctorRate; String? doctorImageURL; String? doctorName; num? doctorRate; @@ -32,7 +32,7 @@ class PatientSickLeavesResponseModel { String? isInOutPatientDescriptionN; bool? isLiveCareAppointment; dynamic medicalDirectorApprovedStatus; - int? noOfPatientsRate; + num? noOfPatientsRate; dynamic patientName; String? projectName; String? qR; diff --git a/lib/features/my_appointments/appointment_via_region_viewmodel.dart b/lib/features/my_appointments/appointment_via_region_viewmodel.dart new file mode 100644 index 0000000..a49e1c4 --- /dev/null +++ b/lib/features/my_appointments/appointment_via_region_viewmodel.dart @@ -0,0 +1,71 @@ +import 'package:flutter/foundation.dart' show ChangeNotifier; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; + +enum AppointmentViaRegionState { + REGION_SELECTION, + TYPE_SELECTION, + HOSPITAL_SELECTION, + CLINIC_SELECTION, + DOCTOR_SELECTION +} + +class AppointmentViaRegionViewmodel extends ChangeNotifier { + String? selectedRegionId; + String? selectedFacilityType; + PatientDoctorAppointmentList? selectedHospital; + final NavigationService navigationService; + AppointmentViaRegionState bottomSheetState = + AppointmentViaRegionState.REGION_SELECTION; + + AppointmentViaRegionViewmodel({required this.navigationService}); + + void setSelectedRegionId(String? regionId) { + selectedRegionId = regionId; + notifyListeners(); + } + + void setFacility(String? facility) { + selectedFacilityType = facility; + notifyListeners(); + } + + void setBottomSheetState(AppointmentViaRegionState state) { + bottomSheetState = state; + notifyListeners(); + } + + void handleLastStep(){ + navigationService.pop(); + navigationService.push(FadePage( + page: SelectClinicPage(), + ),); + } + + void handleBackPress() { + switch (bottomSheetState) { + case AppointmentViaRegionState.REGION_SELECTION: + break; + case AppointmentViaRegionState.TYPE_SELECTION: + setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION); + setSelectedRegionId(null); + break; + case AppointmentViaRegionState.HOSPITAL_SELECTION: + setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION); + break; + default: + } + } + + void flush() { + setSelectedRegionId(null); + setFacility(null); + setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION); + } + + void setHospitalModel(PatientDoctorAppointmentList? hospital) { + selectedHospital = hospital; + } +} diff --git a/lib/features/my_appointments/models/facility_selection.dart b/lib/features/my_appointments/models/facility_selection.dart new file mode 100644 index 0000000..b1f12de --- /dev/null +++ b/lib/features/my_appointments/models/facility_selection.dart @@ -0,0 +1,8 @@ +enum FacilitySelection{ +ALL('ALL'), +HMG('hmg'), +HMC('hmc'); + +final String value; +const FacilitySelection(this.value); +} \ No newline at end of file diff --git a/lib/features/my_appointments/models/resp_models/doctor_list_api_response.dart b/lib/features/my_appointments/models/resp_models/doctor_list_api_response.dart new file mode 100644 index 0000000..c3155e1 --- /dev/null +++ b/lib/features/my_appointments/models/resp_models/doctor_list_api_response.dart @@ -0,0 +1,274 @@ + +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel; + +class DoctorList { + int? clinicID; + dynamic appointmentNo; + String? clinicName; + String? doctorTitle; + num? iD; + String? name; + int? projectID; + String? projectName; + int? actualDoctorRate; + num? clinicRoomNo; + dynamic date; + dynamic appointmentDate; + dynamic dayName; + int? doctorID; + String? doctorImageURL; + dynamic doctorProfile; + dynamic doctorProfileInfo; + int? doctorRate; + num? gender; + String? genderDescription; + bool? isAppointmentAllowed; + bool? isDoctorAllowVedioCall; + bool? isDoctorDummy; + bool? isLiveCare; + bool? isLiveCareClinic; + bool? isDoctorHasPrePostImages; + String? latitude; + String? longitude; + String? nationalityFlagURL; + String? nationalityID; + String? nationalityName; + dynamic nearestFreeSlot; + int? noOfPatientsRate; + num? originalClinicID; + num? personRate; + dynamic projectDistanceInKiloMeters; + String? qR; + dynamic qRString; + num? rateNumber; + dynamic serviceID; + String? setupID; + List? speciality; + List? specialityN; + dynamic workingHours; + dynamic decimalDoctorRate; + String? projectBottomName; + String? projectTopName; + bool? isHMC; + String? region; + String? regionArabic; + String? regionEnglish; + String? regionID; + + DoctorList( + {this.clinicID, + this.appointmentNo, + this.clinicName, + this.doctorTitle, + this.iD, + this.name, + this.projectID, + this.projectName, + this.actualDoctorRate, + this.clinicRoomNo, + this.date, + this.appointmentDate, + this.dayName, + this.doctorID, + this.doctorImageURL, + this.doctorProfile, + this.doctorProfileInfo, + this.doctorRate, + this.gender, + this.genderDescription, + this.isAppointmentAllowed, + this.isDoctorAllowVedioCall, + this.isDoctorDummy, + this.isLiveCare, + this.isLiveCareClinic, + this.isDoctorHasPrePostImages, + this.latitude, + this.longitude, + this.nationalityFlagURL, + this.nationalityID, + this.nationalityName, + this.nearestFreeSlot, + this.noOfPatientsRate, + this.originalClinicID, + this.personRate, + this.projectDistanceInKiloMeters, + this.qR, + this.qRString, + this.rateNumber, + this.serviceID, + this.setupID, + this.speciality, + this.specialityN, + this.workingHours, + this.decimalDoctorRate, + this.projectBottomName, + this.projectTopName, + this.isHMC, + this.region, + this.regionArabic, + this.regionEnglish, + this.regionID, + }); + + DoctorList.fromJson( + Map json, + ) { + clinicID = json['ClinicID']; + appointmentNo = json['AppointmentNo']; + clinicName = json['ClinicName']; + doctorTitle = json['DoctorTitle']; + iD = json['ID']; + name = json['DoctorName'] ?? json['Name']; + projectID = json['ProjectID']; + projectName = json['ProjectName']; + actualDoctorRate = json['ActualDoctorRate']; + clinicRoomNo = json['ClinicRoomNo']; + date = json['Date']; + appointmentDate = json['AppointmentDate']; + dayName = json['DayName']; + doctorID = json['DoctorID']; + doctorImageURL = json['DoctorImageURL']; + doctorProfile = json['DoctorProfile']; + doctorProfileInfo = json['DoctorProfileInfo']; + doctorRate = json['DoctorRate']; + gender = json['Gender']; + genderDescription = json['GenderDescription']; + isAppointmentAllowed = json['IsAppointmentAllowed']; + isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall']; + isDoctorDummy = json['IsDoctorDummy']; + isLiveCare = json['IsLiveCare']; + isLiveCareClinic = json['IsLiveCareClinic']; + isDoctorHasPrePostImages = json['IsDoctorHasPrePostImages']; + latitude = json['Latitude']; + longitude = json['Longitude']; + nationalityFlagURL = json['NationalityFlagURL']; + nationalityID = json['NationalityID']; + nationalityName = json['NationalityName']; + nearestFreeSlot = json['NearestFreeSlot']; + noOfPatientsRate = json['NoOfPatientsRate']; + originalClinicID = json['OriginalClinicID']; + personRate = json['PersonRate']; + projectDistanceInKiloMeters = json['ProjectDistanceInKiloMeters']; + qR = json['QR']; + qRString = json['QRString']; + rateNumber = json['RateNumber']; + serviceID = json['ServiceID']; + setupID = json['SetupID']; + if (json.containsKey('Speciality') && json['Speciality'] != null) speciality = json['Speciality'].cast(); + if (json.containsKey('SpecialityN') && json['SpecialityN'] != null) specialityN = json['SpecialityN'].cast(); + workingHours = json['WorkingHours']; + decimalDoctorRate = json['DecimalDoctorRate']; + projectBottomName = json['ProjectNameBottom']; + projectTopName = json['ProjectNameTop']; + this.isHMC = json["IsHMC"]; + this.regionArabic = json['RegionNameN']; + this.regionEnglish = json['RegionName']; + } + + String? getRegionName(bool isArabic) { + if (isArabic) { + return regionArabic; + } + return regionEnglish; + } + + Map toJson() { + final Map data = new Map(); + data['ClinicID'] = this.clinicID; + data['AppointmentNo'] = this.appointmentNo; + data['ClinicName'] = this.clinicName; + data['DoctorTitle'] = this.doctorTitle; + data['ID'] = this.iD; + data['Name'] = this.name; + data['ProjectID'] = this.projectID; + data['ProjectName'] = this.projectName; + data['ActualDoctorRate'] = this.actualDoctorRate; + data['ClinicRoomNo'] = this.clinicRoomNo; + data['Date'] = this.date; + data['DayName'] = this.dayName; + data['DoctorID'] = this.doctorID; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorProfile'] = this.doctorProfile; + data['DoctorProfileInfo'] = this.doctorProfileInfo; + data['DoctorRate'] = this.doctorRate; + data['Gender'] = this.gender; + data['GenderDescription'] = this.genderDescription; + data['IsAppointmentAllowed'] = this.isAppointmentAllowed; + data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall; + data['IsDoctorDummy'] = this.isDoctorDummy; + data['IsLiveCare'] = this.isLiveCare; + data['IsLiveCareClinic'] = this.isLiveCareClinic; + data['IsDoctorHasPrePostImages'] = this.isDoctorHasPrePostImages; + data['Latitude'] = this.latitude; + data['Longitude'] = this.longitude; + data['NationalityFlagURL'] = this.nationalityFlagURL; + data['NationalityID'] = this.nationalityID; + data['NationalityName'] = this.nationalityName; + data['NearestFreeSlot'] = this.nearestFreeSlot; + data['NoOfPatientsRate'] = this.noOfPatientsRate; + data['OriginalClinicID'] = this.originalClinicID; + data['PersonRate'] = this.personRate; + data['ProjectDistanceInKiloMeters'] = this.projectDistanceInKiloMeters; + data['QR'] = this.qR; + data['QRString'] = this.qRString; + data['RateNumber'] = this.rateNumber; + data['ServiceID'] = this.serviceID; + data['SetupID'] = this.setupID; + data['Speciality'] = this.speciality; + data['SpecialityN'] = this.specialityN; + data['WorkingHours'] = this.workingHours; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + return data; + } + + String getProjectCompleteName(){ + return "${this.projectTopName} ${this.projectBottomName}"; + } + + String getProjectCompleteNameWithLocale({bool isArabic = false}) { + if (isArabic) { + return "${this.projectBottomName} ${this.projectTopName}"; + } + return "${this.projectTopName} ${this.projectBottomName}"; + } +} + +class PatientDoctorAppointmentList { + String? filterName = ""; + String? distanceInKMs = ""; + List? patientDoctorAppointmentList = []; + String? projectTopName = ""; + String? projectBottomName = ""; + bool? isHMC; + List hospitalList = []; + + PatientDoctorAppointmentList( + {this.filterName, + this.distanceInKMs, + this.projectTopName, + this.projectBottomName, + DoctorList? patientDoctorAppointment, + HospitalsModel? model, + this.isHMC = false}) { + if (model != null) { + hospitalList.add(model); + } + if (patientDoctorAppointment != null) { + patientDoctorAppointmentList!.add(patientDoctorAppointment!); + } + } +} + +class PatientDoctorAppointmentListByRegion { + List? hmgDoctorList = []; + List? hmcDoctorList = []; + int hmcSize = 0; + int hmgSize = 0; + num distance = double.infinity; + num hmgDistance = double.infinity; + num hmcDistance = double.infinity; +} + +class RegionList { + Map? registeredDoctorMap = {}; +} \ No newline at end of file diff --git a/lib/features/my_appointments/models/resp_models/hospital_model.dart b/lib/features/my_appointments/models/resp_models/hospital_model.dart new file mode 100644 index 0000000..342fcea --- /dev/null +++ b/lib/features/my_appointments/models/resp_models/hospital_model.dart @@ -0,0 +1,104 @@ +class HospitalsModel { + String? desciption; + dynamic desciptionN; + dynamic iD; + String? legalName; + String? legalNameN; + String? name; + dynamic nameN; + String? phoneNumber; + String? setupID; + dynamic distanceInKilometers; + bool? isActive; + String? latitude; + String? longitude; + dynamic mainProjectID; + bool? projectOutSA; + bool? usingInDoctorApp; + bool? isHMC; + String? region; + String? regionArabic; + String? regionEnglish; + String? regionID; + + HospitalsModel({ + this.desciption, + this.desciptionN, + this.iD, + this.legalName, + this.legalNameN, + this.name, + this.nameN, + this.phoneNumber, + this.setupID, + this.distanceInKilometers, + this.isActive, + this.latitude, + this.longitude, + this.mainProjectID, + this.projectOutSA, + this.usingInDoctorApp, + this.isHMC, + this.region, + this.regionArabic, + this.regionEnglish, + this.regionID, + }); + + HospitalsModel.fromJson(Map json) { + desciption = json['Desciption']; + desciptionN = json['DesciptionN']; + iD = json['ID']; + legalName = json['LegalName']; + legalNameN = json['LegalNameN']; + name = json['Name']; + nameN = json['NameN']; + phoneNumber = json['PhoneNumber']; + setupID = json['SetupID']; + distanceInKilometers = json['DistanceInKilometers']; + isActive = json['IsActive']; + latitude = json['Latitude']; + longitude = json['Longitude']; + mainProjectID = json['MainProjectID']; + projectOutSA = json['ProjectOutSA']; + usingInDoctorApp = json['UsingInDoctorApp']; + this.isHMC = json["IsHMC"]; + this.regionArabic = json['RegionNameN']; + this.regionEnglish = json['RegionName']; + } + + String? getRegionName(bool isArabic) { + if (isArabic) { + return regionArabic; + } + return regionEnglish; + } + + String? getName(bool isArabic) { + if (isArabic) { + return "$nameN"; + } + return name; + } + + Map toJson() { + final Map data = new Map(); + data['Desciption'] = this.desciption; + data['DesciptionN'] = this.desciptionN; + data['ID'] = this.iD; + data['LegalName'] = this.legalName; + data['LegalNameN'] = this.legalNameN; + data['Name'] = this.name; + data['NameN'] = this.nameN; + data['PhoneNumber'] = this.phoneNumber; + data['SetupID'] = this.setupID; + data['DistanceInKilometers'] = this.distanceInKilometers; + data['IsActive'] = this.isActive; + data['Latitude'] = this.latitude; + data['Longitude'] = this.longitude; + data['MainProjectID'] = this.mainProjectID; + data['ProjectOutSA'] = this.projectOutSA; + data['UsingInDoctorApp'] = this.usingInDoctorApp; + return data; + } +} diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 2035f30..e4745cb 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -3,6 +3,8 @@ 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/my_appointments/models/resp_models/hospital_model.dart' + show HospitalsModel; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -36,6 +38,8 @@ abstract class MyAppointmentsRepo { Future>>> getPatientAppointmentsForTimeLine(); Future>>> getPatientDoctorsList(); + + } class MyAppointmentsRepoImp implements MyAppointmentsRepo { diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index c6d3499..779f2b6 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -1,14 +1,18 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import '../../core/utils/doctor_response_mapper.dart' show DoctorMapper; + class MyAppointmentsViewModel extends ChangeNotifier { int selectedTabIndex = 0; MyAppointmentsRepo myAppointmentsRepo; ErrorHandlerService errorHandlerService; + AppState appState; bool isMyAppointmentsLoading = false; bool isAppointmentPatientShareLoading = false; @@ -26,7 +30,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; - MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService}); + MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService, required this.appState}); void onTabChange(int index) { selectedTabIndex = index; @@ -278,7 +282,10 @@ class MyAppointmentsViewModel extends ChangeNotifier { final result = await myAppointmentsRepo.getPatientDoctorsList(); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + isPatientMyDoctorsLoading = false; + }, (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index 535d78f..f5f32a7 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -68,7 +68,10 @@ class PrescriptionsViewModel extends ChangeNotifier { final result = await prescriptionsRepo.getPatientPrescriptionOrders(patientId: "1231755"); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + isPrescriptionsOrdersLoading = false; + }, (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 9a341a2..85c4d6d 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -812,9 +812,18 @@ abstract class LocaleKeys { static const notNow = 'notNow'; static const pendingActivation = 'pendingActivation'; static const awaitingApproval = 'awaitingApproval'; + static const news = 'news'; static const ready = 'ready'; static const enterValidNationalId = 'enterValidNationalId'; static const enterValidPhoneNumber = 'enterValidPhoneNumber'; + static const medicalCentersWithCount = 'medicalCentersWithCount'; + static const medicalCenters = 'medicalCenters'; + static const hospitalsWithCount = 'hospitalsWithCount'; + static const selectRegion = 'selectRegion'; + static const selectFacility = 'selectFacility'; + static const selectFacilitiesSubTitle = 'selectFacilitiesSubTitle'; + static const selectHospitalSubTitle = 'selectHospitalSubTitle'; + static const iAcceptThe = 'iAcceptThe'; + static const personalDetailsVerification = 'personalDetailsVerification'; - static const news = 'news'; } diff --git a/lib/main.dart b/lib/main.dart index e727d19..c75cf91 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; @@ -110,6 +111,7 @@ void main() async { create: (_) => MyAppointmentsViewModel( myAppointmentsRepo: getIt(), errorHandlerService: getIt(), + appState: getIt(), ), ), ChangeNotifierProvider( @@ -143,6 +145,9 @@ void main() async { localAuthService: getIt(), ), ), + ChangeNotifierProvider( + create: (_) => + AppointmentViaRegionViewmodel(navigationService: getIt())) ], child: MyApp()), ), ); diff --git a/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart b/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart new file mode 100644 index 0000000..1660146 --- /dev/null +++ b/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/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/theme/colors.dart' show AppColors; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; + +class FacilitySelectionItem extends StatelessWidget { + final String svgPath; + final String title; + final String subTitle; + + const FacilitySelectionItem( + {super.key, + required this.svgPath, + required this.subTitle, + required this.title}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets( + icon: svgPath, + width: 32, + height: 32, + fit: BoxFit.contain, + ), + SizedBox(height: 16,), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + info, + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18, + height: 13, + fit: BoxFit.contain, + ), + ], + ) + ], + ).paddingAll(16.h), + ); + } + + + Widget get info => Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( + title, + style: TextStyle( + fontSize: 16.h, + fontWeight: FontWeight.w600, + color: AppColors.blackColor, + ), + ), + ), + Align( + alignment: Alignment.centerLeft, + child: Text( + subTitle, + style: TextStyle( + fontSize: 12.h, + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ), + ), + ], + ); +} diff --git a/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart b/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart new file mode 100644 index 0000000..e1ff677 --- /dev/null +++ b/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart @@ -0,0 +1,80 @@ +import 'package:easy_localization/easy_localization.dart' + show tr, StringTranslateExtension; +import 'package:flutter/cupertino.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/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart' + show MyAppointmentsViewModel; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart' show Provider; + +class FacilityTypeSelectionWidget extends StatelessWidget { + late BookAppointmentsViewModel bookAppointmentViewModel; + late AppointmentViaRegionViewmodel regionalViewModel; + final String selectedRegion; + + FacilityTypeSelectionWidget({super.key, required this.selectedRegion}); + + @override + Widget build(BuildContext context) { + bookAppointmentViewModel = Provider.of(context); + regionalViewModel = Provider.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + LocaleKeys.selectFacility.tr(), + style: TextStyle( + fontSize: 21, + fontWeight: FontWeight.w600, + color: AppColors.blackColor, + ), + ), + Text( + LocaleKeys.selectFacilitiesSubTitle, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ), + SizedBox(height: 24.h), + FacilitySelectionItem( + svgPath: AppAssets.hmg, + title: "HMG".needTranslation, + subTitle: LocaleKeys.hospitalsWithCount.tr(namedArgs: { + 'count': + "${bookAppointmentViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmgSize ?? 0}" + }), + ).onPress( + () { + regionalViewModel.setFacility(FacilitySelection.HMG.name); + regionalViewModel.setBottomSheetState( + AppointmentViaRegionState.HOSPITAL_SELECTION); + }, + ), + SizedBox(height: 16.h), + FacilitySelectionItem( + svgPath: AppAssets.hmc, + title: "HMC".needTranslation, + subTitle: LocaleKeys.medicalCentersWithCount.tr(namedArgs: { + 'count': + "${bookAppointmentViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmcSize ?? 0}" + })).onPress( + () { + regionalViewModel.setFacility(FacilitySelection.HMC.name); + regionalViewModel.setBottomSheetState( + AppointmentViaRegionState.HOSPITAL_SELECTION); + }, + ), + ], + ); + } +} diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart new file mode 100644 index 0000000..71cc07d --- /dev/null +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart @@ -0,0 +1,118 @@ +import 'package:easy_localization/easy_localization.dart' + show tr, StringTranslateExtension; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; + +class HospitalBottomSheetBody extends StatelessWidget { + late BookAppointmentsViewModel appointmentsViewModel; + late AppointmentViaRegionViewmodel regionalViewModel; + final TextEditingController searchText = TextEditingController(); + + HospitalBottomSheetBody({super.key}); + + @override + Widget build(BuildContext context) { + appointmentsViewModel = Provider.of(context); + regionalViewModel = Provider.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + LocaleKeys.selectHospital.tr(), + style: TextStyle( + fontSize: 21, + fontWeight: FontWeight.w600, + color: AppColors.blackColor, + ), + ), + Text( + LocaleKeys.selectHospitalSubTitle.tr(), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ), + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(), + hintText: "Search Hospital".tr(), + controller: searchText, + onChange: (value) { + appointmentsViewModel.filterHospitalListByString(value, regionalViewModel.selectedRegionId , regionalViewModel.selectedFacilityType == + FacilitySelection.HMG.name); + }, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + isAllowLeadingIcon: true, + selectionType: SelectionTypeEnum.search, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + SizedBox(height: 24.h), + // TypeSelectionWidget( + // hmcCount: "0", + // hmgCount: "0", + // ), + // SizedBox(height: 21.h), + SizedBox( + height: MediaQuery.sizeOf(context).height * .4, + child: ListView.separated( + itemBuilder: (_, index) + { + var hospital = regionalViewModel.selectedFacilityType == + FacilitySelection.HMG.name + ? appointmentsViewModel + .filteredHospitalList! + .registeredDoctorMap![ + regionalViewModel.selectedRegionId!]! + .hmgDoctorList![index] + : appointmentsViewModel + .filteredHospitalList + ?.registeredDoctorMap?[ + regionalViewModel.selectedRegionId!] + ?.hmcDoctorList?[index]; + return HospitalListItem( + hospitalData: hospital, + isLocationEnabled: appointmentsViewModel.getLocationStatus(), + ).onPress(() { + regionalViewModel.setHospitalModel(hospital); + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION); + regionalViewModel.handleLastStep(); + });}, + separatorBuilder: (_, __) => SizedBox( + height: 16.h, + ), + itemCount: (regionalViewModel.selectedFacilityType == + FacilitySelection.HMG.name + ? (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[ + regionalViewModel.selectedRegionId]?.hmgDoctorList) + : (appointmentsViewModel + .filteredHospitalList + ?.registeredDoctorMap?[ + regionalViewModel.selectedRegionId]?.hmcDoctorList))?.length ?? + 0), + ) + ], + ); + } +} diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart new file mode 100644 index 0000000..41fb924 --- /dev/null +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/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/my_appointments/models/resp_models/doctor_list_api_response.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +class HospitalListItem extends StatelessWidget { + final PatientDoctorAppointmentList? hospitalData; + final bool isLocationEnabled; + + late AppState appState; + + HospitalListItem( + {super.key, required this.hospitalData, required this.isLocationEnabled}); + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [hospitalName, distanceInfo], + ), + ), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18, + height: 13, + fit: BoxFit.contain, + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ); + } + + Widget get hospitalName => Row( + children: [ + Utils.buildSvgWithAssets( + icon: (hospitalData?.isHMC == true) ? AppAssets.hmc : AppAssets.hmg, + ).paddingOnly(right: 10), + Expanded( + child: Text( + hospitalData?.filterName ?? "", + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: AppColors.blackColor, + ), + ), + ) + ], + ); + + Widget get distanceInfo => Row( + + children: [ + Visibility( + visible: (hospitalData?.distanceInKMs != "0"), + child: + + + AppCustomChipWidget( + labelText: + "${hospitalData?.distanceInKMs ?? ""} km".needTranslation, + deleteIcon: AppAssets.location_red, + deleteIconSize: Size(9, 12), + backgroundColor: AppColors.secondaryLightRedColor, + textColor: AppColors.errorColor, + ), + + + ), + Visibility( + visible: (hospitalData?.distanceInKMs == "0"), + child: Row( + children: [ + AppCustomChipWidget( + labelText: "Distance not available".needTranslation, + textColor: AppColors.blackColor, + ), + SizedBox(width: 8.h,) + + ], + )), + Visibility( + visible: !isLocationEnabled, + child: AppCustomChipWidget( + labelText: "Location turned off".needTranslation, + deleteIcon: AppAssets.location_unavailable, + deleteIconSize: Size(9, 12), + textColor: AppColors.blackColor, + )), + ], + ); +} diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart new file mode 100644 index 0000000..b023f7a --- /dev/null +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.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/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:provider/provider.dart' show Consumer; + +class TypeSelectionWidget extends StatelessWidget { + final String hmcCount; + final String hmgCount; + + const TypeSelectionWidget( + {super.key, required this.hmcCount, required this.hmgCount}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (_, data, __) => Row( + spacing: 8, + mainAxisSize: MainAxisSize.max, + children: [ + AppCustomChipWidget( + labelText: "All Facilities".needTranslation, + shape: RoundedRectangleBorder( + side: BorderSide( + color: data.currentlySelectedFacility == FacilitySelection.ALL + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: + data.currentlySelectedFacility == FacilitySelection.ALL + ? AppColors.secondaryLightRedColor + : AppColors.whiteColor, + textColor: data.currentlySelectedFacility == FacilitySelection.ALL + ? AppColors.errorColor + : AppColors.blackColor, + ).onPress((){ + data.setSelectedFacility(FacilitySelection.ALL); + }), + AppCustomChipWidget( + icon: AppAssets.hmg, + iconHasColor: false, + labelText: "Hospitals".needTranslation, + shape: RoundedRectangleBorder( + side: BorderSide( + color: data.currentlySelectedFacility == FacilitySelection.HMG + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: + data.currentlySelectedFacility == FacilitySelection.HMG + ? AppColors.secondaryLightRedColor + : AppColors.whiteColor, + textColor: data.currentlySelectedFacility == FacilitySelection.HMG + ? AppColors.errorColor + : AppColors.blackColor, + ).onPress((){ + data.setSelectedFacility(FacilitySelection.HMG); + }), + AppCustomChipWidget( + icon: AppAssets.hmc, + iconHasColor: false, + labelText: "Medical Centers".needTranslation, + shape: RoundedRectangleBorder( + side: BorderSide( + color: data.currentlySelectedFacility == FacilitySelection.HMC + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: + data.currentlySelectedFacility == FacilitySelection.HMC + ? AppColors.secondaryLightRedColor + : AppColors.whiteColor, + textColor: data.currentlySelectedFacility == FacilitySelection.HMC + ? AppColors.errorColor + : AppColors.blackColor, + ).onPress((){ + data.setSelectedFacility(FacilitySelection.HMC); + }), + ], + ), + ); + } +} diff --git a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart new file mode 100644 index 0000000..ab8e21f --- /dev/null +++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart @@ -0,0 +1,114 @@ +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/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +class RegionListItem extends StatelessWidget { + final String title; + final String hmcCount; + final String hmgCount; + final String subTitle; + + const RegionListItem( + {super.key, + required this.title, + required this.subTitle, + required this.hmcCount, + required this.hmgCount}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + header, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + spacing: 8.h, + children: [ + placesCountItem( + AppAssets.hmg, hmgCount, " ${LocaleKeys.hospital.tr()}"), + placesCountItem(AppAssets.hmc, hmcCount, + " ${LocaleKeys.medicalCenters.tr()}"), + ], + ), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18, + height: 13, + fit: BoxFit.contain, + ), + ], + ) + ], + ).paddingAll(16.h), + ); + } + + Widget placesCountItem(String svgPath, String count, String title) { + return AppCustomChipWidget( + iconSize: 14, + icon: svgPath, + iconHasColor: false, + richText: RichText( + text: TextSpan( + text: count, + style: TextStyle( + fontSize: 12.h, + fontWeight: FontWeight.w700, + color: AppColors.blackColor), + children: [ + TextSpan( + text: title, + style: TextStyle( + fontSize: 12.h, + fontWeight: FontWeight.w500, + color: AppColors.blackColor)) + ])), + ); + } + + Widget get header => Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( + title, + style: TextStyle( + fontSize: 16.h, + fontWeight: FontWeight.w600, + color: AppColors.blackColor, + ), + ), + ), + Align( + alignment: Alignment.centerLeft, + child: Text( + subTitle, + style: TextStyle( + fontSize: 12.h, + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ), + ), + ], + ); +} diff --git a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart new file mode 100644 index 0000000..180185f --- /dev/null +++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart @@ -0,0 +1,87 @@ +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/core/utils/utils.dart' show Utils; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart' + show MyAppointmentsViewModel; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart' + show RegionListItem; +import 'package:provider/provider.dart'; + +class RegionBottomSheetBody extends StatefulWidget { + + const RegionBottomSheetBody({super.key}); + + @override + State createState() => _RegionBottomSheetBodyState(); +} + +class _RegionBottomSheetBodyState extends State { + late BookAppointmentsViewModel myAppointmentsViewModel; + late AppointmentViaRegionViewmodel regionalViewModel; + + @override + void initState() { + scheduleMicrotask(() { + myAppointmentsViewModel.getRegionMappedProjectList(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + myAppointmentsViewModel = Provider.of(context); + regionalViewModel = Provider.of(context); + return Consumer( + builder: (context, myAppointmentsVM, child) { + if (myAppointmentsVM.isRegionListLoading) { + return Container( + height: MediaQuery.of(context).size.height * 0.3, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + child: Center( + child: Utils.getLoadingWidget(), + ), + ); + } else { + return SizedBox( + height: MediaQuery.of(context).size.height * 0.5, + child: ListView.separated( + itemCount: + myAppointmentsVM.hospitalList?.registeredDoctorMap?.length ?? + 0, + separatorBuilder: (_, __) { + return SizedBox( + height: 16.h, + ); + }, + itemBuilder: (_, index) { + String key = myAppointmentsVM + .hospitalList?.registeredDoctorMap?.keys + .toList()[index] ?? + ''; + return RegionListItem( + title: key, + subTitle: "", + hmcCount: + "${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmcSize ?? 0}", + hmgCount: + "${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmgSize ?? 0}", + ).onPress(() { + regionalViewModel.setSelectedRegionId(key); + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION); + }); + }, + ), + ); + } + }, + ); + } +} diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index 2a8c744..f3d7664 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -4,6 +4,8 @@ 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'; @@ -153,6 +155,7 @@ class LoginScreenState extends State { required TextEditingController? phoneNumberController, required AuthenticationViewModel authViewModel, }) { + AppState appState = getIt(); context.showBottomSheet( isScrollControlled: true, isDismissible: false, @@ -180,6 +183,8 @@ class LoginScreenState extends State { onOkPress: () { Navigator.of(context).pop(); })) { + Navigator.of(context).pop(); + appState.setSelectDeviceByImeiRespModelElement(null); await authViewModel.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); } }, @@ -209,6 +214,8 @@ class LoginScreenState extends State { onOkPress: () { Navigator.of(context).pop(); })) { + Navigator.of(context).pop(); + appState.setSelectDeviceByImeiRespModelElement(null); await authViewModel.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); } }, diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index 8ec988d..5150b0c 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -2,6 +2,8 @@ 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'; @@ -152,12 +154,36 @@ class _RegisterNew extends State { }, ), SizedBox(width: 12.h), - Expanded( - child: Text( - LocaleKeys.iAcceptTermsConditions.tr(), - style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), - ), + Row( + children: [ + Text( + LocaleKeys.iAcceptThe.tr(), + style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), + ), + GestureDetector( + onTap: () { + // Navigate to terms and conditions page + Navigator.of(context).pushNamed('/terms'); + }, + child: Text( + LocaleKeys.termsConditoins.tr(), + style: context.dynamicTextStyle( + fontSize: 14.fSize, + fontWeight: FontWeight.w500, + color: AppColors.primaryRedColor, + decoration: TextDecoration.underline, + decorationColor: AppColors.primaryRedBorderColor, + ), + ), + ), + ], ), + // Expanded( + // child: Text( + // LocaleKeys.iAcceptTermsConditions.tr().split("the").first, + // style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), + // ), + // ), ], ), ), @@ -224,6 +250,7 @@ class _RegisterNew extends State { } void showRegisterModel({required BuildContext context, required AuthenticationViewModel authVM}) { + AppState appState = getIt.get(); showModalBottomSheet( context: context, isScrollControlled: true, @@ -235,7 +262,7 @@ class _RegisterNew extends State { child: SingleChildScrollView( child: GenericBottomSheet( countryCode: authVM.selectedCountrySignup.countryCode, - initialPhoneNumber: "", + initialPhoneNumber: authVM.phoneNumberController.text, textController: authVM.phoneNumberController, isEnableCountryDropdown: false, onCountryChange: authVM.onCountryChange, @@ -256,6 +283,7 @@ class _RegisterNew extends State { Navigator.of(context).pop(); }, )) { + appState.setSelectDeviceByImeiRespModelElement(null); await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.sms); } }, @@ -280,15 +308,14 @@ class _RegisterNew extends State { child: CustomButton( text: LocaleKeys.sendOTPWHATSAPP.tr(), onPressed: () async { - // Dismiss keyboard before validation FocusScope.of(context).unfocus(); - if (ValidationUtils.isValidatePhone( phoneNumber: authVM.phoneNumberController.text, onOkPress: () { Navigator.of(context).pop(); }, )) { + appState.setSelectDeviceByImeiRespModelElement(null); await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.whatsapp); } }, @@ -296,6 +323,7 @@ class _RegisterNew extends State { borderColor: AppColors.borderOnlyColor, textColor: AppColors.textColor, icon: AppAssets.whatsapp, + iconColor: null, ), ), ], diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index f31909f..23ea7ae 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -44,20 +44,24 @@ class _RegisterNew extends State { Widget build(BuildContext context) { AppState appState = getIt.get(); return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, appBar: CustomAppBar( onBackPressed: () { Navigator.of(context).pop(); // authVM!.clearDefaultInputValues(); + authVM!.clearEmailInput(); }, onLanguageChanged: (lang) {}, hideLogoAndLang: true, ), body: SingleChildScrollView( reverse: false, - padding: EdgeInsets.only(left: 24.h, right: 24.h, top: 24.h), + padding: EdgeInsets.only(left: 24.h, right: 24.h, top: 0.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + LocaleKeys.personalDetailsVerification.tr().toText26(color: AppColors.textColor, weight: FontWeight.w600, letterSpacing: -2), + SizedBox(height: 24.h), Directionality( textDirection: Directionality.of(context), child: Container( @@ -66,18 +70,20 @@ class _RegisterNew extends State { child: Column( children: [ TextInputWidget( - labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), - hintText: authVM!.isUserFromUAE() ? LocaleKeys.enterNameHere.tr() : ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}"), - controller: authVM!.isUserFromUAE() ? authVM!.nameController : null, - isEnable: true, - prefix: null, - isAllowRadius: false, - isBorderAllowed: false, - keyboardType: TextInputType.text, - isAllowLeadingIcon: true, - isReadOnly: authVM!.isUserFromUAE() ? false : true, - leadingIcon: AppAssets.user_circle) - .paddingSymmetrical(0.h, 16.h), + labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), + hintText: + authVM!.isUserFromUAE() ? LocaleKeys.enterNameHere.tr() : ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}"), + controller: authVM!.isUserFromUAE() ? authVM!.nameController : null, + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + isAllowLeadingIcon: true, + isReadOnly: authVM!.isUserFromUAE() ? false : true, + leadingIcon: AppAssets.user_circle, + labelColor: AppColors.textColor, + ).paddingSymmetrical(0.h, 16.h), Divider(height: 1, color: AppColors.greyColor), TextInputWidget( labelText: LocaleKeys.nationalIdNumber.tr(), @@ -89,6 +95,7 @@ class _RegisterNew extends State { isBorderAllowed: false, isAllowLeadingIcon: true, isReadOnly: true, + labelColor: AppColors.textColor, leadingIcon: AppAssets.student_card) .paddingSymmetrical(0.h, 16.h), Divider(height: 1, color: AppColors.greyColor), @@ -108,6 +115,7 @@ class _RegisterNew extends State { isBorderAllowed: false, hasSelectionCustomIcon: true, isAllowRadius: false, + labelColor: AppColors.textColor, padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), selectionCustomIcon: AppAssets.arrow_down, leadingIcon: AppAssets.user_full, @@ -124,6 +132,7 @@ class _RegisterNew extends State { isAllowLeadingIcon: true, isReadOnly: authVM!.isUserFromUAE() ? false : true, leadingIcon: AppAssets.user_full, + labelColor: AppColors.textColor, onChange: (value) {}) .paddingSymmetrical(0.h, 16.h), Divider(height: 1, color: AppColors.greyColor), @@ -143,6 +152,7 @@ class _RegisterNew extends State { isBorderAllowed: false, hasSelectionCustomIcon: true, isAllowRadius: false, + labelColor: AppColors.textColor, padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), selectionCustomIcon: AppAssets.arrow_down, leadingIcon: AppAssets.smart_phone, @@ -160,6 +170,7 @@ class _RegisterNew extends State { isBorderAllowed: false, isAllowLeadingIcon: true, isReadOnly: true, + labelColor: AppColors.textColor, leadingIcon: AppAssets.smart_phone, onChange: (value) {}) .paddingSymmetrical(0.h, 16.h), @@ -190,6 +201,7 @@ class _RegisterNew extends State { onChange: authVM.onUAEUserCountrySelection, isBorderAllowed: false, hasSelectionCustomIcon: true, + labelColor: AppColors.textColor, isAllowRadius: false, padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), selectionCustomIcon: AppAssets.arrow_down, @@ -208,6 +220,7 @@ class _RegisterNew extends State { isBorderAllowed: false, isAllowLeadingIcon: true, isReadOnly: true, + labelColor: AppColors.textColor, leadingIcon: AppAssets.globe, onChange: (value) {}) .paddingSymmetrical(0.h, 16.h), @@ -224,6 +237,7 @@ class _RegisterNew extends State { isAllowRadius: false, isBorderAllowed: false, isAllowLeadingIcon: true, + labelColor: AppColors.textColor, isReadOnly: true, leadingIcon: AppAssets.call) .paddingSymmetrical(0.h, 16.h), @@ -240,6 +254,7 @@ class _RegisterNew extends State { isBorderAllowed: false, isAllowLeadingIcon: true, isReadOnly: true, + labelColor: AppColors.textColor, leadingIcon: AppAssets.birthday_cake, selectionType: null, ).paddingSymmetrical(0.h, 16.h), @@ -269,12 +284,12 @@ class _RegisterNew extends State { ), Expanded( child: CustomButton( - backgroundColor: AppColors.lightGreenColor, - borderColor: AppColors.lightGreenColor, - textColor: AppColors.textGreenColor, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, text: LocaleKeys.confirm.tr(), icon: AppAssets.confirm, - iconColor: AppColors.textGreenColor, + iconColor: AppColors.whiteColor, onPressed: () { if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) { if (ValidationUtils.validateUaeRegistration( diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index 0a6138c..073ff8b 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -12,6 +12,8 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; +import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart'; @@ -164,17 +166,16 @@ class _SavedLogin extends State { Padding( padding: EdgeInsets.only(bottom: 10.h), child: CustomButton( - text: LocaleKeys.sendOTPSMS.tr(), - onPressed: () { - Navigator.of(context).pop(); - loginType = LoginTypeEnum.sms; - authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedBorderColor, - textColor: AppColors.whiteColor, - icon: AppAssets.sms, - ), + text: LocaleKeys.sendOTPSMS.tr(), + onPressed: () { + Navigator.of(context).pop(); + loginType = LoginTypeEnum.sms; + authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + icon: AppAssets.sms), ), Row( crossAxisAlignment: CrossAxisAlignment.center, @@ -247,13 +248,13 @@ class _SavedLogin extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - child: Container( + child: SizedBox( height: 56, child: CustomButton( text: LocaleKeys.guest.tr(), onPressed: () { Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (BuildContext context) => LoginScreen()), + MaterialPageRoute(builder: (BuildContext context) => LandingNavigation()), ); }, backgroundColor: Color(0xffFEE9EA), @@ -277,7 +278,7 @@ class _SavedLogin extends State { text: LocaleKeys.switchAccount.tr(), onPressed: () async { await authVm.clearDefaultInputValues(); - Navigator.of(context).pushReplacement( + Navigator.of(context).push( MaterialPageRoute(builder: (BuildContext context) => LoginScreen()), ); }, diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 9533d6c..482d864 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -10,15 +10,24 @@ 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/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart' + show RegionBottomSheetBody; import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart' + show showCommonBottomSheetWithoutHeight; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; +import '../appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; + class BookAppointmentPage extends StatefulWidget { const BookAppointmentPage({super.key}); @@ -28,6 +37,7 @@ class BookAppointmentPage extends StatefulWidget { class _BookAppointmentPageState extends State { late AppState appState; + late AppointmentViaRegionViewmodel regionalViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; @override @@ -42,6 +52,8 @@ class _BookAppointmentPageState extends State { Widget build(BuildContext context) { bookAppointmentsViewModel = Provider.of(context, listen: false); appState = getIt.get(); + regionalViewModel = + Provider.of(context, listen: true); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( @@ -110,6 +122,8 @@ class _BookAppointmentPageState extends State { ], ).onPress(() { bookAppointmentsViewModel.setIsClinicsListLoading(true); + bookAppointmentsViewModel.setLoadSpecificClinic(false); + bookAppointmentsViewModel.setProjectID(null); Navigator.of(context).push( FadePage( page: SelectClinicPage(), @@ -166,7 +180,9 @@ class _BookAppointmentPageState extends State { ), Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), ], - ).onPress(() {}), + ).onPress(() { + openRegionListBottomSheet(context); + }), ], ), ), @@ -178,4 +194,54 @@ class _BookAppointmentPageState extends State { } return Container(); } + + void openRegionListBottomSheet(BuildContext context) { + regionalViewModel.flush(); + // AppointmentViaRegionViewmodel? viewmodel = null; + showCommonBottomSheetWithoutHeight(context, + title: "", + titleWidget: Consumer( + builder: (_, data, __) => getTitle(data)), + isDismissible: false, + child: Consumer(builder: (_, data, __) { + return getRegionalSelectionWidget(data); + }), callBackFunc: () { + }); + } + + Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) { + if (data.bottomSheetState == AppointmentViaRegionState.REGION_SELECTION) { + return RegionBottomSheetBody(); + } + if(data.bottomSheetState == AppointmentViaRegionState.TYPE_SELECTION){ + bookAppointmentsViewModel.resetFilterList(); + return FacilityTypeSelectionWidget(selectedRegion: data.selectedRegionId??"",); + } + if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) { + return HospitalBottomSheetBody(); + } + if (data.bottomSheetState == AppointmentViaRegionState.CLINIC_SELECTION) { + // Navigator.of(context).pop(); + bookAppointmentsViewModel.setIsClinicsListLoading(true); + bookAppointmentsViewModel.setLoadSpecificClinic(true); + bookAppointmentsViewModel.setProjectID(regionalViewModel.selectedHospital?.hospitalList.first?.mainProjectID.toString()); + + } + else { + SizedBox.shrink(); + } + return SizedBox.shrink(); + } + + getTitle(AppointmentViaRegionViewmodel data) { + if (data.selectedRegionId == null) { + return LocaleKeys.selectRegion.tr().toText20(weight: FontWeight.w600); + } else { + return Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, iconColor: Color(0xff2B353E)) + .onPress(() { + data.handleBackPress(); + }); + } + } } diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 871a97c..acaf622 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -70,6 +70,7 @@ class _MedicalFilePageState extends State { insuranceViewModel.initInsuranceProvider(); medicalFileViewModel.setIsPatientSickLeaveListLoading(true); medicalFileViewModel.getPatientSickLeaveList(); + medicalFileViewModel.onTabChanged(0); } }); super.initState(); @@ -647,9 +648,14 @@ class _MedicalFilePageState extends State { Consumer(builder: (context, insuranceVM, child) { return insuranceVM.isInsuranceLoading ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.0) - : PatientInsuranceCard( - insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, - isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))); + : insuranceVM.patientInsuranceList.isNotEmpty + ? PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isAfter( + DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), + ), + ) + : SizedBox.shrink(); }), SizedBox(height: 10.h), GridView( diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index 9835bba..420c4cf 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -22,19 +22,9 @@ class NavigationService { navigatorKey.currentState?.pushReplacementNamed(routeName); } - Future pushToOtpScreen({ - required String phoneNumber, - required Function(int code) checkActivationCode, - required Function(String phoneNumber) onResendOTPPressed, - }) { + Future pushToOtpScreen({required String phoneNumber, required Function(int code) checkActivationCode, required Function(String phoneNumber) onResendOTPPressed}) { return navigatorKey.currentState!.push( - MaterialPageRoute( - builder: (_) => OTPVerificationScreen( - phoneNumber: phoneNumber, - checkActivationCode: checkActivationCode, - onResendOTPPressed: onResendOTPPressed, - ), - ), + MaterialPageRoute(builder: (_) => OTPVerificationScreen(phoneNumber: phoneNumber, checkActivationCode: checkActivationCode, onResendOTPPressed: onResendOTPPressed)), ); } diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 8f96e57..0c7da58 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -31,6 +31,7 @@ class AppColors { static const Color textColor = Color(0xFF2E3039); static const Color textColorLight = Color(0xFF5E5E5E); static const Color borderOnlyColor = Color(0xFF2E3039); + static const Color chipBorderColorOpacity20 = Color(0x332E3039); static const Color dividerColor = Color(0xFFD2D2D2); static const Color warningColorYellow = Color(0xFFF4A308); static const Color blackBgColor = Color(0xFF2E3039); diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index 203cb86..85c13f2 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -10,20 +10,34 @@ import 'package:smooth_corner/smooth_corner.dart'; class AppCustomChipWidget extends StatelessWidget { AppCustomChipWidget({ super.key, - required this.labelText, + this.labelText, this.textColor = AppColors.textColor, this.backgroundColor = AppColors.greyColor, this.iconSize = 12, this.icon = "", this.iconColor = AppColors.textColor, + this.richText, + this.iconHasColor = true, + this.shape, + this.deleteIcon, + this.deleteIconSize = const Size(12, 12), + this.deleteIconColor = AppColors.textColor, + this.deleteIconHasColor = false, }); - String? labelText; - Color? textColor; - Color? backgroundColor; - num iconSize; - String icon; - Color iconColor; + final String? labelText; + final Widget? richText; + final Color? textColor; + final Color? backgroundColor; + final num iconSize; + final String icon; + final String? deleteIcon; + final Size? deleteIconSize; + final Color iconColor; + final Color? deleteIconColor; + final bool iconHasColor; + final bool deleteIconHasColor; + final OutlinedBorder? shape; @override Widget build(BuildContext context) { @@ -41,18 +55,55 @@ class AppCustomChipWidget extends StatelessWidget { ), child: icon.isNotEmpty ? Chip( - avatar: icon.isNotEmpty ? Utils.buildSvgWithAssets(icon: icon, width: iconSize.h, height: iconSize.h, iconColor: iconColor) : SizedBox.shrink(), - label: labelText!.toText10(weight: FontWeight.w500, letterSpacing: -0.64, color: textColor), + avatar: icon.isNotEmpty + ? Utils.buildSvgWithAssets( + icon: icon, + width: iconSize.h, + height: iconSize.h, + iconColor: iconHasColor ? iconColor : null) + : SizedBox.shrink(), + label: richText ?? + labelText!.toText10( + weight: FontWeight.w500, + letterSpacing: -0.64, + color: textColor), padding: EdgeInsets.all(0.0), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - labelPadding: EdgeInsets.only(left: -4.h, right: 8.h), + labelPadding: EdgeInsets.only( + left: -4.h, + right: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), backgroundColor: backgroundColor, + shape: shape, + deleteIcon: deleteIcon?.isNotEmpty == true + ? Utils.buildSvgWithAssets( + icon: deleteIcon!, + width: deleteIconSize!.width!.h, + height: deleteIconSize!.height.h, + iconColor: deleteIconHasColor ? deleteIconColor : null) + : null, + onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, ) : Chip( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - label: labelText!.toText10(weight: FontWeight.w500, letterSpacing: -0.64, color: textColor), + label: richText ?? + labelText!.toText10( + weight: FontWeight.w500, + letterSpacing: -0.64, + color: textColor), padding: EdgeInsets.all(0.0), backgroundColor: backgroundColor, + shape: shape, + labelPadding: EdgeInsets.only( + left: 8.h, + right: deleteIcon?.isNotEmpty == true ? -2.h : 8.h), + deleteIcon: deleteIcon?.isNotEmpty == true + ? Utils.buildSvgWithAssets( + icon: deleteIcon!, + width: deleteIconSize!.width.h, + height: deleteIconSize!.height.h, + iconColor: deleteIconHasColor ? deleteIconColor : null) + : null, + onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, ), ); } diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index b88c922..576e923 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -109,9 +109,11 @@ void showCommonBottomSheetWithoutHeight( required Widget child, required VoidCallback callBackFunc, String title = "", - bool isCloseButtonVisible = true, - bool isFullScreen = true, -}) { + + bool isCloseButtonVisible = true, + bool isFullScreen = true, + bool isDismissible = true, + Widget? titleWidget,}) { showModalBottomSheet( sheetAnimationStyle: AnimationStyle( duration: Duration(milliseconds: 500), // Custom animation duration @@ -120,6 +122,7 @@ void showCommonBottomSheetWithoutHeight( context: context, isScrollControlled: true, showDragHandle: false, + isDismissible: isDismissible, backgroundColor: AppColors.bottomSheetBgColor, builder: (BuildContext context) { return SafeArea( @@ -135,9 +138,10 @@ void showCommonBottomSheetWithoutHeight( spacing: 16.h, children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ - title.toText20(weight: FontWeight.w600).expanded, + titleWidget ?? title.toText20(weight: FontWeight.w600), Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() { Navigator.of(context).pop(); }), diff --git a/lib/widgets/dropdown/dropdown_widget.dart b/lib/widgets/dropdown/dropdown_widget.dart index 2a0cad3..f093f0b 100644 --- a/lib/widgets/dropdown/dropdown_widget.dart +++ b/lib/widgets/dropdown/dropdown_widget.dart @@ -18,29 +18,31 @@ class DropdownWidget extends StatelessWidget { final bool hasSelectionCustomIcon; final String? selectionCustomIcon; final String? leadingIcon; + final Color? labelColor; - 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); + 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, + this.labelColor}) + : super(key: key); @override Widget build(BuildContext context) { Widget content = Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, - children: [_buildLabelText(), _buildDropdown(context)], + children: [_buildLabelText(labelColor), _buildDropdown(context)], ); return Container( @@ -75,13 +77,13 @@ class DropdownWidget extends StatelessWidget { child: Utils.buildSvgWithAssets(icon: leadingIcon!)); } - Widget _buildLabelText() { + Widget _buildLabelText(Color? labelColor) { return Text( labelText, style: TextStyle( fontSize: 12.fSize, fontWeight: FontWeight.w500, - color: Color(0xff898A8D), + color: labelColor ?? Color(0xff898A8D), letterSpacing: -0.2, height: 18 / 12, ), diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index 03d7b5f..fa2d873 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -41,39 +41,41 @@ class TextInputWidget extends StatelessWidget { final num? fontSize; final bool? isWalletAmountInput; final Widget? suffix; + final Color? labelColor; // final List countryList; // final Function(Country)? onCountryChange; - TextInputWidget({ - super.key, - required this.labelText, - required this.hintText, - this.controller, - this.onChange, - this.onCalendarTypeChanged, - this.prefix, - this.isEnable = true, - this.isBorderAllowed = true, - this.isAllowRadius = true, - this.isReadOnly = false, - this.keyboardType = TextInputType.number, - this.focusNode, - this.autoFocus = false, - this.padding, - this.isAllowLeadingIcon = false, - this.leadingIcon, - this.isCountryDropDown = false, - this.hasError = false, - this.errorMessage, - this.onCountryChange, - this.selectionType, - this.fontSize = 14, - this.isWalletAmountInput = false, - this.suffix, - // this.countryList = const [], - // this.onCountryChange, - }); + TextInputWidget( + {super.key, + required this.labelText, + required this.hintText, + this.controller, + this.onChange, + this.onCalendarTypeChanged, + this.prefix, + this.isEnable = true, + this.isBorderAllowed = true, + this.isAllowRadius = true, + this.isReadOnly = false, + this.keyboardType = TextInputType.number, + this.focusNode, + this.autoFocus = false, + this.padding, + this.isAllowLeadingIcon = false, + this.leadingIcon, + this.isCountryDropDown = false, + this.hasError = false, + this.errorMessage, + this.onCountryChange, + this.selectionType, + this.fontSize = 14, + this.isWalletAmountInput = false, + this.suffix, + this.labelColor + // this.countryList = const [], + // this.onCountryChange, + }); final FocusNode _focusNode = FocusNode(); @@ -135,12 +137,13 @@ class TextInputWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildLabelText().paddingOnly(right: (appState.getLanguageCode() == "ar" ? 10 : 0)), + _buildLabelText(labelColor).paddingOnly(right: (appState.getLanguageCode() == "ar" ? 10 : 0)), _buildTextField(context), ], ), ), if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context), + if (selectionType == SelectionTypeEnum.search) _buildTrailingIconForSearch(context), ], ), ), @@ -205,13 +208,13 @@ class TextInputWidget extends StatelessWidget { ); } - Widget _buildLabelText() { + Widget _buildLabelText(Color? labelColor) { return Text( labelText, style: TextStyle( fontSize: 12.fSize, fontWeight: FontWeight.w500, - color: AppColors.inputLabelTextColor, + color: labelColor ?? AppColors.inputLabelTextColor, letterSpacing: -0.2, height: 18 / 12, ), @@ -250,4 +253,16 @@ class TextInputWidget extends StatelessWidget { ), ); } + + _buildTrailingIconForSearch(BuildContext context) { + final AppState appState = getIt.get(); + return Container( + height: 40.h, + width: 40.h, + margin: EdgeInsets.zero, + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h, color: AppColors.whiteColor), + child: Utils.buildSvgWithAssets(icon: AppAssets.search_icon), + ); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 055fb7b..e55d241 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,6 +66,7 @@ dependencies: firebase_analytics: ^11.5.1 jiffy: ^6.4.3 hijri_gregorian_calendar: ^0.1.1 + sms_otp_auto_verify: ^2.2.0 web: any flutter_staggered_animations: ^1.1.1 @@ -81,6 +82,8 @@ dependencies: flutter_swiper_view: ^1.1.8 family_bottom_sheet: ^0.1.0 + location: ^8.0.1 + dev_dependencies: flutter_test: sdk: flutter