From 8a8efa37f2a091f113c76173e03f46e59b79a808 Mon Sep 17 00:00:00 2001 From: Faiz Hashmi Date: Thu, 2 Nov 2023 11:19:25 +0300 Subject: [PATCH] Added Appoinment Module and Ads Filter --- lib/config/routes.dart | 3 +- .../appointment_list_model.dart | 16 +- lib/models/service_schedule_model.dart | 9 +- lib/repositories/ads_repo.dart | 44 ++++-- lib/repositories/common_repo.dart | 2 +- lib/services/payments_service.dart | 30 ++-- lib/theme/colors.dart | 1 + lib/view_models/ad_view_model.dart | 123 +++++++++++++++- lib/view_models/payment_view_model.dart | 104 +++++++++---- .../vehicle_details_container.dart | 1 + lib/views/advertisement/ads_detail_view.dart | 4 +- lib/views/advertisement/ads_filter_view.dart | 138 ++++++++++++++++++ .../advertisement/ads_search_filter_view.dart | 19 --- .../common_widgets/custom_rectangle_chip.dart | 50 +++++++ .../common_widgets/my_service_provider.dart | 16 +- .../common_widgets/search_entity_widget.dart | 56 +++++++ lib/widgets/common_widgets/time_slots.dart | 7 +- lib/widgets/dropdown/dropdow_field.dart | 2 +- 18 files changed, 523 insertions(+), 102 deletions(-) create mode 100644 lib/views/advertisement/ads_filter_view.dart delete mode 100644 lib/views/advertisement/ads_search_filter_view.dart create mode 100644 lib/widgets/common_widgets/custom_rectangle_chip.dart create mode 100644 lib/widgets/common_widgets/search_entity_widget.dart diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 17faaac..8b2fdb1 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -54,7 +54,7 @@ class AppRoutes { static const String selectAdTypeView = "/selectAdTypeView"; static const String adsDetailView = "/adsDetailView"; static const String createAdView = "/createAdView"; - static const String adsSearchFilterScreen = "/adsSearchFilterScreen"; + static const String adsFilterView = "/adsFilterView"; // Payments static const String paymentMethodsView = "/paymentMethodsView"; @@ -71,7 +71,6 @@ class AppRoutes { static const String createRequestPage = "/createRequestPage"; static const String offersListPage = "/offersListPage"; - static const String initialRoute = splash; static final Map routes = { diff --git a/lib/models/appointments_models/appointment_list_model.dart b/lib/models/appointments_models/appointment_list_model.dart index b37be89..091a898 100644 --- a/lib/models/appointments_models/appointment_list_model.dart +++ b/lib/models/appointments_models/appointment_list_model.dart @@ -1,4 +1,5 @@ import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/models/services/service_model.dart'; import 'package:mc_common_app/utils/enums.dart'; class AppointmentListModel { @@ -17,7 +18,7 @@ class AppointmentListModel { String? duration; String? appointmentDate; AppointmentStatusEnum? appointmentStatusEnum; - List? serviceAppointmentItems; + List? appointmentServicesList; AppointmentListModel( {this.id, @@ -34,11 +35,11 @@ class AppointmentListModel { this.providerName, this.duration, this.appointmentDate, - this.serviceAppointmentItems}); + this.appointmentServicesList}); @override String toString() { - return 'AppointmentListModel{id: $id, serviceSlotID: $serviceSlotID, appointmentStatusID: $appointmentStatusID, appointmentStatusText: $appointmentStatusText, serviceProviderID: $serviceProviderID, customerID: $customerID, isActive: $isActive, isPaymentRequired: $isPaymentRequired, paymentStatus: $paymentStatus, paymentStatusText: $paymentStatusText, customerName: $customerName, providerName: $providerName, duration: $duration, appointmentDate: $appointmentDate, appointmentStatusEnum: $appointmentStatusEnum, serviceAppointmentItems: $serviceAppointmentItems}'; + return 'AppointmentListModel{id: $id, serviceSlotID: $serviceSlotID, appointmentStatusID: $appointmentStatusID, appointmentStatusText: $appointmentStatusText, serviceProviderID: $serviceProviderID, customerID: $customerID, isActive: $isActive, isPaymentRequired: $isPaymentRequired, paymentStatus: $paymentStatus, paymentStatusText: $paymentStatusText, customerName: $customerName, providerName: $providerName, duration: $duration, appointmentDate: $appointmentDate, appointmentStatusEnum: $appointmentStatusEnum, appointmentServicesList: $appointmentServicesList}'; } AppointmentListModel.fromJson(Map json) { @@ -57,10 +58,11 @@ class AppointmentListModel { duration = json['duration']; appointmentDate = json['appointmentDate']; appointmentStatusEnum = (json['appointmentStatusID'] as int).toAppointmentStatusEnum(); - if (json['serviceAppointmentItems'] != null) { - serviceAppointmentItems = []; - json['serviceAppointmentItems'].forEach((v) { - serviceAppointmentItems!.add(ServiceAppointmentItems.fromJson(v)); + + if (json['serviceList'] != null) { + appointmentServicesList = []; + json['serviceList'].forEach((v) { + appointmentServicesList!.add(ServiceModel.fromJson(v, isForAppointment: true)); }); } } diff --git a/lib/models/service_schedule_model.dart b/lib/models/service_schedule_model.dart index f682e57..071d1ff 100644 --- a/lib/models/service_schedule_model.dart +++ b/lib/models/service_schedule_model.dart @@ -38,7 +38,7 @@ class ServiceAppointmentScheduleModel { for (var element in serviceSlotList!) { if (!isAlreadyThere(element.slotDate!, customTimeDateSlotList)) { customTimeDateSlotList.add(CustomTimeDateSlotModel( - date: TimeSlotModel(slotId: element.id!, isSelected: false, date: element.slotDate!, allowAppointment: true), + date: TimeSlotModel(slotId: element.id!, isSelected: false, date: element.slotDate!, allowAppointment: (element.allowAppointment ?? 0) > (element.bookAppointment ?? 0)), availableSlots: getAvailableSlotsByDate(element.slotDate!), )); } @@ -97,12 +97,9 @@ class ServiceAppointmentScheduleModel { return slotDates; } - //TODO: I WILL START FROM HERE; I NEED TO ONLY PICK THE DISTINCT DATES FROM THE RESPONSE AND THEN BASED ON THE DATE SELECTION I WILL PICK THEIR SLOTS. - //TODO: AFTER THAT, I WILL + //comprehensive checkup and body checkup - ServiceAppointmentScheduleModel.fromJson - - (Map json, {bool isForAppointment = false}) { + ServiceAppointmentScheduleModel.fromJson(Map json, {bool isForAppointment = false}) { if (json['serviceSlotList'] != null) { serviceSlotList = []; json['serviceSlotList'].forEach((v) { diff --git a/lib/repositories/ads_repo.dart b/lib/repositories/ads_repo.dart index 2dbd80c..9507097 100644 --- a/lib/repositories/ads_repo.dart +++ b/lib/repositories/ads_repo.dart @@ -86,8 +86,8 @@ class AdsRepoImp implements AdsRepo { var postParams = { "CountryID": countryId.toString(), }; - GenericRespModel adsGenericModel = - await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleCityGet, queryParameters: postParams); + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( + token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleCityGet, queryParameters: countryId != -1 ? postParams : null); List vehicleCities = List.generate(adsGenericModel.data.length, (index) => VehicleCityModel.fromJson(adsGenericModel.data[index])); return vehicleCities; } @@ -141,8 +141,12 @@ class AdsRepoImp implements AdsRepo { var postParams = { "VehicleType": vehicleTypeId.toString(), }; - GenericRespModel adsGenericModel = - await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleModelYearGet, queryParameters: postParams); + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( + token: appState.getUser.data!.accessToken, + (json) => GenericRespModel.fromJson(json), + ApiConsts.vehicleModelYearGet, + queryParameters: vehicleTypeId != -1 ? postParams : null, + ); List vehicleModelYears = List.generate(adsGenericModel.data.length, (index) => VehicleYearModel.fromJson(adsGenericModel.data[index])); return vehicleModelYears; } @@ -152,8 +156,12 @@ class AdsRepoImp implements AdsRepo { var postParams = { "VehicleType": vehicleTypeId.toString(), }; - GenericRespModel adsGenericModel = - await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleModelGet, queryParameters: postParams); + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( + token: appState.getUser.data!.accessToken, + (json) => GenericRespModel.fromJson(json), + ApiConsts.vehicleModelGet, + queryParameters: postParams, + ); List vehicleModels = List.generate(adsGenericModel.data.length, (index) => VehicleModel.fromJson(adsGenericModel.data[index])); return vehicleModels; } @@ -163,8 +171,12 @@ class AdsRepoImp implements AdsRepo { var postParams = { "VehicleType": vehicleTypeId.toString(), }; - GenericRespModel adsGenericModel = - await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleBrandGet, queryParameters: postParams); + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( + token: appState.getUser.data!.accessToken, + (json) => GenericRespModel.fromJson(json), + ApiConsts.vehicleBrandGet, + queryParameters: vehicleTypeId != -1 ? postParams : null, + ); List vehicleBrands = List.generate(adsGenericModel.data.length, (index) => VehicleBrandsModel.fromJson(adsGenericModel.data[index])); return vehicleBrands; } @@ -174,8 +186,12 @@ class AdsRepoImp implements AdsRepo { var postParams = { "VehicleType": vehicleTypeId.toString(), }; - GenericRespModel adsGenericModel = - await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleSellerTypeGet, queryParameters: postParams); + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( + token: appState.getUser.data!.accessToken, + (json) => GenericRespModel.fromJson(json), + ApiConsts.vehicleSellerTypeGet, + queryParameters: postParams, + ); List vehicleSellerTypes = List.generate(adsGenericModel.data.length, (index) => VehicleSellerTypeModel.fromJson(adsGenericModel.data[index])); return vehicleSellerTypes; } @@ -185,8 +201,12 @@ class AdsRepoImp implements AdsRepo { var postParams = { "VehicleType": vehicleTypeId.toString(), }; - GenericRespModel adsGenericModel = - await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleTransmissionGet, queryParameters: postParams); + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( + token: appState.getUser.data!.accessToken, + (json) => GenericRespModel.fromJson(json), + ApiConsts.vehicleTransmissionGet, + queryParameters: postParams, + ); List vehicleTransmissions = List.generate(adsGenericModel.data.length, (index) => VehicleTransmissionModel.fromJson(adsGenericModel.data[index])); return vehicleTransmissions; } diff --git a/lib/repositories/common_repo.dart b/lib/repositories/common_repo.dart index 68e3614..0ed494b 100644 --- a/lib/repositories/common_repo.dart +++ b/lib/repositories/common_repo.dart @@ -66,7 +66,7 @@ class CommonRepoImp implements CommonRepo { @override Future> getMyAppointments() async { var params = { - "userID": appState.getUser.data!.userInfo!.userId ?? "", + "customerID": appState.getUser.data!.userInfo!.customerId.toString() ?? "", }; GenericRespModel genericRespModel = await apiClient.getJsonForObject( token: appState.getUser.data!.accessToken, diff --git a/lib/services/payments_service.dart b/lib/services/payments_service.dart index 30fadc4..a82ddb9 100644 --- a/lib/services/payments_service.dart +++ b/lib/services/payments_service.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/services/my_in_app_browser.dart'; @@ -55,31 +56,38 @@ class PaymentServiceImp implements PaymentService { required Function() onFailure, }) async { String urlRequest = ""; + int customerId = AppState().getUser.data!.userInfo!.customerId ?? 0; switch (paymentType) { case PaymentTypes.subscription: - urlRequest = ""; + urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&OrderProviderSubscriptionID=$id"; + break; case PaymentTypes.appointment: String appointIds = ''; - for (var element in appointmentIds!) { - appointIds = "$appointIds$element:"; + for (int i = 0; i < appointmentIds!.length; i++) { + var element = appointmentIds[i]; + if (i == appointmentIds.length - 1) { + appointIds = "$appointIds$element"; + } else { + appointIds = "$appointIds$element:"; + } } - urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&AppointmentIDs=$appointIds"; + urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&CustomerID=$customerId&AppointmentIDs=$appointIds"; break; case PaymentTypes.adReserve: - urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&AdsID=$id"; + urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&CustomerID=$customerId&AdsID=$id"; break; case PaymentTypes.ads: - urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&AdsID=$id"; - break; - case PaymentTypes.request: - urlRequest = ""; + urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&CustomerID=$customerId&AdsID=$id"; break; case PaymentTypes.extendAds: - urlRequest = ""; + urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&CustomerID=$customerId&AdsID=$id"; + break; + case PaymentTypes.request: + urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&CustomerID=$customerId&RequestID=$id"; break; } - print("PaymentUrl: $urlRequest"); + log("PaymentUrl: $urlRequest"); myInAppBrowser = MyInAppBrowser(onExitCallback: () { log("Browser Exited"); }, onLoadStartCallback: (String url) { diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 32d7e49..7234075 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -21,6 +21,7 @@ class MyColors { static const Color grey70Color = Color(0xff707070); static const Color greyACColor = Color(0xffACACAC); static const Color chipColor = Color(0xffE6E6E6); + static const Color roundedCrossBgColor = Color(0xFFA3A3A3); static const Color greyShadowColor = Color(0xFFE8E7E7); static const Color grey98Color = Color(0xff989898); static const Color lightGreyEFColor = Color(0xffEFEFEF); diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index c343678..1386dcb 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -417,7 +417,14 @@ class AdVM extends BaseVM { SelectionModel vehicleBrandId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); - void updateSelectionVehicleBrandId(SelectionModel id) { + void updateSelectionVehicleBrandId(SelectionModel id, {bool isForSearch = false}) { + if (isForSearch) { + VehicleBrandsModel brand = vehicleBrands.firstWhere((element) => element.id == id.selectedId); + DropValue brandValue = DropValue(brand.id ?? 0, brand.vehicleBrandDescription ?? "", ""); + if (!ifAlreadyExist(list: vehicleBrandsAdSearchHistory, value: brandValue)) { + addToVehicleBrandsAdSearchHistory(value: brandValue); + } + } vehicleBrandId = id; getVehicleDetailsByVehicleBrandId(); @@ -433,7 +440,14 @@ class AdVM extends BaseVM { SelectionModel vehicleModelYearId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); - void updateSelectionVehicleModelYearId(SelectionModel id) { + void updateSelectionVehicleModelYearId(SelectionModel id, {bool isForSearch = false}) { + if (isForSearch) { + VehicleYearModel year = vehicleModelYears.firstWhere((element) => element.id == id.selectedId); + DropValue yearValue = DropValue(year.id ?? 0, year.modelYear ?? "", ""); + if (!ifAlreadyExist(list: vehicleYearAdSearchHistory, value: yearValue)) { + addToVehicleYearAdSearchHistory(value: yearValue); + } + } vehicleModelYearId = id; notifyListeners(); } @@ -534,7 +548,14 @@ class AdVM extends BaseVM { SelectionModel vehicleCityId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); - void updateSelectionVehicleCityId(SelectionModel id) { + void updateSelectionVehicleCityId(SelectionModel id, {bool isForSearch = false}) async { + if (isForSearch) { + VehicleCityModel city = vehicleCities.firstWhere((element) => element.id == id.selectedId); + DropValue cityValue = DropValue(city.id ?? 0, city.cityName ?? "", ""); + if (!ifAlreadyExist(list: vehicleLocationAdSearchHistory, value: cityValue)) { + addToVehicleLocationAdSearchHistory(value: cityValue); + } + } vehicleCityId = id; notifyListeners(); } @@ -1161,6 +1182,102 @@ class AdVM extends BaseVM { ); return vehiclePostingDamageParts; } + +// ************ ADS SEARCH VIEW **************** + + Future populateDataForAdFilter() async { + if (vehicleBrands.isEmpty) { + vehicleBrands = await adsRepo.getVehicleBrands(vehicleTypeId: -1); + } + if (vehicleModelYears.isEmpty) { + vehicleModelYears = await adsRepo.getVehicleModelYears(vehicleTypeId: -1); + } + vehicleCities = await adsRepo.getVehicleCities(countryId: -1); // fetch all the cities + } + + ifAlreadyExist({required List list, required DropValue value}) { + int index = list.indexWhere((element) { + return element.id == value.id; + }); + + if (index != -1) { + return true; + } + + return false; + } + + //BRANDS + List vehicleBrandsAdSearchHistory = []; + + void removeVehicleBrandsAdSearchHistory({bool isClear = false, required int index}) { + if (isClear) { + vehicleBrandsAdSearchHistory.clear(); + notifyListeners(); + return; + } + vehicleBrandsAdSearchHistory.removeAt(index); + notifyListeners(); + } + + void addToVehicleBrandsAdSearchHistory({required DropValue value}) { + vehicleBrandsAdSearchHistory.add(value); + notifyListeners(); + } + + // LOCATION + List vehicleLocationAdSearchHistory = []; + + void removeVehicleLocationAdSearchHistory({bool isClear = false, required int index}) { + if (isClear) { + vehicleLocationAdSearchHistory.clear(); + notifyListeners(); + return; + } + vehicleLocationAdSearchHistory.removeAt(index); + notifyListeners(); + } + + void addToVehicleLocationAdSearchHistory({required DropValue value}) { + vehicleLocationAdSearchHistory.add(value); + notifyListeners(); + } + + // OWNER + List vehicleAdOwnerSearchHistory = []; + + void removeVehicleAdOwnerSearchHistory({bool isClear = false, required int index}) { + if (isClear) { + vehicleAdOwnerSearchHistory.clear(); + notifyListeners(); + return; + } + vehicleAdOwnerSearchHistory.removeAt(index); + notifyListeners(); + } + + void addToVehicleAdOwnerSearchHistory({required DropValue value}) { + vehicleAdOwnerSearchHistory.add(value); + notifyListeners(); + } + + // YEAR + List vehicleYearAdSearchHistory = []; + + void removeVehicleYearAdSearchHistory({bool isClear = false, required int index}) { + if (isClear) { + vehicleYearAdSearchHistory.clear(); + notifyListeners(); + return; + } + vehicleYearAdSearchHistory.removeAt(index); + notifyListeners(); + } + + void addToVehicleYearAdSearchHistory({required DropValue value}) { + vehicleYearAdSearchHistory.add(value); + notifyListeners(); + } } class VehicleDamageCard { diff --git a/lib/view_models/payment_view_model.dart b/lib/view_models/payment_view_model.dart index a93e5f7..4d742a4 100644 --- a/lib/view_models/payment_view_model.dart +++ b/lib/view_models/payment_view_model.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:flutter/cupertino.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; @@ -23,6 +25,25 @@ class PaymentVM extends ChangeNotifier { currentAdId = id; } + int requestId = -1; + + void updateRequestId({required int id}) { + requestId = id; + } + + int orderProviderSubscriptionId = -1; + + void updateOrderProviderSubscriptionId({required int id}) { + orderProviderSubscriptionId = id; + } + + List appointmentIdsForPayment = []; + + void updateAppointmentIdsForPayment({required List ids}) { + log("appointmetList: $ids"); + appointmentIdsForPayment = ids; + } + updateSelectedPaymentMethod(PaymentMethods selectedMethod) { selectedPaymentMethod = selectedMethod; notifyListeners(); @@ -69,9 +90,46 @@ class PaymentVM extends ChangeNotifier { } } - Future placeThePayment({required int adId, required PaymentTypes paymentTypeEnum, required BuildContext context}) async { + Future onAppointmentPaymentSuccess({required BuildContext context, required int currentAdId, required int paymentTypeId}) async { + Utils.showLoading(context); + //TODO: CONFIRM FROM ZAHOOR THAT WILL THIS METHOD WORK FOR APPOINTMENT + PayOrderDetailRespModel payOrderDetailRespModel = await paymentRepo.getPayOrderDetails(paymentId: paymentTypeId, adId: currentAdId); + await Future.delayed(const Duration(seconds: 2)); + Utils.hideLoading(context); + + print("payOrderDetailRespModel: ${payOrderDetailRespModel.toString()}"); + + if (payOrderDetailRespModel.isPaid == null || !payOrderDetailRespModel.isPaid!) { + Utils.showToast("Payment Failed!"); + return; + } + + if (payOrderDetailRespModel.isPaid != null && payOrderDetailRespModel.isPaid!) { + Utils.showToast("Payment Successful"); + navigateReplaceWithNameUntilRoute(context, AppRoutes.dashboard); + } + } + + int getIdTypeByPaymentType(PaymentTypes paymentTypes) { + switch (paymentTypes) { + case PaymentTypes.subscription: + return orderProviderSubscriptionId; + case PaymentTypes.appointment: + return -1; + case PaymentTypes.request: + return requestId; + case PaymentTypes.adReserve: + case PaymentTypes.ads: + case PaymentTypes.extendAds: + return currentAdId; + } + } + + Future placeThePayment({required PaymentTypes paymentTypeEnum, required BuildContext context}) async { await paymentService.placePayment( - id: currentAdId, + // This will be request ID for request payment || ad ID for Ad Related Payment || OrderProviderSubscriptionID for SubscriptionPayment + id: getIdTypeByPaymentType(paymentTypeEnum), + appointmentIds: appointmentIdsForPayment, paymentType: paymentTypeEnum, onFailure: () { Utils.showToast("Payment Failed!"); @@ -81,6 +139,7 @@ class PaymentVM extends ChangeNotifier { break; case PaymentTypes.appointment: // TODO: Handle this case. + log("Appointment Payment has been Failed!!"); break; case PaymentTypes.adReserve: // TODO: Handle this case. @@ -99,22 +158,16 @@ class PaymentVM extends ChangeNotifier { onSuccess: () async { switch (paymentTypeEnum) { case PaymentTypes.subscription: - // TODO: Handle this case. break; case PaymentTypes.appointment: - // TODO: Handle this case. - break; - case PaymentTypes.adReserve: - // TODO: Handle this case. - break; - case PaymentTypes.ads: - await onAdsPaymentSuccess(context: context, paymentTypeId: paymentTypeEnum.getIdFromPaymentTypesEnum(), currentAdId: currentAdId); + log("Appointment Payment has been Succeeded"); break; case PaymentTypes.request: - // TODO: Handle this case. break; + case PaymentTypes.adReserve: + case PaymentTypes.ads: case PaymentTypes.extendAds: - // TODO: Handle this case. + await onAdsPaymentSuccess(context: context, paymentTypeId: paymentTypeEnum.getIdFromPaymentTypesEnum(), currentAdId: currentAdId); break; } }, @@ -125,32 +178,21 @@ class PaymentVM extends ChangeNotifier { currentPaymentType = paymentType; switch (currentPaymentType) { case PaymentTypes.appointment: + if (appointmentIdsForPayment.isEmpty) return; + await placeThePayment(context: context, paymentTypeEnum: paymentType); break; - - case PaymentTypes.ads: - if (currentAdId == -1) return; - - await placeThePayment(adId: currentAdId, context: context, paymentTypeEnum: paymentType); - break; - - case PaymentTypes.adReserve: - if (currentAdId == -1) return; - - await placeThePayment(adId: currentAdId, context: context, paymentTypeEnum: paymentType); - break; - - case PaymentTypes.extendAds: - if (currentAdId == -1) return; - - await placeThePayment(adId: currentAdId, context: context, paymentTypeEnum: paymentType); - break; - case PaymentTypes.request: // TODO: Handle this case. break; case PaymentTypes.subscription: // TODO: Handle this case. break; + case PaymentTypes.ads: + case PaymentTypes.adReserve: + case PaymentTypes.extendAds: + if (currentAdId == -1) return; + await placeThePayment(context: context, paymentTypeEnum: paymentType); + break; } } } diff --git a/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart b/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart index b772ca5..17009ec 100644 --- a/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart +++ b/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart @@ -141,6 +141,7 @@ class VehicleDetails extends StatelessWidget { (DropValue value) => adVM.updateSelectionVehicleTransmissionId(SelectionModel(selectedId: value.id, selectedOption: value.value)), list: vehicleTransmissionsDrop, hint: "Vehicle Transmission", + dropdownValue: adVM.vehicleTransmissionId.selectedId != -1 ? DropValue(adVM.vehicleTransmissionId.selectedId, adVM.vehicleTransmissionId.selectedOption, "") : null, errorValue: adVM.vehicleTransmissionId.errorValue, ); }), diff --git a/lib/views/advertisement/ads_detail_view.dart b/lib/views/advertisement/ads_detail_view.dart index af6898e..cb40c61 100644 --- a/lib/views/advertisement/ads_detail_view.dart +++ b/lib/views/advertisement/ads_detail_view.dart @@ -65,6 +65,7 @@ class AdsDetailView extends StatelessWidget { Widget build(BuildContext context) { print("adId: ${adDetails.id}"); print("statusID: ${adDetails.statusID}"); + context.read().updateCurrentAdId(id: adDetails.id!); return Scaffold( appBar: CustomAppBar( title: "Ads", @@ -467,7 +468,6 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { @override Widget build(BuildContext context) { - context.read().updateCurrentAdId(id: adDetailsModel.id!); // switch (adPostStatus) { // case AdPostStatus.pendingForPayment: // break; @@ -802,7 +802,6 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { adVM.updateSelectionVehicleTypeId( SelectionModel(selectedId: adDetailsModel.vehicle!.vehicleType!, selectedOption: (adDetailsModel.vehicle!.vehicleType!).toVehicleTypeString(), errorValue: ""), ); - showMyBottomSheet(context, child: const AdDurationSelectionSheetContent(isFromExtendAd: true, isUpdateAdSelected: true)); }, ), @@ -868,7 +867,6 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { @override Widget build(BuildContext context) { - context.read().updateCurrentAdId(id: adDetailsModel.id!); switch (adDetailsModel.adPostStatus!) { case AdPostStatus.pendingForPayment: return pendingForPaymentAction(context, adID: adDetailsModel.id!); diff --git a/lib/views/advertisement/ads_filter_view.dart b/lib/views/advertisement/ads_filter_view.dart new file mode 100644 index 0000000..eb1d6e7 --- /dev/null +++ b/lib/views/advertisement/ads_filter_view.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/models/widgets_models.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/view_models/ad_view_model.dart'; +import 'package:mc_common_app/widgets/button/show_fill_button.dart'; +import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; +import 'package:mc_common_app/widgets/common_widgets/search_entity_widget.dart'; +import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:sizer/sizer.dart'; + +class AdsFilterView extends StatelessWidget { + const AdsFilterView({super.key}); + + // + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar( + title: "Ads Filter", + profileImageUrl: MyAssets.bnCar, + isRemoveBackButton: false, + isDrawerEnabled: false, + onBackButtonTapped: () { + context.read().resetValues(); + Navigator.pop(context); + }, + ), + body: Consumer( + builder: (BuildContext context, AdVM adVM, Widget? child) { + return WillPopScope( + onWillPop: () async { + context.read().resetValues(); + return true; + }, + child: Column( + children: [ + ListView( + children: [ + 20.height, + SearchEntityWidget( + title: "Search By Location", + actionWidget: Builder(builder: (context) { + List vehicleBrandsDrop = []; + for (var element in adVM.vehicleCities) { + vehicleBrandsDrop.add(DropValue(element.id?.toInt() ?? 0, element.cityName ?? "", "")); + } + return DropdownField( + (DropValue value) => adVM.updateSelectionVehicleCityId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true), + list: vehicleBrandsDrop, + dropdownValue: adVM.vehicleCityId.selectedId != -1 ? DropValue(adVM.vehicleCityId.selectedId, adVM.vehicleCityId.selectedOption, "") : null, + hint: "Select Location", + errorValue: adVM.vehicleCityId.errorValue, + ); + }), + historyContent: adVM.vehicleLocationAdSearchHistory, + onHistoryItemDeleted: (index) { + adVM.removeVehicleLocationAdSearchHistory(index: index); + }, + onHistoryItemTapped: (DropValue value) => adVM.updateSelectionVehicleCityId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true), + ), + const Divider(thickness: 1.2).paddingOnly(top: 7, bottom: 7), + SearchEntityWidget( + title: "Search By Brand Name", + actionWidget: Builder(builder: (context) { + List vehicleBrandsDrop = []; + for (var element in adVM.vehicleBrands) { + vehicleBrandsDrop.add(DropValue(element.id?.toInt() ?? 0, element.vehicleBrandDescription ?? "", "")); + } + return DropdownField( + (DropValue value) => adVM.updateSelectionVehicleBrandId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true), + list: vehicleBrandsDrop, + dropdownValue: adVM.vehicleBrandId.selectedId != -1 ? DropValue(adVM.vehicleBrandId.selectedId, adVM.vehicleBrandId.selectedOption, "") : null, + hint: "Select Vehicle Brand", + errorValue: adVM.vehicleBrandId.errorValue, + ); + }), + historyContent: adVM.vehicleBrandsAdSearchHistory, + onHistoryItemDeleted: (index) => adVM.removeVehicleBrandsAdSearchHistory(index: index), + onHistoryItemTapped: (DropValue value) => adVM.updateSelectionVehicleBrandId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true), + ), + const Divider(thickness: 1.2).paddingOnly(top: 7, bottom: 7), + SearchEntityWidget( + title: "Search By Year", + actionWidget: Builder(builder: (context) { + List vehicleBrandsDrop = []; + for (var element in adVM.vehicleModelYears) { + vehicleBrandsDrop.add(DropValue(element.id?.toInt() ?? 0, element.modelYear ?? "", "")); + } + return DropdownField( + (DropValue value) => adVM.updateSelectionVehicleModelYearId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true), + list: vehicleBrandsDrop, + dropdownValue: adVM.vehicleModelYearId.selectedId != -1 ? DropValue(adVM.vehicleModelYearId.selectedId, adVM.vehicleModelYearId.selectedOption, "") : null, + hint: "Select Year", + errorValue: adVM.vehicleModelYearId.errorValue, + ); + }), + historyContent: adVM.vehicleYearAdSearchHistory, + onHistoryItemDeleted: (index) => adVM.removeVehicleYearAdSearchHistory(index: index), + onHistoryItemTapped: (DropValue value) => adVM.updateSelectionVehicleModelYearId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true), + ) + ], + ).expand(), + Container( + color: MyColors.white, + child: Column( + children: [ + 5.height, + Row( + children: [ + Expanded( + child: ShowFillButton( + maxHeight: 55, + title: "Search", + onPressed: () {}, + backgroundColor: MyColors.darkPrimaryColor, + txtColor: MyColors.white, + fontSize: 18, + ), + ) + ], + ), + 10.height, + ], + ), + ), + ], + ).horPaddingMain(), + ); + }, + ), + ); + } +} diff --git a/lib/views/advertisement/ads_search_filter_view.dart b/lib/views/advertisement/ads_search_filter_view.dart deleted file mode 100644 index 313f7db..0000000 --- a/lib/views/advertisement/ads_search_filter_view.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mc_common_app/classes/consts.dart'; -import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; - -class AdsSearchFilterView extends StatelessWidget { - const AdsSearchFilterView({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: CustomAppBar( - title: "Filter Ads", - profileImageUrl: MyAssets.bnCar, - isRemoveBackButton: false, - isDrawerEnabled: false, - ), - ); - } -} diff --git a/lib/widgets/common_widgets/custom_rectangle_chip.dart b/lib/widgets/common_widgets/custom_rectangle_chip.dart new file mode 100644 index 0000000..509e664 --- /dev/null +++ b/lib/widgets/common_widgets/custom_rectangle_chip.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; + +class CustomRectangleChip extends StatelessWidget { + final String text; + final double? height; + final double? width; + final Color bgColor; + final Color textColor; + final Function() onHistoryItemDeleted; + final Function() onHistoryItemTapped; + + const CustomRectangleChip({ + super.key, + this.bgColor = MyColors.chipColor, + required this.text, + this.textColor = MyColors.darkTextColor, + this.width, + this.height = 28, + required this.onHistoryItemDeleted, + required this.onHistoryItemTapped, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + color: bgColor, + height: height, + child: Row( + children: [ + text.toText(fontSize: 15, color: MyColors.darkTextColor), + 8.width, + Container( + height: 15, + width: 15, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: MyColors.roundedCrossBgColor, + ), + child: const Icon(Icons.close, color: MyColors.white, size: 10), + ).onPress(onHistoryItemDeleted), + ], + ), + ).onPress(onHistoryItemTapped); + } +} diff --git a/lib/widgets/common_widgets/my_service_provider.dart b/lib/widgets/common_widgets/my_service_provider.dart index afcb536..f316c1b 100644 --- a/lib/widgets/common_widgets/my_service_provider.dart +++ b/lib/widgets/common_widgets/my_service_provider.dart @@ -1,11 +1,16 @@ import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/models/provider_branches_models/branch_detail_model.dart'; +import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; class ServiceProviderWidget extends StatelessWidget { - const ServiceProviderWidget({Key? key}) : super(key: key); + final List nearbyBranches; + + const ServiceProviderWidget({Key? key, required this.nearbyBranches}) : super(key: key); @override Widget build(BuildContext context) { @@ -13,10 +18,11 @@ class ServiceProviderWidget extends StatelessWidget { width: double.infinity, height: 140, child: ListView.builder( - itemCount: 9, + itemCount: nearbyBranches.length, scrollDirection: Axis.horizontal, shrinkWrap: true, itemBuilder: (BuildContext context, int index) { + BranchDetailModel branchDetailModel = nearbyBranches[index]; return SizedBox( width: 90, child: Column( @@ -28,10 +34,12 @@ class ServiceProviderWidget extends StatelessWidget { fit: BoxFit.cover, ).toCircle(borderRadius: 100), 8.height, - "Olaya Branch".toText(fontSize: 14, isBold: true, textAlign: TextAlign.center), + "${branchDetailModel.branchName}".toText(fontSize: 14, isBold: true, textAlign: TextAlign.center), ], ), - ); + ).onPress(() { + navigateWithName(context, AppRoutes.branchDetailPage, arguments: branchDetailModel); + }); }, ), ); diff --git a/lib/widgets/common_widgets/search_entity_widget.dart b/lib/widgets/common_widgets/search_entity_widget.dart new file mode 100644 index 0000000..d675d11 --- /dev/null +++ b/lib/widgets/common_widgets/search_entity_widget.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/widgets/common_widgets/custom_rectangle_chip.dart'; +import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; + +class SearchEntityWidget extends StatelessWidget { + final String title; + final Widget actionWidget; + final List historyContent; + final Function(int) onHistoryItemDeleted; + final Function(DropValue) onHistoryItemTapped; + + const SearchEntityWidget({ + super.key, + required this.title, + required this.actionWidget, + required this.historyContent, + required this.onHistoryItemDeleted, + required this.onHistoryItemTapped, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText(fontSize: 16, isBold: true), + 8.height, + actionWidget, + 10.height, + historyContent.isNotEmpty + ? SizedBox( + height: 33, + child: ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: historyContent.length, + itemBuilder: (BuildContext context, int index) { + return CustomRectangleChip( + text: historyContent[index].value, + onHistoryItemDeleted: () { + onHistoryItemDeleted(index); + }, + onHistoryItemTapped: () { + onHistoryItemTapped(historyContent[index]); + }, + ).paddingOnly(right: 10); + }), + ) + : const SizedBox.shrink(), + ], + ); + } +} diff --git a/lib/widgets/common_widgets/time_slots.dart b/lib/widgets/common_widgets/time_slots.dart index 32607a9..2c4099e 100644 --- a/lib/widgets/common_widgets/time_slots.dart +++ b/lib/widgets/common_widgets/time_slots.dart @@ -23,6 +23,9 @@ class BuildTimeSlots extends StatelessWidget { itemBuilder: (BuildContext context, int index) { return InkWell( onTap: () { + if (!timeSlots[index].allowAppointment) { + return; + } onPressed(index); }, child: Container( @@ -31,9 +34,9 @@ class BuildTimeSlots extends StatelessWidget { // width: 50, padding: const EdgeInsets.symmetric(horizontal: 9), decoration: BoxDecoration( - color: timeSlots[index].isSelected ? MyColors.darkIconColor : null, + color: timeSlots[index].allowAppointment ? (timeSlots[index].isSelected ? MyColors.darkIconColor : null) : null, border: Border.all( - color: timeSlots[index].isSelected ? MyColors.darkIconColor : MyColors.primaryColor, + color: timeSlots[index].allowAppointment ? (timeSlots[index].isSelected ? MyColors.darkIconColor : MyColors.primaryColor) : Colors.grey, ), ), child: timeSlots[index].slot.toText( diff --git a/lib/widgets/dropdown/dropdow_field.dart b/lib/widgets/dropdown/dropdow_field.dart index bd0fc9a..4445317 100644 --- a/lib/widgets/dropdown/dropdow_field.dart +++ b/lib/widgets/dropdown/dropdow_field.dart @@ -55,7 +55,7 @@ class _DropdownFieldState extends State { Container( decoration: widget.showAppointmentPickerVariant ? null : Utils.containerColorRadiusBorderWidth(MyColors.white, 0, MyColors.darkPrimaryColor, 2), margin: const EdgeInsets.all(0), - // padding: const EdgeInsets.only(left: 0, right: ), + padding: const EdgeInsets.only(left: 8, right: 8), width: widget.showAppointmentPickerVariant ? 170 : null, child: DropdownButton( value: dropdownValue,