From f1555c76a37ee42cf4f07c3fd679ec335bece5ea Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Tue, 31 Mar 2026 10:48:25 +0300 Subject: [PATCH] Invoices Filter --- assets/langs/ar-SA.json | 5 + assets/langs/en-US.json | 5 + .../authentication/authentication_repo.dart | 6 + .../my_invoices/my_invoices_view_model.dart | 141 ++++++++++- lib/generated/locale_keys.g.dart | 5 + .../my_invoices/my_invoices_list.dart | 239 ++++++++++++++---- .../widgets/invoice_filter_bottom_sheet.dart | 133 ++++++++++ 7 files changed, 484 insertions(+), 50 deletions(-) create mode 100644 lib/presentation/my_invoices/widgets/invoice_filter_bottom_sheet.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index b78b8545..765abc78 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -752,6 +752,11 @@ "erConsultation": "تتيح لك هذه الخدمة إجراء استشارة افتراضية عبر الإنترنت عبر مكالمة فيديو مباشرة مع الطبيب من أي مكان وفي أي وقت.", "myInvoice": "القائمة", "invoiceList": "فواتيري", + "allInvoices": "كل الفواتير", + "hospitals": "المستشفيات", + "clinics": "العيادات", + "doctors": "الأطباء", + "selectDoctor": "اختر الطبيب", "thisItemIsNotAvailable": "هذا العنصر غير متوفر", "beforeAfterImages": "صور قبل وبعد", "clinicAcceptLivecare": "لا حاجة للانتظار أو الزيارة يمكنك الآن الحصول على استشارة طبية عبر مكالمة فيديو (لايف كير) في اسم العيادة وسيتصل بك الطبيب على الفور", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index cbea7b94..b0dd38ad 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -744,6 +744,11 @@ "erConsultation": "This service allows you to make an online virtual consultation via video call directly with the doctor from anywhere at any time.", "myInvoice": "List", "invoiceList": "My Invoice", + "allInvoices": "All Invoices", + "hospitals": "Hospitals", + "clinics": "Clinics", + "doctors": "Doctors", + "selectDoctor": "Select Doctor", "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", diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index f3b14c67..4fc8883f 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -315,7 +315,13 @@ class AuthenticationRepoImp implements AuthenticationRepo { } if (appState.getSuperUserID == responseID) { + // switchRequest['LoginType'] = 0; switchRequest['PatientIdentificationID'] = ""; + // switchRequest["PatientID"] = responseID; + // switchRequest["SuperUser"] = responseID; + // switchRequest["PatientID"] = appState.getAuthenticatedUser()?.patientId ?? 0; + + // switchRequest["PatientMobileNumber"] = newRequest.patientMobileNumber?.toString().startsWith('0') == true ? newRequest.patientMobileNumber.toString() : '0${newRequest.patientMobileNumber}'; switchRequest.removeWhere((key, value) => ['NationalID', 'isDentalAllowedBackend', 'ProjectOutSA', 'ForRegisteration'].contains(key)); } } diff --git a/lib/features/my_invoices/my_invoices_view_model.dart b/lib/features/my_invoices/my_invoices_view_model.dart index f3c716ae..d51ea086 100644 --- a/lib/features/my_invoices/my_invoices_view_model.dart +++ b/lib/features/my_invoices/my_invoices_view_model.dart @@ -5,6 +5,8 @@ import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +enum InvoiceFilterType { all, hospital, clinic, doctor } + class MyInvoicesViewModel extends ChangeNotifier { bool isInvoicesListLoading = false; bool isInvoiceDetailsLoading = false; @@ -14,13 +16,23 @@ class MyInvoicesViewModel extends ChangeNotifier { NavigationService navServices; List allInvoicesList = []; + List _originalInvoicesList = []; + InvoiceFilterType currentFilter = InvoiceFilterType.all; late GetInvoiceDetailsResponseModel invoiceDetailsResponseModel; + // Filter bottom sheet properties + List filterDisplayList = []; + List _filterOriginalList = []; + TextEditingController filterSearchController = TextEditingController(); + String? selectedFilterItem; + MyInvoicesViewModel({required this.myInvoicesRepo, required this.errorHandlerService, required this.navServices}); setInvoicesListLoading() { isInvoicesListLoading = true; allInvoicesList.clear(); + _originalInvoicesList.clear(); + currentFilter = InvoiceFilterType.all; notifyListeners(); } @@ -41,9 +53,10 @@ class MyInvoicesViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { - allInvoicesList = apiResponse.data!; + _originalInvoicesList = apiResponse.data!; + _originalInvoicesList.sort((a, b) => b.appointmentDate!.compareTo(a.appointmentDate!)); + allInvoicesList = List.from(_originalInvoicesList); isInvoicesListLoading = false; - allInvoicesList.sort((a, b) => b.appointmentDate!.compareTo(a.appointmentDate!)); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -99,4 +112,128 @@ class MyInvoicesViewModel extends ChangeNotifier { }, ); } + + void filterInvoices(InvoiceFilterType filterType) { + currentFilter = filterType; + + switch (filterType) { + case InvoiceFilterType.all: + allInvoicesList = List.from(_originalInvoicesList); + break; + case InvoiceFilterType.hospital: + allInvoicesList = _originalInvoicesList.where((invoice) { + return invoice.projectName != null && invoice.projectName!.isNotEmpty; + }).toList(); + // Group by hospital (projectName) and sort + allInvoicesList.sort((a, b) { + int projectComparison = (a.projectName ?? '').compareTo(b.projectName ?? ''); + if (projectComparison != 0) return projectComparison; + return b.appointmentDate!.compareTo(a.appointmentDate!); + }); + break; + case InvoiceFilterType.clinic: + allInvoicesList = _originalInvoicesList.where((invoice) { + return invoice.clinicName != null && invoice.clinicName!.isNotEmpty; + }).toList(); + // Group by clinic (clinicName) and sort + allInvoicesList.sort((a, b) { + int clinicComparison = (a.clinicName ?? '').compareTo(b.clinicName ?? ''); + if (clinicComparison != 0) return clinicComparison; + return b.appointmentDate!.compareTo(a.appointmentDate!); + }); + break; + case InvoiceFilterType.doctor: + allInvoicesList = _originalInvoicesList.where((invoice) { + return invoice.doctorName != null && invoice.doctorName!.isNotEmpty; + }).toList(); + // Group by doctor (doctorName) and sort + allInvoicesList.sort((a, b) { + int doctorComparison = (a.doctorName ?? '').compareTo(b.doctorName ?? ''); + if (doctorComparison != 0) return doctorComparison; + return b.appointmentDate!.compareTo(a.appointmentDate!); + }); + break; + } + + notifyListeners(); + } + + void filterInvoicesByHospital(String hospitalName) { + currentFilter = InvoiceFilterType.hospital; + allInvoicesList = _originalInvoicesList + .where((invoice) => invoice.projectName == hospitalName) + .toList(); + allInvoicesList.sort((a, b) => b.appointmentDate!.compareTo(a.appointmentDate!)); + notifyListeners(); + } + + void filterInvoicesByClinic(String clinicName) { + currentFilter = InvoiceFilterType.clinic; + allInvoicesList = _originalInvoicesList + .where((invoice) => invoice.clinicName == clinicName) + .toList(); + allInvoicesList.sort((a, b) => b.appointmentDate!.compareTo(a.appointmentDate!)); + notifyListeners(); + } + + void filterInvoicesByDoctor(String doctorName) { + currentFilter = InvoiceFilterType.doctor; + allInvoicesList = _originalInvoicesList + .where((invoice) => invoice.doctorName == doctorName) + .toList(); + allInvoicesList.sort((a, b) => b.appointmentDate!.compareTo(a.appointmentDate!)); + notifyListeners(); + } + + List getOriginalInvoicesList() { + return _originalInvoicesList; + } + + // Filter bottom sheet methods + void prepareFilterList(InvoiceFilterType filterType) { + Set uniqueItems = {}; + + for (var invoice in _originalInvoicesList) { + String? itemName; + if (filterType == InvoiceFilterType.hospital) { + itemName = invoice.projectName; + } else if (filterType == InvoiceFilterType.clinic) { + itemName = invoice.clinicName; + } else if (filterType == InvoiceFilterType.doctor) { + itemName = invoice.doctorName; + } + + if (itemName != null && itemName.isNotEmpty) { + uniqueItems.add(itemName); + } + } + + _filterOriginalList = uniqueItems.toList()..sort(); + filterDisplayList = List.from(_filterOriginalList); + filterSearchController.clear(); + selectedFilterItem = null; + notifyListeners(); + } + + void searchFilterItems(String query) { + if (query.isEmpty) { + filterDisplayList = List.from(_filterOriginalList); + } else { + filterDisplayList = _filterOriginalList + .where((item) => item.toLowerCase().contains(query.toLowerCase())) + .toList(); + } + notifyListeners(); + } + + void clearFilterSearch() { + filterSearchController.clear(); + filterDisplayList = List.from(_filterOriginalList); + notifyListeners(); + } + + void selectFilterItem(String item) { + selectedFilterItem = item; + notifyListeners(); + } } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 4f66d29c..a28652d9 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -751,6 +751,11 @@ abstract class LocaleKeys { static const erConsultation = 'erConsultation'; static const myInvoice = 'myInvoice'; static const invoiceList = 'invoiceList'; + static const allInvoices = 'allInvoices'; + static const hospitals = 'hospitals'; + static const clinics = 'clinics'; + static const doctors = 'doctors'; + static const selectDoctor = 'selectDoctor'; static const thisItemIsNotAvailable = 'thisItemIsNotAvailable'; static const beforeAfterImages = 'beforeAfterImages'; static const clinicAcceptLivecare = 'clinicAcceptLivecare'; diff --git a/lib/presentation/my_invoices/my_invoices_list.dart b/lib/presentation/my_invoices/my_invoices_list.dart index dddb0a15..054d2fce 100644 --- a/lib/presentation/my_invoices/my_invoices_list.dart +++ b/lib/presentation/my_invoices/my_invoices_list.dart @@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_details_page.dart'; +import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/invoice_filter_bottom_sheet.dart'; import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/invoice_list_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -21,6 +22,8 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; +import '../../widgets/buttons/custom_button.dart'; + class MyInvoicesList extends StatefulWidget { const MyInvoicesList({super.key}); @@ -40,6 +43,75 @@ class _MyInvoicesListState extends State { super.initState(); } + void _showHospitalFilterBottomSheet(BuildContext context) { + myInvoicesViewModel.prepareFilterList(InvoiceFilterType.hospital); + + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.selectHospital.tr(context: context), + context, + child: InvoiceFilterBottomSheet( + title: LocaleKeys.hospital.tr(context: context), + onItemSelected: (selectedHospital) { + Navigator.pop(context); + myInvoicesViewModel.filterInvoicesByHospital(selectedHospital); + }, + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + myInvoicesViewModel.clearFilterSearch(); + }, + ); + } + + void _showClinicFilterBottomSheet(BuildContext context) { + myInvoicesViewModel.prepareFilterList(InvoiceFilterType.clinic); + + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.selectClinic.tr(context: context), + context, + child: InvoiceFilterBottomSheet( + title: LocaleKeys.clinic.tr(context: context), + onItemSelected: (selectedClinic) { + Navigator.pop(context); + myInvoicesViewModel.filterInvoicesByClinic(selectedClinic); + }, + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + myInvoicesViewModel.clearFilterSearch(); + }, + ); + } + + void _showDoctorFilterBottomSheet(BuildContext context) { + myInvoicesViewModel.prepareFilterList(InvoiceFilterType.doctor); + + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.selectDoctor.tr(context: context), + context, + child: InvoiceFilterBottomSheet( + title: LocaleKeys.doctor.tr(context: context), + onItemSelected: (selectedDoctor) { + Navigator.pop(context); + myInvoicesViewModel.filterInvoicesByDoctor(selectedDoctor); + }, + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + myInvoicesViewModel.clearFilterSearch(); + }, + ); + } + @override Widget build(BuildContext context) { myInvoicesViewModel = Provider.of(context, listen: false); @@ -50,62 +122,133 @@ class _MyInvoicesListState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 24.h), + SizedBox(height: 16.h), + Row( + children: [ + CustomButton( + text: LocaleKeys.allInvoices.tr(context: context), + onPressed: () { + myInvoicesViewModel.filterInvoices(InvoiceFilterType.all); + }, + backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.hospitals.tr(context: context), + onPressed: () { + if (myInvoicesVM.allInvoicesList.isEmpty) return; + _showHospitalFilterBottomSheet(context); + }, + backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.clinics.tr(context: context), + onPressed: () { + if (myInvoicesVM.allInvoicesList.isEmpty) return; + _showClinicFilterBottomSheet(context); + }, + backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.doctors.tr(context: context), + onPressed: () { + if (myInvoicesVM.allInvoicesList.isEmpty) return; + _showDoctorFilterBottomSheet(context); + }, + backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), ListView.builder( - itemCount: myInvoicesVM.isInvoicesListLoading ? 4 : myInvoicesVM.allInvoicesList.isEmpty ? 1 : myInvoicesVM.allInvoicesList.length, + itemCount: myInvoicesVM.isInvoicesListLoading + ? 4 + : myInvoicesVM.allInvoicesList.isEmpty + ? 1 + : myInvoicesVM.allInvoicesList.length, physics: NeverScrollableScrollPhysics(), shrinkWrap: true, padding: EdgeInsetsGeometry.zero, itemBuilder: (context, index) { return myInvoicesVM.isInvoicesListLoading - ? LabResultItemView( - onTap: () {}, - labOrder: null, - index: index, - isLoading: true, - ) - : myInvoicesVM.allInvoicesList.isNotEmpty ? 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, - child: InvoiceListCard( - getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], - onTap: () async { - myInvoicesVM.setInvoiceDetailLoading(); - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context)); - await myInvoicesVM.getInvoiceDetails( - appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, - invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, - projectID: myInvoicesVM.allInvoicesList[index].projectID!, - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - Navigator.of(context).push( - CustomPageRoute( - page: MyInvoicesDetailsPage(getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel, getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],), - ), - ); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - }, + ? LabResultItemView(onTap: () {}, labOrder: null, index: index, isLoading: true) + : myInvoicesVM.allInvoicesList.isNotEmpty + ? 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, + child: InvoiceListCard( + getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], + onTap: () async { + myInvoicesVM.setInvoiceDetailLoading(); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context)); + await myInvoicesVM.getInvoiceDetails( + appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, + invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, + projectID: myInvoicesVM.allInvoicesList[index].projectID!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + Navigator.of(context).push( + CustomPageRoute( + page: MyInvoicesDetailsPage( + getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel, + getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], + ), + ), + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + ), + ), ), ), - ), - ), - ) : Utils.getNoDataWidget(context); + ) + : Utils.getNoDataWidget(context); }).paddingSymmetrical(24.w, 0.h), ], ); diff --git a/lib/presentation/my_invoices/widgets/invoice_filter_bottom_sheet.dart b/lib/presentation/my_invoices/widgets/invoice_filter_bottom_sheet.dart new file mode 100644 index 00000000..340f1020 --- /dev/null +++ b/lib/presentation/my_invoices/widgets/invoice_filter_bottom_sheet.dart @@ -0,0 +1,133 @@ +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/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/debouncer.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/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; + +class InvoiceFilterBottomSheet extends StatelessWidget { + final Function(String) onItemSelected; + final String title; + + const InvoiceFilterBottomSheet({ + super.key, + required this.onItemSelected, + required this.title, + }); + + @override + Widget build(BuildContext context) { + final debouncer = Debouncer(milliseconds: 500); + + return Consumer( + builder: (context, vm, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: "Search $title", + controller: vm.filterSearchController, + onChange: (value) { + debouncer.run(() { + vm.searchFilterItems(value ?? ""); + }); + }, + 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), + SizedBox( + height: MediaQuery.sizeOf(context).height * 0.4, + child: vm.filterDisplayList.isEmpty + ? Center( + child: Text( + "No $title found", + style: TextStyle( + fontSize: 16, + color: AppColors.greyTextColor, + ), + ), + ) + : ListView.separated( + itemBuilder: (_, index) { + final item = vm.filterDisplayList[index]; + return FilterListItem( + itemName: item, + ).onPress(() { + vm.selectFilterItem(item); + onItemSelected(item); + }); + }, + separatorBuilder: (_, __) => SizedBox(height: 16.h), + itemCount: vm.filterDisplayList.length, + ), + ), + ], + ); + }, + ); + } +} + +class FilterListItem extends StatelessWidget { + final String itemName; + + const FilterListItem({super.key, required this.itemName}); + + AppState get appState => getIt.get(); + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + itemName, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: AppColors.blackColor, + ), + ), + ), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ); + } +}