diff --git a/assets/images/svg/doctor_contact_icon.svg b/assets/images/svg/doctor_contact_icon.svg new file mode 100644 index 0000000..00cba33 --- /dev/null +++ b/assets/images/svg/doctor_contact_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/doctor_profile_icon.svg b/assets/images/svg/doctor_profile_icon.svg new file mode 100644 index 0000000..120c3c1 --- /dev/null +++ b/assets/images/svg/doctor_profile_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/svg/free_med_delivery_icon.svg b/assets/images/svg/free_med_delivery_icon.svg new file mode 100644 index 0000000..626e035 --- /dev/null +++ b/assets/images/svg/free_med_delivery_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/immediate_service_icon.svg b/assets/images/svg/immediate_service_icon.svg new file mode 100644 index 0000000..261ac9a --- /dev/null +++ b/assets/images/svg/immediate_service_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/livecare_book_icon.svg b/assets/images/svg/livecare_book_icon.svg new file mode 100644 index 0000000..3d38ac7 --- /dev/null +++ b/assets/images/svg/livecare_book_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/livecare_clinic_icon.svg b/assets/images/svg/livecare_clinic_icon.svg new file mode 100644 index 0000000..2b60209 --- /dev/null +++ b/assets/images/svg/livecare_clinic_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/no_visit_icon.svg b/assets/images/svg/no_visit_icon.svg new file mode 100644 index 0000000..2c8616a --- /dev/null +++ b/assets/images/svg/no_visit_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index e054238..df0f391 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -754,7 +754,7 @@ "thisItemIsNotAvailable": "This item is not available", "beforeAfterImages": "Before & After Images", "clinicAcceptLivecare": "No need to wait or visit You can now get medical consultation via Video call (LiveCare service) in The name of the clinic clinic and the doctor will contact you immediately", - "livecareModalTop": "This Clinic is Accepting livecare services", + "livecareModalTop": "This Clinic is Accepting LiveCare services", "livecarePoint2": "A Specialized doctor will contact you", "livecarePoint3": "A Doctor will be able to see your full medical file history", "livecarePoint4": "Free Medicine Delivery Available", @@ -811,4 +811,5 @@ "pendingActivation": "Pending Activation", "awaitingApproval": "Awaiting Approval", "ready": "Ready" + } \ No newline at end of file diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 106a5ad..2603fb5 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -106,6 +106,13 @@ 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 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'; + static const String doctor_contact_icon = '$svgBasePath/doctor_contact_icon.svg'; + static const String free_med_delivery_icon = '$svgBasePath/free_med_delivery_icon.svg'; + static const String livecare_book_icon = '$svgBasePath/livecare_book_icon.svg'; + static const String doctor_profile_icon = '$svgBasePath/doctor_profile_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index f86ec97..191b75a 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -145,13 +145,6 @@ class AppDependencies { ), ); - getIt.registerLazySingleton( - () => HabibWalletViewModel( - habibWalletRepo: getIt(), - errorHandlerService: getIt(), - ), - ); - getIt.registerLazySingleton( () => MedicalFileViewModel( medicalFileRepo: getIt(), diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 66847a4..1c38a5e 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -193,11 +193,11 @@ extension EmailValidator on String { style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); - Widget toText18({Color? color, bool isBold = false, bool isCenter = false, int? maxlines}) => Text( + Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines}) => Text( maxLines: maxlines, textAlign: isCenter ? TextAlign.center : null, this, - style: TextStyle(fontSize: 18.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), + style: TextStyle(fontSize: 18.fSize, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText19({Color? color, bool isBold = false}) => Text( diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index 5b0bf10..baa7a09 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -1,10 +1,24 @@ import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/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/services/logger_service.dart'; abstract class BookAppointmentsRepo { - Future> getDoctors(); + Future>>> getClinics(); + + Future> getProjects(); + + Future>>> getDoctorsList(int clinicID, int projectID, bool isNearest, int doctorId, String doctorName, {isContinueDentalPlan = false}); + + Future>> getDoctorProfile(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}); } class BookAppointmentsRepoImp implements BookAppointmentsRepo { @@ -14,35 +28,155 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { BookAppointmentsRepoImp({required this.loggerService, required this.apiClient}); @override - Future> getDoctors() async { + Future>>> getClinics() async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_CLINICS_LIST_URL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListClinicCentralized']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + 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())); + } + } + + @override + Future> getProjects() { + // TODO: implement getProjects + throw UnimplementedError(); + } + + @override + Future>>> getDoctorsList(int clinicID, int projectID, bool isNearest, int doctorId, String doctorName, + {isContinueDentalPlan = false}) async { + Map mapDevice = { + "ClinicID": clinicID, + "ProjectID": projectID, + "DoctorName": doctorName, //!= null ? doctorId : 0, + "ContinueDentalPlan": isContinueDentalPlan, + "IsSearchAppointmnetByClinicID": isContinueDentalPlan ? false : true, + "isDentalAllowedBackend": clinicID == 17 ? true : isContinueDentalPlan, + "IsGetNearAppointment": isNearest, + if (isNearest) "SelectedDate": DateUtil.convertDateToString(DateTime.now()), + "License": true + }; + try { - // Mock API call with delayed response - final result = await Future.delayed( - const Duration(seconds: 2), - () => { - 'success': true, - 'data': [ - { - 'id': '1', - 'name': 'Dr. Ahmed Hassan', - 'specialty': 'Cardiology', - 'experience': '10 years', - 'rating': 4.8, - 'image': 'https://example.com/doctor1.jpg' - }, - ] - } + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_DOCTORS_LIST_URL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['DoctorList']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + final doctorsList = list.map((item) => DoctorsListResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: doctorsList, + ); + } 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>> getDoctorProfile(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "ClinicID": clinicID, + "ProjectID": projectID, + "doctorID": doctorId, + "License": true, + "IsRegistered": true, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_DOCTOR_PROFILE, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final doctorProfileList = response['DoctorProfileList']; + if (doctorProfileList == null || doctorProfileList.isEmpty) { + // throw Exception("lab list is empty"); + onError!("Doctor profile not found"); + } + + final doctorsList = DoctorsProfileResponseModel.fromJson(doctorProfileList[0]); - if (result != null && result is Map && result['success'] != null && result['success'] != false) { - return Right(result); - } else { - loggerService.errorLogs(result.toString()); - return Left(ServerFailure(result.toString())); - } + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: doctorsList, + ); + } 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) { - loggerService.errorLogs(e.toString()); - return Left(ServerFailure(e.toString())); + return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}) { + throw UnimplementedError(); + } } diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 8f096dd..dbe1439 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -1,17 +1,159 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/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/services/error_handler_service.dart'; class BookAppointmentsViewModel extends ChangeNotifier { int selectedTabIndex = 0; + bool isClinicsListLoading = false; + bool isDoctorsListLoading = false; + bool isDoctorProfileLoading = false; + + List clinicsList = []; + List _filteredClinicsList = []; + + List get filteredClinicsList => _filteredClinicsList; + + List doctorsList = []; + + GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(); + DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); + + late DoctorsProfileResponseModel doctorsProfileResponseModel; + BookAppointmentsRepo bookAppointmentsRepo; ErrorHandlerService errorHandlerService; BookAppointmentsViewModel({required this.bookAppointmentsRepo, required this.errorHandlerService}); + void initializeFilteredList() { + _filteredClinicsList = List.from(clinicsList); + notifyListeners(); + } + + void filterClinics(String? query) { + if (query!.isEmpty) { + _filteredClinicsList = List.from(clinicsList); + } else { + _filteredClinicsList = clinicsList.where((clinic) => clinic.clinicDescription?.toLowerCase().contains(query!.toLowerCase()) ?? false).toList(); + } + notifyListeners(); + } + + initBookAppointmentViewModel() { + isClinicsListLoading = true; + isDoctorsListLoading = true; + isDoctorProfileLoading = true; + clinicsList.clear(); + doctorsList.clear(); + notifyListeners(); + } + + setIsDoctorsListLoading(bool value) { + if (value) { + doctorsList.clear(); + } + isDoctorsListLoading = value; + notifyListeners(); + } + + setIsClinicsListLoading(bool value) { + if (value) { + clinicsList.clear(); + } + isClinicsListLoading = value; + notifyListeners(); + } + + setSelectedClinic(GetClinicsListResponseModel clinic) { + selectedClinic = clinic; + notifyListeners(); + } + + setSelectedDoctor(DoctorsListResponseModel doctor) { + selectedDoctor = doctor; + notifyListeners(); + } + + setIsDoctorProfileLoading(bool value) { + isDoctorProfileLoading = value; + notifyListeners(); + } + + setDoctorsProfile(DoctorsProfileResponseModel profile) { + doctorsProfileResponseModel = profile; + notifyListeners(); + } + void onTabChanged(int index) { selectedTabIndex = index; notifyListeners(); } + + Future getClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await bookAppointmentsRepo.getClinics(); + + 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(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + //TODO: Make the API dynamic with parameters for ProjectID, isNearest, languageID, doctorId, doctorName + Future getDoctorsList( + {int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID!, 0, isNearest, doctorId, doctorName); + + 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) { + doctorsList = apiResponse.data!; + isDoctorsListLoading = false; + initializeFilteredList(); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getDoctorProfile({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await bookAppointmentsRepo.getDoctorProfile(selectedDoctor.clinicID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, onError: onError); + + result.fold( + (failure) async {}, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage ?? "Unknown error occurred"); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + doctorsProfileResponseModel = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/book_appointments/models/doctor_profile_response_model.dart b/lib/features/book_appointments/models/doctor_profile_response_model.dart new file mode 100644 index 0000000..67e6ffa --- /dev/null +++ b/lib/features/book_appointments/models/doctor_profile_response_model.dart @@ -0,0 +1,196 @@ +class DoctorsProfileResponseModel { + int? doctorID; + String? doctorName; + dynamic doctorNameN; + int? clinicID; + String? clinicDescription; + dynamic clinicDescriptionN; + dynamic licenseExpiry; + int? employmentType; + dynamic setupID; + int? projectID; + String? projectName; + String? nationalityID; + String? nationalityName; + dynamic nationalityNameN; + int? gender; + String? genderDescription; + dynamic genderDescriptionN; + dynamic doctorTitle; + dynamic projectNameN; + bool? isAllowWaitList; + String? titleDescription; + dynamic titleDescriptionN; + dynamic isRegistered; + dynamic isDoctorDummy; + bool? isActive; + dynamic isDoctorAppointmentDisplayed; + bool? doctorClinicActive; + dynamic isbookingAllowed; + String? doctorCases; + dynamic doctorPicture; + String? doctorProfileInfo; + List? specialty; + num? actualDoctorRate; + String? consultationFee; + double? decimalDoctorRate; + String? doctorImageURL; + String? doctorMobileNumber; + num? doctorRate; + num? doctorStarsRate; + String? doctorTitleForProfile; + bool? isAppointmentAllowed; + bool? isDoctorHasPrePostImages; + String? nationalityFlagURL; + num? noOfPatientsRate; + String? qR; + int? serviceID; + + DoctorsProfileResponseModel( + {this.doctorID, + this.doctorName, + this.doctorNameN, + this.clinicID, + this.clinicDescription, + this.clinicDescriptionN, + this.licenseExpiry, + this.employmentType, + this.setupID, + this.projectID, + this.projectName, + this.nationalityID, + this.nationalityName, + this.nationalityNameN, + this.gender, + this.genderDescription, + this.genderDescriptionN, + this.doctorTitle, + this.projectNameN, + this.isAllowWaitList, + this.titleDescription, + this.titleDescriptionN, + this.isRegistered, + this.isDoctorDummy, + this.isActive, + this.isDoctorAppointmentDisplayed, + this.doctorClinicActive, + this.isbookingAllowed, + this.doctorCases, + this.doctorPicture, + this.doctorProfileInfo, + this.specialty, + this.actualDoctorRate, + this.consultationFee, + this.decimalDoctorRate, + this.doctorImageURL, + this.doctorMobileNumber, + this.doctorRate, + this.doctorStarsRate, + this.doctorTitleForProfile, + this.isAppointmentAllowed, + this.isDoctorHasPrePostImages, + this.nationalityFlagURL, + this.noOfPatientsRate, + this.qR, + this.serviceID}); + + DoctorsProfileResponseModel.fromJson(Map json) { + doctorID = json['DoctorID']; + doctorName = json['DoctorName']; + doctorNameN = json['DoctorNameN']; + clinicID = json['ClinicID']; + clinicDescription = json['ClinicDescription']; + clinicDescriptionN = json['ClinicDescriptionN']; + licenseExpiry = json['LicenseExpiry']; + employmentType = json['EmploymentType']; + setupID = json['SetupID']; + projectID = json['ProjectID']; + projectName = json['ProjectName']; + nationalityID = json['NationalityID']; + nationalityName = json['NationalityName']; + nationalityNameN = json['NationalityNameN']; + gender = json['Gender']; + genderDescription = json['Gender_Description']; + genderDescriptionN = json['Gender_DescriptionN']; + doctorTitle = json['DoctorTitle']; + projectNameN = json['ProjectNameN']; + isAllowWaitList = json['IsAllowWaitList']; + titleDescription = json['Title_Description']; + titleDescriptionN = json['Title_DescriptionN']; + isRegistered = json['IsRegistered']; + isDoctorDummy = json['IsDoctorDummy']; + isActive = json['IsActive']; + isDoctorAppointmentDisplayed = json['IsDoctorAppointmentDisplayed']; + doctorClinicActive = json['DoctorClinicActive']; + isbookingAllowed = json['IsbookingAllowed']; + doctorCases = json['DoctorCases']; + doctorPicture = json['DoctorPicture']; + doctorProfileInfo = json['DoctorProfileInfo']; + specialty = json['Specialty'].cast(); + actualDoctorRate = json['ActualDoctorRate']; + consultationFee = json['ConsultationFee']; + decimalDoctorRate = json['DecimalDoctorRate']; + doctorImageURL = json['DoctorImageURL']; + doctorMobileNumber = json['DoctorMobileNumber']; + doctorRate = json['DoctorRate']; + doctorStarsRate = json['DoctorStarsRate']; + doctorTitleForProfile = json['DoctorTitleForProfile']; + isAppointmentAllowed = json['IsAppointmentAllowed']; + isDoctorHasPrePostImages = json['IsDoctorHasPrePostImages']; + nationalityFlagURL = json['NationalityFlagURL']; + noOfPatientsRate = json['NoOfPatientsRate']; + qR = json['QR']; + serviceID = json['ServiceID']; + } + + Map toJson() { + final Map data = new Map(); + data['DoctorID'] = this.doctorID; + data['DoctorName'] = this.doctorName; + data['DoctorNameN'] = this.doctorNameN; + data['ClinicID'] = this.clinicID; + data['ClinicDescription'] = this.clinicDescription; + data['ClinicDescriptionN'] = this.clinicDescriptionN; + data['LicenseExpiry'] = this.licenseExpiry; + data['EmploymentType'] = this.employmentType; + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['ProjectName'] = this.projectName; + data['NationalityID'] = this.nationalityID; + data['NationalityName'] = this.nationalityName; + data['NationalityNameN'] = this.nationalityNameN; + data['Gender'] = this.gender; + data['Gender_Description'] = this.genderDescription; + data['Gender_DescriptionN'] = this.genderDescriptionN; + data['DoctorTitle'] = this.doctorTitle; + data['ProjectNameN'] = this.projectNameN; + data['IsAllowWaitList'] = this.isAllowWaitList; + data['Title_Description'] = this.titleDescription; + data['Title_DescriptionN'] = this.titleDescriptionN; + data['IsRegistered'] = this.isRegistered; + data['IsDoctorDummy'] = this.isDoctorDummy; + data['IsActive'] = this.isActive; + data['IsDoctorAppointmentDisplayed'] = this.isDoctorAppointmentDisplayed; + data['DoctorClinicActive'] = this.doctorClinicActive; + data['IsbookingAllowed'] = this.isbookingAllowed; + data['DoctorCases'] = this.doctorCases; + data['DoctorPicture'] = this.doctorPicture; + data['DoctorProfileInfo'] = this.doctorProfileInfo; + data['Specialty'] = this.specialty; + data['ActualDoctorRate'] = this.actualDoctorRate; + data['ConsultationFee'] = this.consultationFee; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorMobileNumber'] = this.doctorMobileNumber; + data['DoctorRate'] = this.doctorRate; + data['DoctorStarsRate'] = this.doctorStarsRate; + data['DoctorTitleForProfile'] = this.doctorTitleForProfile; + data['IsAppointmentAllowed'] = this.isAppointmentAllowed; + data['IsDoctorHasPrePostImages'] = this.isDoctorHasPrePostImages; + data['NationalityFlagURL'] = this.nationalityFlagURL; + data['NoOfPatientsRate'] = this.noOfPatientsRate; + data['QR'] = this.qR; + data['ServiceID'] = this.serviceID; + return data; + } +} diff --git a/lib/features/book_appointments/models/resp_models/doctors_list_response_model.dart b/lib/features/book_appointments/models/resp_models/doctors_list_response_model.dart new file mode 100644 index 0000000..4cd03a3 --- /dev/null +++ b/lib/features/book_appointments/models/resp_models/doctors_list_response_model.dart @@ -0,0 +1,252 @@ +class DoctorsListResponseModel { + int? clinicID; + String? clinicName; + String? clinicNameN; + String? doctorTitle; + int? iD; + String? name; + int? projectID; + String? projectName; + num? actualDoctorRate; + int? clinicRoomNo; + dynamic date; + dynamic dayName; + num? decimalDoctorRate; + dynamic doctorAvailability; + int? doctorID; + String? doctorImageURL; + String? doctorMobileNumber; + dynamic doctorProfile; + dynamic doctorProfileInfo; + num? doctorRate; + num? doctorStarsRate; + int? employmentType; + int? gender; + String? genderDescription; + int? hISRegionId; + bool? isActive; + bool? isAllowWaitList; + bool? isAppointmentAllowed; + bool? isDoctorAllowVedioCall; + bool? isDoctorDummy; + bool? isDoctorHasPrePostImages; + bool? isHMC; + bool? isHmg; + bool? isLiveCare; + String? latitude; + String? longitude; + String? nationalityFlagURL; + String? nationalityID; + String? nationalityName; + dynamic nearestFreeSlot; + int? noOfFreeSlotsAvailable; + num? noOfPatientsRate; + dynamic originalClinicID; + int? personRate; + num? projectDistanceInKiloMeters; + String? projectNameBottom; + String? projectNameTop; + String? qR; + dynamic qRString; + num? rateNumber; + String? regionName; + dynamic regionNameN; + dynamic serviceID; + String? setupID; + List? speciality; + List? specialityN; + int? transactionType; + int? virtualEmploymentType; + dynamic workingHours; + dynamic vida3Id; + + DoctorsListResponseModel( + {this.clinicID, + this.clinicName, + this.clinicNameN, + this.doctorTitle, + this.iD, + this.name, + this.projectID, + this.projectName, + this.actualDoctorRate, + this.clinicRoomNo, + this.date, + this.dayName, + this.decimalDoctorRate, + this.doctorAvailability, + this.doctorID, + this.doctorImageURL, + this.doctorMobileNumber, + this.doctorProfile, + this.doctorProfileInfo, + this.doctorRate, + this.doctorStarsRate, + this.employmentType, + this.gender, + this.genderDescription, + this.hISRegionId, + this.isActive, + this.isAllowWaitList, + this.isAppointmentAllowed, + this.isDoctorAllowVedioCall, + this.isDoctorDummy, + this.isDoctorHasPrePostImages, + this.isHMC, + this.isHmg, + this.isLiveCare, + this.latitude, + this.longitude, + this.nationalityFlagURL, + this.nationalityID, + this.nationalityName, + this.nearestFreeSlot, + this.noOfFreeSlotsAvailable, + this.noOfPatientsRate, + this.originalClinicID, + this.personRate, + this.projectDistanceInKiloMeters, + this.projectNameBottom, + this.projectNameTop, + this.qR, + this.qRString, + this.rateNumber, + this.regionName, + this.regionNameN, + this.serviceID, + this.setupID, + this.speciality, + this.specialityN, + this.transactionType, + this.virtualEmploymentType, + this.workingHours, + this.vida3Id}); + + DoctorsListResponseModel.fromJson(Map json) { + clinicID = json['ClinicID']; + clinicName = json['ClinicName']; + clinicNameN = json['ClinicNameN']; + doctorTitle = json['DoctorTitle']; + iD = json['ID']; + name = json['Name']; + projectID = json['ProjectID']; + projectName = json['ProjectName']; + actualDoctorRate = json['ActualDoctorRate']; + clinicRoomNo = json['ClinicRoomNo']; + date = json['Date']; + dayName = json['DayName']; + decimalDoctorRate = json['DecimalDoctorRate']; + doctorAvailability = json['DoctorAvailability']; + doctorID = json['DoctorID']; + doctorImageURL = json['DoctorImageURL']; + doctorMobileNumber = json['DoctorMobileNumber']; + doctorProfile = json['DoctorProfile']; + doctorProfileInfo = json['DoctorProfileInfo']; + doctorRate = json['DoctorRate']; + doctorStarsRate = json['DoctorStarsRate']; + employmentType = json['EmploymentType']; + gender = json['Gender']; + genderDescription = json['GenderDescription']; + hISRegionId = json['HISRegionId']; + isActive = json['IsActive']; + isAllowWaitList = json['IsAllowWaitList']; + isAppointmentAllowed = json['IsAppointmentAllowed']; + isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall']; + isDoctorDummy = json['IsDoctorDummy']; + isDoctorHasPrePostImages = json['IsDoctorHasPrePostImages']; + isHMC = json['IsHMC']; + isHmg = json['IsHmg']; + isLiveCare = json['IsLiveCare']; + latitude = json['Latitude']; + longitude = json['Longitude']; + nationalityFlagURL = json['NationalityFlagURL']; + nationalityID = json['NationalityID']; + nationalityName = json['NationalityName']; + nearestFreeSlot = json['NearestFreeSlot']; + noOfFreeSlotsAvailable = json['NoOfFreeSlotsAvailable']; + noOfPatientsRate = json['NoOfPatientsRate']; + originalClinicID = json['OriginalClinicID']; + personRate = json['PersonRate']; + projectDistanceInKiloMeters = json['ProjectDistanceInKiloMeters']; + projectNameBottom = json['ProjectNameBottom']; + projectNameTop = json['ProjectNameTop']; + qR = json['QR']; + qRString = json['QRString']; + rateNumber = json['RateNumber']; + regionName = json['RegionName']; + regionNameN = json['RegionNameN']; + serviceID = json['ServiceID']; + setupID = json['SetupID']; + speciality = json['Speciality'].cast(); + specialityN = json['SpecialityN'].cast(); + transactionType = json['TransactionType']; + virtualEmploymentType = json['VirtualEmploymentType']; + workingHours = json['WorkingHours']; + vida3Id = json['vida3Id']; + } + + Map toJson() { + final Map data = new Map(); + data['ClinicID'] = this.clinicID; + data['ClinicName'] = this.clinicName; + data['ClinicNameN'] = this.clinicNameN; + 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['DecimalDoctorRate'] = this.decimalDoctorRate; + data['DoctorAvailability'] = this.doctorAvailability; + data['DoctorID'] = this.doctorID; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorMobileNumber'] = this.doctorMobileNumber; + data['DoctorProfile'] = this.doctorProfile; + data['DoctorProfileInfo'] = this.doctorProfileInfo; + data['DoctorRate'] = this.doctorRate; + data['DoctorStarsRate'] = this.doctorStarsRate; + data['EmploymentType'] = this.employmentType; + data['Gender'] = this.gender; + data['GenderDescription'] = this.genderDescription; + data['HISRegionId'] = this.hISRegionId; + data['IsActive'] = this.isActive; + data['IsAllowWaitList'] = this.isAllowWaitList; + data['IsAppointmentAllowed'] = this.isAppointmentAllowed; + data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall; + data['IsDoctorDummy'] = this.isDoctorDummy; + data['IsDoctorHasPrePostImages'] = this.isDoctorHasPrePostImages; + data['IsHMC'] = this.isHMC; + data['IsHmg'] = this.isHmg; + data['IsLiveCare'] = this.isLiveCare; + 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['NoOfFreeSlotsAvailable'] = this.noOfFreeSlotsAvailable; + data['NoOfPatientsRate'] = this.noOfPatientsRate; + data['OriginalClinicID'] = this.originalClinicID; + data['PersonRate'] = this.personRate; + data['ProjectDistanceInKiloMeters'] = this.projectDistanceInKiloMeters; + data['ProjectNameBottom'] = this.projectNameBottom; + data['ProjectNameTop'] = this.projectNameTop; + data['QR'] = this.qR; + data['QRString'] = this.qRString; + data['RateNumber'] = this.rateNumber; + data['RegionName'] = this.regionName; + data['RegionNameN'] = this.regionNameN; + data['ServiceID'] = this.serviceID; + data['SetupID'] = this.setupID; + data['Speciality'] = this.speciality; + data['SpecialityN'] = this.specialityN; + data['TransactionType'] = this.transactionType; + data['VirtualEmploymentType'] = this.virtualEmploymentType; + data['WorkingHours'] = this.workingHours; + data['vida3Id'] = this.vida3Id; + return data; + } +} diff --git a/lib/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart b/lib/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart new file mode 100644 index 0000000..0b0fa13 --- /dev/null +++ b/lib/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart @@ -0,0 +1,36 @@ +class GetClinicsListResponseModel { + int? clinicID; + String? clinicDescription; + String? clinicDescriptionN; + int? age; + int? gender; + bool? isLiveCareClinicAndOnline; + int? liveCareClinicID; + int? liveCareServiceID; + + GetClinicsListResponseModel({this.clinicID, this.clinicDescription, this.clinicDescriptionN, this.age, this.gender, this.isLiveCareClinicAndOnline, this.liveCareClinicID, this.liveCareServiceID}); + + GetClinicsListResponseModel.fromJson(Map json) { + clinicID = json['ClinicID']; + clinicDescription = json['ClinicDescription']; + clinicDescriptionN = json['ClinicDescriptionN']; + age = json['Age']; + gender = json['Gender']; + isLiveCareClinicAndOnline = json['IsLiveCareClinicAndOnline']; + liveCareClinicID = json['LiveCareClinicID']; + liveCareServiceID = json['LiveCareServiceID']; + } + + Map toJson() { + final Map data = new Map(); + data['ClinicID'] = this.clinicID; + data['ClinicDescription'] = this.clinicDescription; + data['ClinicDescriptionN'] = this.clinicDescriptionN; + data['Age'] = this.age; + data['Gender'] = this.gender; + data['IsLiveCareClinicAndOnline'] = this.isLiveCareClinicAndOnline; + data['LiveCareClinicID'] = this.liveCareClinicID; + data['LiveCareServiceID'] = this.liveCareServiceID; + return data; + } +} diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 0f9a325..6613320 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -9,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +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'; @@ -25,14 +28,25 @@ class BookAppointmentPage extends StatefulWidget { class _BookAppointmentPageState extends State { late AppState appState; + late BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + void initState() { + scheduleMicrotask(() { + bookAppointmentsViewModel.initBookAppointmentViewModel(); + }); + super.initState(); + } @override Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.bookAppo.tr(context: context), + isLeading: false, child: SingleChildScrollView( child: Consumer(builder: (context, bookAppointmentsVM, child) { return Column( @@ -95,6 +109,7 @@ class _BookAppointmentPageState extends State { Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), ], ).onPress(() { + bookAppointmentsViewModel.setIsClinicsListLoading(true); Navigator.of(context).push( FadePage( page: SelectClinicPage(), @@ -122,7 +137,13 @@ class _BookAppointmentPageState extends State { ), Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), ], - ).onPress(() {}), + ).onPress(() { + Navigator.of(context).push( + FadePage( + page: SearchDoctorByName(), + ), + ); + }), SizedBox(height: 16.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), SizedBox(height: 16.h), diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart new file mode 100644 index 0000000..0bb1098 --- /dev/null +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -0,0 +1,129 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:provider/provider.dart'; + +class DoctorProfilePage extends StatelessWidget { + DoctorProfilePage({super.key}); + + late AppState appState; + late BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); + appState = getIt.get(); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Doctor Profile".needTranslation, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Image.network( + bookAppointmentsViewModel.doctorsProfileResponseModel.doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Column( + children: [ + ("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}") + .toString() + .toText28(isBold: true), + (bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty ? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first : "") + .toString() + .toText18(weight: FontWeight.w500, color: AppColors.primaryRedColor), + ], + ), + ], + ), + Image.network( + bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL!, + width: 32.h, + height: 32.h, + fit: BoxFit.fill, + ), + ], + ), + SizedBox(height: 12.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.rating_icon, + iconColor: AppColors.ratingColorYellow, + labelText: "Rating: ${bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate}".needTranslation, + ), + AppCustomChipWidget( + icon: AppAssets.rating_icon, + iconColor: AppColors.ratingColorYellow, + labelText: "Reviews: ${bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate}".needTranslation, + ), + ], + ), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + "Biography".toText14(weight: FontWeight.w600, color: AppColors.textColor), + bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo!.toText12(fontWeight: FontWeight.w600, color: AppColors.greyTextColor), + ], + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: "View available appointments".needTranslation, + onPressed: () { + showCommonBottomSheetWithoutHeight(context, title: "".needTranslation, child: Utils.getLoadingWidget(), callBackFunc: () {}, isFullScreen: false); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.calendar, + iconColor: AppColors.whiteColor, + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart new file mode 100644 index 0000000..6180932 --- /dev/null +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -0,0 +1,76 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.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/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; + +class SearchDoctorByName extends StatefulWidget { + const SearchDoctorByName({super.key}); + + @override + State createState() => _SearchDoctorByNameState(); +} + +class _SearchDoctorByNameState extends State { + TextEditingController searchEditingController = TextEditingController(); + + FocusNode textFocusNode = FocusNode(); + + late AppState appState; + late BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "Choose Doctor".needTranslation, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + children: [ + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: LocaleKeys.doctorName.tr(context: context), + controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + focusNode: textFocusNode, + suffix: searchEditingController.text.isNotEmpty + ? GestureDetector( + onTap: () { + searchEditingController.clear(); + bookAppointmentsViewModel.filterClinics(""); + textFocusNode.unfocus(); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), + ) + : null, + onChange: (value) { + bookAppointmentsViewModel.filterClinics(value!); + }, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 496f7d5..214d4ad 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -1,8 +1,26 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_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/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/select_livecare_clinic_page.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/clinic_card.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/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; +import 'package:provider/provider.dart'; class SelectClinicPage extends StatefulWidget { const SelectClinicPage({super.key}); @@ -12,18 +30,127 @@ class SelectClinicPage extends StatefulWidget { } class _SelectClinicPageState extends State { + TextEditingController searchEditingController = TextEditingController(); + FocusNode textFocusNode = FocusNode(); + + late AppState appState; + late BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + void initState() { + scheduleMicrotask(() { + bookAppointmentsViewModel.getClinics(); + }); + super.initState(); + } + + @override + void dispose() { + textFocusNode.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); + appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.selectClinic.tr(context: context), child: SingleChildScrollView( - child: Column( - children: [], + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Consumer(builder: (context, bookAppointmentsVM, child) { + return Column( + children: [ + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: LocaleKeys.clinicName.tr(context: context), + controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + focusNode: textFocusNode, + suffix: searchEditingController.text.isNotEmpty + ? GestureDetector( + onTap: () { + searchEditingController.clear(); + bookAppointmentsViewModel.filterClinics(""); + textFocusNode.unfocus(); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), + ) + : null, + onChange: (value) { + bookAppointmentsViewModel.filterClinics(value!); + }, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length, + itemBuilder: (context, index) { + return bookAppointmentsVM.isClinicsListLoading + ? ClinicCard( + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: ClinicCard( + clinicsListResponseModel: bookAppointmentsVM.filteredClinicsList[index], + isLoading: bookAppointmentsVM.isClinicsListLoading, + ).onPress(() { + onClinicSelected(bookAppointmentsVM.filteredClinicsList[index]); + }), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + }), ), ), ), ); } + + void onClinicSelected(GetClinicsListResponseModel clinic) { + bookAppointmentsViewModel.setSelectedClinic(clinic); + bookAppointmentsViewModel.setIsDoctorsListLoading(true); + if (clinic.isLiveCareClinicAndOnline ?? false) { + Navigator.of(context).push( + FadePage( + page: SelectLivecareClinicPage(), + ), + ); + } else { + Navigator.of(context).push( + FadePage( + page: SelectDoctorPage(), + ), + ); + } + } } diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart new file mode 100644 index 0000000..0986c29 --- /dev/null +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -0,0 +1,155 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_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/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_card.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'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; +import 'package:provider/provider.dart'; + +class SelectDoctorPage extends StatefulWidget { + SelectDoctorPage({super.key}); + + @override + State createState() => _SelectDoctorPageState(); +} + +class _SelectDoctorPageState extends State { + TextEditingController searchEditingController = TextEditingController(); + + FocusNode textFocusNode = FocusNode(); + + late AppState appState; + late BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + void initState() { + scheduleMicrotask(() { + bookAppointmentsViewModel.getDoctorsList(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); + appState = getIt.get(); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "Choose Doctor".needTranslation, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Consumer(builder: (context, bookAppointmentsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // TODO: Implement doctor filter functionality + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: LocaleKeys.doctorName.tr(context: context), + controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + focusNode: textFocusNode, + suffix: searchEditingController.text.isNotEmpty + ? GestureDetector( + onTap: () { + searchEditingController.clear(); + bookAppointmentsViewModel.filterClinics(""); + textFocusNode.unfocus(); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), + ) + : null, + onChange: (value) { + bookAppointmentsViewModel.filterClinics(value!); + }, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : bookAppointmentsVM.doctorsList.length, + itemBuilder: (context, index) { + return bookAppointmentsVM.isDoctorsListLoading + ? DoctorCard( + doctorsListResponseModel: DoctorsListResponseModel(), + isLoading: true, + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: DoctorCard( + doctorsListResponseModel: bookAppointmentsVM.doctorsList[index], + isLoading: false, + ).onPress(() async { + bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.doctorsList[index]); + // bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel()); + LoaderBottomSheet.showLoader(); + await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) { + LoaderBottomSheet.hideLoader(); + Navigator.of(context).push( + FadePage( + page: DoctorProfilePage(), + ), + ); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + }), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/book_appointment/select_livecare_clinic_page.dart b/lib/presentation/book_appointment/select_livecare_clinic_page.dart new file mode 100644 index 0000000..ef682c3 --- /dev/null +++ b/lib/presentation/book_appointment/select_livecare_clinic_page.dart @@ -0,0 +1,146 @@ +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/presentation/book_appointment/select_doctor_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/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; + +class SelectLivecareClinicPage extends StatelessWidget { + const SelectLivecareClinicPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.livecare.tr(context: context), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + LocaleKeys.livecareModalTop.tr(context: context).toText18(isBold: true), + SizedBox(height: 40.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.h, height: 58.h), + SizedBox(width: 18.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Immediate service".needTranslation.toText18(color: AppColors.textColor, isBold: true), + "No need to wait, you will get medical consultation immediately via video call".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ), + ], + ), + SizedBox(height: 24.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.no_visit_icon, width: 58.h, height: 58.h), + SizedBox(width: 18.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "No visit required".needTranslation.toText18(color: AppColors.textColor, isBold: true), + LocaleKeys.livecarePoint5.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ), + ], + ), + SizedBox(height: 24.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.doctor_contact_icon, width: 58.h, height: 58.h), + SizedBox(width: 18.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Doctor will contact".needTranslation.toText18(color: AppColors.textColor, isBold: true), + "A specialised doctor will contact you and will be able to view your medical history".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ), + ], + ), + SizedBox(height: 24.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.free_med_delivery_icon, width: 58.h, height: 58.h), + SizedBox(width: 18.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Free medicine delivery".needTranslation.toText18(color: AppColors.textColor, isBold: true), + "Offers free medicine delivery for the LiveCare appointment".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ), + ], + ), + ], + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ), + Column( + children: [ + CustomButton( + text: "Yes please, I am in a hurry".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.livecare_book_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + CustomButton( + text: "No, Thanks. I would like a physical visit".needTranslation, + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).push( + FadePage( + page: SelectDoctorPage(), + ), + ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + ], + ), + ], + ), + ); + } +} diff --git a/lib/presentation/book_appointment/widgets/clinic_card.dart b/lib/presentation/book_appointment/widgets/clinic_card.dart new file mode 100644 index 0000000..5cfc624 --- /dev/null +++ b/lib/presentation/book_appointment/widgets/clinic_card.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class ClinicCard extends StatelessWidget { + ClinicCard({super.key, required this.clinicsListResponseModel, required this.isLoading}); + + GetClinicsListResponseModel clinicsListResponseModel; + bool isLoading; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Column( + children: [ + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + "".toText16(isBold: true).toShimmer2(isShow: isLoading), + (clinicsListResponseModel.isLiveCareClinicAndOnline ?? true) + ? Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading) + : SizedBox.shrink(), + ]), + SizedBox(height: 16.h), + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Expanded(child: (isLoading ? "Cardiology" : clinicsListResponseModel.clinicDescription!).toText16(isBold: true).toShimmer2(isShow: isLoading)), + Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor).toShimmer2(isShow: isLoading), + ]), + ], + ), + ); + } +} diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart new file mode 100644 index 0000000..648f5ef --- /dev/null +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -0,0 +1,121 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +class DoctorCard extends StatelessWidget { + DoctorCard({super.key, required this.doctorsListResponseModel, required this.isLoading}); + + DoctorsListResponseModel doctorsListResponseModel; + bool isLoading = false; + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.network( + isLoading + ? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png" + : doctorsListResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png", + width: 63.h, + height: 63.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: isLoading), + SizedBox(width: 8.h), + Expanded( + flex: 9, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.49, + child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}").toString().toText16(isBold: true, maxlines: 1), + ).toShimmer2(isShow: isLoading), + Image.network( + isLoading ? "https://hmgwebservices.com/Images/flag/SYR.png" : doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png", + width: 20.h, + height: 15.h, + fit: BoxFit.fill, + ).toShimmer2(isShow: isLoading), + ], + ), + SizedBox(height: 2.h), + (isLoading + ? "Consultant Cardiologist" + : doctorsListResponseModel.speciality!.isNotEmpty + ? doctorsListResponseModel.speciality!.first + : "") + .toString() + .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1) + .toShimmer2(isShow: isLoading), + ], + ), + ), + Expanded( + flex: 1, + child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading), + ), + ], + ), + SizedBox(height: 12.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: "Clinic: ${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}".needTranslation, + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: "Branch: ${isLoading ? "Olaya Hospital" : doctorsListResponseModel.projectName}".needTranslation, + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + icon: AppAssets.rating_icon, + iconColor: AppColors.ratingColorYellow, + labelText: "Rating: ${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate}".needTranslation, + ).toShimmer2(isShow: isLoading), + ], + ), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () {}, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 15.h, + ).toShimmer2(isShow: isLoading), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 26e5147..50d8032 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; @@ -89,24 +90,26 @@ class _LandingPageState extends State { children: [ appState.isAuthenticated ? WelcomeWidget( - onTap: () {}, - name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), - imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, - ) + onTap: () {}, + name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), + imageUrl: appState + .getAuthenticatedUser() + ?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, + ) : CustomButton( - text: LocaleKeys.loginOrRegister.tr(context: context), - onPressed: () async { - await authVM.onLoginPressed(); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50, - ), + text: LocaleKeys.loginOrRegister.tr(context: context), + onPressed: () async { + await authVM.onLoginPressed(); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50, + ), Row( children: [ Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 20, width: 20).onPress(() { @@ -141,153 +144,156 @@ class _LandingPageState extends State { SizedBox(height: 16.h), appState.isAuthenticated ? Column( - children: [ - Container( - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(12.h), - child: Column( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), - SizedBox(height: 12.h), - "You do not have any upcoming appointment. Please book an appointment".toText12(isCenter: true), - SizedBox(height: 12.h), - CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (BuildContext context) => LandingPage()), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40, - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, + children: [ + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(12.h), + child: Column( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), + SizedBox(height: 12.h), + "You do not have any upcoming appointment. Please book an appointment".toText12(isCenter: true), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + Navigator.of(context) + .push( + FadePage( + page: BookAppointmentPage(), ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 12.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "Quick Links".toText16(isBold: true), - Row( - children: [ - "View medical file".toText12(color: AppColors.primaryRedColor), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push( - FadePage( - page: MedicalFilePage(), - ), - ); - }), - SizedBox(height: 12.h), - Container( - height: 127.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, + ); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40, + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - Expanded( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getLoggedInServiceCardsList.length, - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - icon: LandingPageData.getLoggedInServiceCardsList[index].icon, - title: LandingPageData.getLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor, - textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, - serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, - ), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 12.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Quick Links".toText16(isBold: true), + Row( + children: [ + "View medical file".toText12(color: AppColors.primaryRedColor), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ), + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push( + FadePage( + page: MedicalFilePage(), + ), + ); + }), + SizedBox(height: 12.h), + Container( + height: 127.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getLoggedInServiceCardsList.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + icon: LandingPageData.getLoggedInServiceCardsList[index].icon, + title: LandingPageData.getLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor, + textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, + serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, + ), + ), ), - ), - ], + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 0.width, ), ), - ).paddingSymmetrical(24.h, 0.h), - ], - ) - : Container( - height: 127.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, + ], ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - Expanded( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, - title: LandingPageData.getNotLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor, - textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, - ), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, + ), + ).paddingSymmetrical(24.h, 0.h), + ], + ) + : Container( + height: 127.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, + title: LandingPageData.getNotLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor, + textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, + ), + ), ), - ), - ], + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 0.width, ), ), - ).paddingSymmetrical(24.h, 0.h), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index bdce393..8566aa8 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -24,7 +24,7 @@ class _LandingNavigationState extends State { children: [ const LandingPage(), MedicalFilePage(), - const BookAppointmentPage(), + BookAppointmentPage(), const LandingPage(), const LandingPage(), ], diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 48193e8..f0e7587 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -60,9 +60,11 @@ class _MedicalFilePageState extends State { @override void initState() { scheduleMicrotask(() { - insuranceViewModel.initInsuranceProvider(); - medicalFileViewModel.setIsPatientSickLeaveListLoading(true); - medicalFileViewModel.getPatientSickLeaveList(); + if (appState.isAuthenticated) { + insuranceViewModel.initInsuranceProvider(); + medicalFileViewModel.setIsPatientSickLeaveListLoading(true); + medicalFileViewModel.getPatientSickLeaveList(); + } }); super.initState(); } diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index 44e1327..0fb9223 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -40,6 +40,7 @@ class TextInputWidget extends StatelessWidget { final SelectionTypeEnum? selectionType; final num? fontSize; final bool? isWalletAmountInput; + final Widget? suffix; // final List countryList; // final Function(Country)? onCountryChange; @@ -69,6 +70,7 @@ class TextInputWidget extends StatelessWidget { this.selectionType, this.fontSize = 14, this.isWalletAmountInput = false, + this.suffix, // this.countryList = const [], // this.onCountryChange, }); @@ -238,6 +240,7 @@ class TextInputWidget extends StatelessWidget { hintText: hintText, hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.2), prefixIconConstraints: BoxConstraints(minWidth: 45.h), + suffixIconConstraints: BoxConstraints(maxHeight: 20.h), prefixIcon: prefix == null ? null : Text( @@ -250,6 +253,12 @@ class TextInputWidget extends StatelessWidget { letterSpacing: -0.2, ), ), + suffixIcon: suffix != null + ? Padding( + padding: EdgeInsets.only(right: 0.h), + child: suffix, + ) + : null, contentPadding: EdgeInsets.zero, border: InputBorder.none, focusedBorder: InputBorder.none, diff --git a/lib/widgets/loader/bottomsheet_loader.dart b/lib/widgets/loader/bottomsheet_loader.dart index 57dfa01..aa97fdf 100644 --- a/lib/widgets/loader/bottomsheet_loader.dart +++ b/lib/widgets/loader/bottomsheet_loader.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -17,7 +18,7 @@ class LoaderBottomSheet { showModalBottomSheet( context: _navService.navigatorKey.currentContext!, - isDismissible: ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? true : false, + isDismissible: (ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat || kDebugMode) ? true : false, enableDrag: false, backgroundColor: Colors.transparent, builder: (_) {