From 021618fa6b04bd3de7828ca12f41846bdf5e1306 Mon Sep 17 00:00:00 2001 From: Faiz Hashmi Date: Thu, 6 Feb 2025 10:02:24 +0300 Subject: [PATCH] Changes --- assets/langs/ar-SA.json | 12 +- assets/langs/en-US.json | 10 +- lib/classes/consts.dart | 8 +- lib/config/routes.dart | 3 + lib/generated/codegen_loader.g.dart | 22 +- lib/generated/locale_keys.g.dart | 10 +- .../ads_bank_details_model.dart | 14 + .../appointment_list_model.dart | 3 + lib/models/requests_models/offers_model.dart | 86 +++--- .../providers_offers_chat_model.dart | 158 ++++++++++ lib/models/services_models/service_model.dart | 2 +- lib/repositories/ads_repo.dart | 55 +++- lib/repositories/request_repo.dart | 14 +- lib/view_models/ad_view_model.dart | 272 ++++++++++++++++-- lib/view_models/appointments_view_model.dart | 53 ++-- lib/view_models/requests_view_model.dart | 65 +++-- .../ads_detail_view/ads_detail_view.dart | 33 ++- lib/views/advertisement/ads_filter_view.dart | 131 ++++++++- .../ad_duration_selection_sheet.dart | 11 +- .../advertisement/select_ad_type_view.dart | 11 +- .../appointments/appointment_detail_view.dart | 47 +-- .../common_fragments/requests_fragment.dart | 18 +- .../requests/providers_chat_list_page.dart | 180 ++++++++++++ .../common_widgets/search_entity_widget.dart | 6 +- 24 files changed, 1036 insertions(+), 188 deletions(-) create mode 100644 lib/models/requests_models/providers_offers_chat_model.dart create mode 100644 lib/views/requests/providers_chat_list_page.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 2e1aff2..582f61e 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -357,7 +357,7 @@ "setupTouchID": "يرجى إعداد معرف اللمس", "reenableTouchID": "يرجى إعادة تمكين معرف اللمس", "scanFaceIDAuthenticate": "امسح بصمة الإصبع أو معرف الوجه للتحقق", - "AdDeletedSuccessfully": "تم حذف الإعلان بنجاح!", + "adDeletedSuccessfully": "تم حذف الإعلان بنجاح!", "yourReservationCancelled": "تم إلغاء حجزك.", "adDeactivatedSuccessfully": "تم إلغاء تفعيل الإعلان بنجاح!", "vehicle": { @@ -797,5 +797,13 @@ "continueAsGuest": "الاستمرار كضيف", "loginToViewAppointments": "الرجاء تسجيل الدخول لعرض المواعيد", "itemType": "غرض", - "upgradeSubscription": "قم بترقية اشتراكك" + "upgradeSubscription": "قم بترقية اشتراكك", + "searchByAdID": "البحث برقم الإعلان", + "enterAdID": "أدخل رقم الإعلان", + "searchByDemandPrice": "البحث حسب سعر الطلب", + "enterStartPrice": "أدخل سعر البداية", + "enterEndPrice": "أدخل سعر النهاية", + "searchByVehicleType": "البحث حسب نوع المركبة", + "selectVehicleType": "اختر نوع المركبة", + "specialRequestsChats": "دردشات الطلبات الخاصة" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 6e51b39..6ef6e82 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -795,5 +795,13 @@ "ok": "Ok", "continueAsGuest": "Continue as a guest", "loginToViewAppointments": "Please Login to View Appointments", - "itemType": "Item Type" + "itemType": "Item Type", + "searchByAdID": "Search by Ad ID", + "enterAdID": "Enter Ad ID", + "searchByDemandPrice": "Search by demand price", + "enterStartPrice": "Enter start price", + "enterEndPrice": "Enter end price", + "searchByVehicleType": "Search by vehicle type", + "selectVehicleType": "Select vehicle type", + "specialRequestsChats": "Special Requests Chats" } \ No newline at end of file diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index adbb239..7476bc5 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -125,6 +125,7 @@ class ApiConsts { static String vehicleAdsSpecialServicesGet = "${baseUrlServices}api/Common/SpecialService_Get"; static String vehicleAdsSingleStepCreate = "${baseUrlServices}api/Advertisement/AdsSingleStep_Create"; static String vehicleAdsSingleStepUpdate = "${baseUrlServices}api/Advertisement/AdsSingleStep_Update"; + static String vehicleAdsSingleStepUpdateExtend = "${baseUrlServices}api/Advertisement/AdsSingleStep_Update_Extend"; static String vehicleAdsGet = "${baseUrlServices}api/Advertisement/Ads_Get"; static String myAdsReserveGet = "${baseUrlServices}api/Advertisement/AdsReserve_Get"; static String reserveAdsBankDetailsGet = "${baseUrlServices}api/Advertisement/MCBankAccountAd_Get"; @@ -137,6 +138,9 @@ class ApiConsts { static String adsReserveCreate = "${baseUrlServices}api/Advertisement/AdsReserve_Create"; static String reserveAdPaymentOnDealDoneCreate = "${baseUrlServices}api/Advertisement/ReserveAdPaymentOnDealDone_Create"; static String adsExtendDurationCreate = "${baseUrlServices}api/Advertisement/Ads_ExtendDuration_Create"; + static String adsSingleStepDraftCreate = "${baseUrlServices}api/Advertisement/AdsSingleStepDraft_Create"; + static String adsSingleStepDraftUpdate = "${baseUrlServices}api/Advertisement/AdsSingleStepDraft_Update"; + static String getMyDraftAds = "${baseUrlServices}api/Advertisement/AdsDraft_Get"; //Subscription static String getMySubscriptions = "${baseUrlServices}api/ServiceProviders/ProviderSubscription_Get"; @@ -415,8 +419,6 @@ class SignalrConsts { static String receiveMessageGeneral = "ReceiveMessageGeneral"; } - - class GuestConsts { UserInfo userInfo = UserInfo.fromJson({ "id": -1, @@ -456,4 +458,4 @@ class GuestConsts { "deviceType": "1", "deviceToken": null, }); -} \ No newline at end of file +} diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 3dd2764..52d39bf 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -15,6 +15,7 @@ import 'package:mc_common_app/views/payments/payment_methods_view.dart'; import 'package:mc_common_app/views/profile/profile_view.dart'; import 'package:mc_common_app/views/requests/create_request_page.dart'; import 'package:mc_common_app/views/requests/offer_list_page.dart'; +import 'package:mc_common_app/views/requests/providers_chat_list_page.dart'; import 'package:mc_common_app/views/requests/request_detail_page.dart'; import 'package:mc_common_app/views/requests/requests_filter_view.dart'; import 'package:mc_common_app/views/requests/review_request_offer.dart'; @@ -151,6 +152,7 @@ class AppRoutes { static const String offersListPage = "/offersListPage"; static const String reviewRequestOffer = "/reviewRequestOffer"; static const String providerAcceptedRequestsView = "/providerAcceptedRequestsView"; + static const String providersChatListPage = "/providersChatListPage"; //Setting Options static const String settingOptionsFaqs = "/settingOptionsFaqs"; @@ -221,6 +223,7 @@ class AppRoutes { AppRoutes.offersListPage: (context) => OfferListPage(requestId: ModalRoute.of(context)!.settings.arguments as int), AppRoutes.reviewRequestOffer: (context) => const ReviewRequestOffer(), AppRoutes.requestsFilterView: (context) => const RequestsFilterView(), + AppRoutes.providersChatListPage: (context) => const ProvidersChatListPage(), //MediaViewer AppRoutes.mediaViewerScreen: (context) => MediaViewerScreen(images: ModalRoute.of(context)!.settings.arguments as List), diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 9c841ed..45dbe4b 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -373,7 +373,7 @@ class CodegenLoader extends AssetLoader{ "setupTouchID": "يرجى إعداد معرف اللمس", "reenableTouchID": "يرجى إعادة تمكين معرف اللمس", "scanFaceIDAuthenticate": "امسح بصمة الإصبع أو معرف الوجه للتحقق", - "AdDeletedSuccessfully": "تم حذف الإعلان بنجاح!", + "adDeletedSuccessfully": "تم حذف الإعلان بنجاح!", "yourReservationCancelled": "تم إلغاء حجزك.", "adDeactivatedSuccessfully": "تم إلغاء تفعيل الإعلان بنجاح!", "vehicle": { @@ -813,7 +813,15 @@ class CodegenLoader extends AssetLoader{ "continueAsGuest": "الاستمرار كضيف", "loginToViewAppointments": "الرجاء تسجيل الدخول لعرض المواعيد", "itemType": "غرض", - "upgradeSubscription": "قم بترقية اشتراكك" + "upgradeSubscription": "قم بترقية اشتراكك", + "searchByAdID": "البحث برقم الإعلان", + "enterAdID": "أدخل رقم الإعلان", + "searchByDemandPrice": "البحث حسب سعر الطلب", + "enterStartPrice": "أدخل سعر البداية", + "enterEndPrice": "أدخل سعر النهاية", + "searchByVehicleType": "البحث حسب نوع المركبة", + "selectVehicleType": "اختر نوع المركبة", + "specialRequestsChats": "دردشات الطلبات الخاصة" }; static const Map en_US = { "firstTimeLogIn": "First Time Log In", @@ -1612,7 +1620,15 @@ static const Map en_US = { "ok": "Ok", "continueAsGuest": "Continue as a guest", "loginToViewAppointments": "Please Login to View Appointments", - "itemType": "Item Type" + "itemType": "Item Type", + "searchByAdID": "Search by Ad ID", + "enterAdID": "Enter Ad ID", + "searchByDemandPrice": "Search by demand price", + "enterStartPrice": "Enter start price", + "enterEndPrice": "Enter end price", + "searchByVehicleType": "Search by vehicle type", + "selectVehicleType": "Select vehicle type", + "specialRequestsChats": "Special Requests Chats" }; static const Map> mapLocales = {"ar_SA": ar_SA, "en_US": en_US}; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 6255669..cad4d35 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -337,7 +337,7 @@ abstract class LocaleKeys { static const setupTouchID = 'setupTouchID'; static const reenableTouchID = 'reenableTouchID'; static const scanFaceIDAuthenticate = 'scanFaceIDAuthenticate'; - static const AdDeletedSuccessfully = 'AdDeletedSuccessfully'; + static const adDeletedSuccessfully = 'adDeletedSuccessfully'; static const yourReservationCancelled = 'yourReservationCancelled'; static const adDeactivatedSuccessfully = 'adDeactivatedSuccessfully'; static const vehicle_selectVehicleType = 'vehicle.selectVehicleType'; @@ -777,5 +777,13 @@ abstract class LocaleKeys { static const loginToViewAppointments = 'loginToViewAppointments'; static const itemType = 'itemType'; static const upgradeSubscription = 'upgradeSubscription'; + static const searchByAdID = 'searchByAdID'; + static const enterAdID = 'enterAdID'; + static const searchByDemandPrice = 'searchByDemandPrice'; + static const enterStartPrice = 'enterStartPrice'; + static const enterEndPrice = 'enterEndPrice'; + static const searchByVehicleType = 'searchByVehicleType'; + static const selectVehicleType = 'selectVehicleType'; + static const specialRequestsChats = 'specialRequestsChats'; } diff --git a/lib/models/advertisment_models/ads_bank_details_model.dart b/lib/models/advertisment_models/ads_bank_details_model.dart index 47fb8cc..b3b1820 100644 --- a/lib/models/advertisment_models/ads_bank_details_model.dart +++ b/lib/models/advertisment_models/ads_bank_details_model.dart @@ -28,3 +28,17 @@ class AdsBankDetailsModel { return data; } } + +class AdExtensionOrderResponseModel { + final int adsID; + final bool isPaymentRequired; + + AdExtensionOrderResponseModel({required this.adsID, required this.isPaymentRequired}); + + factory AdExtensionOrderResponseModel.fromJson(Map json) { + return AdExtensionOrderResponseModel( + adsID: json['adsID'] as int, + isPaymentRequired: json['isPaymentRequired'] as bool, + ); + } +} diff --git a/lib/models/appointments_models/appointment_list_model.dart b/lib/models/appointments_models/appointment_list_model.dart index 98767d5..f70c830 100644 --- a/lib/models/appointments_models/appointment_list_model.dart +++ b/lib/models/appointments_models/appointment_list_model.dart @@ -28,6 +28,7 @@ class AppointmentListModel { String? branchName; int? branchId; int? servicePaymentStatus; + bool? isPaymentRequiredAtBooking; double? totalAmount; double? remainingAmount; bool? isSelected; @@ -61,6 +62,7 @@ class AppointmentListModel { this.branchName, this.branchId, this.servicePaymentStatus, + this.isPaymentRequiredAtBooking, this.totalAmount, this.remainingAmount, this.isSelected, @@ -102,6 +104,7 @@ class AppointmentListModel { branchName = json['branchName']; branchId = json['branchID']; servicePaymentStatus = json['servicePaymentStatus']; + isPaymentRequiredAtBooking = json['isPaymentRequiredAtBooking']; totalAmount = json['amountTotal']; remainingAmount = json['amountRem']; isSelected = false; diff --git a/lib/models/requests_models/offers_model.dart b/lib/models/requests_models/offers_model.dart index 349fa78..c4c1a6d 100644 --- a/lib/models/requests_models/offers_model.dart +++ b/lib/models/requests_models/offers_model.dart @@ -1,50 +1,30 @@ +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/utils/enums.dart'; + class OffersModel { int? id; int? requestID; int? serviceProviderID; ServiceProvider? serviceProvider; int? offerStatus; + RequestOfferStatusEnum? requestOfferStatusEnum; String? offerStatusText; String? comment; double? price; - OffersModel( - {this.id, - this.requestID, - this.serviceProviderID, - this.serviceProvider, - this.offerStatus, - this.offerStatusText, - this.comment, - this.price}); + OffersModel({this.id, this.requestID, this.serviceProviderID, this.serviceProvider, this.offerStatus, this.offerStatusText, this.comment, this.price}); OffersModel.fromJson(Map json) { id = json['id']; requestID = json['requestID']; serviceProviderID = json['serviceProviderID']; - serviceProvider = json['serviceProvider'] != null - ? ServiceProvider.fromJson(json['serviceProvider']) - : null; + serviceProvider = json['serviceProvider'] != null ? ServiceProvider.fromJson(json['serviceProvider']) : null; offerStatus = json['offerStatus']; + requestOfferStatusEnum = ((json['offerStatus']) as int).toRequestOfferStatusEnum(); offerStatusText = json['offerStatusText']; comment = json['comment']; price = json['price']; } - - Map toJson() { - final Map data = {}; - data['id'] = id; - data['requestID'] = requestID; - data['serviceProviderID'] = serviceProviderID; - if (serviceProvider != null) { - data['serviceProvider'] = serviceProvider!.toJson(); - } - data['offerStatus'] = offerStatus; - data['offerStatusText'] = offerStatusText; - data['comment'] = comment; - data['price'] = price; - return data; - } } class ServiceProvider { @@ -78,32 +58,32 @@ class ServiceProvider { ServiceProvider( {this.providerId, - this.providerGUID, - this.firstName, - this.lastName, - this.name, - this.gender, - this.genderName, - this.mobileNo, - this.email, - this.isEmailVerfied, - this.isCompleted, - this.city, - this.cityName, - this.country, - this.countryName, - this.accountStatus, - this.accountStatusText, - this.activityStatus, - this.activityStatusText, - this.bankName, - this.iBanNo, - this.isActive, - this.subscriptionDate, - this.companyName, - this.currency, - this.branch, - this.requestOffer}); + this.providerGUID, + this.firstName, + this.lastName, + this.name, + this.gender, + this.genderName, + this.mobileNo, + this.email, + this.isEmailVerfied, + this.isCompleted, + this.city, + this.cityName, + this.country, + this.countryName, + this.accountStatus, + this.accountStatusText, + this.activityStatus, + this.activityStatusText, + this.bankName, + this.iBanNo, + this.isActive, + this.subscriptionDate, + this.companyName, + this.currency, + this.branch, + this.requestOffer}); ServiceProvider.fromJson(Map json) { providerId = json['providerId']; diff --git a/lib/models/requests_models/providers_offers_chat_model.dart b/lib/models/requests_models/providers_offers_chat_model.dart new file mode 100644 index 0000000..841bda3 --- /dev/null +++ b/lib/models/requests_models/providers_offers_chat_model.dart @@ -0,0 +1,158 @@ +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/utils/enums.dart'; + +class ProviderOffersChatsModel { + int? id; + int? requestID; + int? serviceProviderID; + ServiceProviderModel? serviceProvider; + int? offerStatus; + RequestOfferStatusEnum? requestOfferStatusEnum; + String? offerStatusText; + String? comment; + String? customerName; + double? price; + String? serviceItem; + String? offeredItemCreatedBy; + String? offeredItemCreatedByName; + String? offeredItemCreatedOn; + String? reqOfferImages; + bool? isDeliveryAvailable; + String? createdOn; + + ProviderOffersChatsModel({ + this.id, + this.requestID, + this.serviceProviderID, + this.serviceProvider, + this.offerStatus, + this.requestOfferStatusEnum, + this.offerStatusText, + this.comment, + this.customerName, + this.price, + this.serviceItem, + this.offeredItemCreatedBy, + this.offeredItemCreatedByName, + this.offeredItemCreatedOn, + this.reqOfferImages, + this.isDeliveryAvailable, + this.createdOn, + }); + + ProviderOffersChatsModel.fromJson(Map json) { + id = json['id']; + requestID = json['requestID']; + serviceProviderID = json['serviceProviderID']; + serviceProvider = json['serviceProvider'] != null ? ServiceProviderModel.fromJson(json['serviceProvider']) : null; + offerStatus = json['offerStatus']; + requestOfferStatusEnum = ((json['offerStatus']) as int).toRequestOfferStatusEnum(); + offerStatusText = json['offerStatusText']; + comment = json['comment']; + customerName = json['customerName']; + price = json['price']; + serviceItem = json['serviceItem']; + offeredItemCreatedBy = json['offeredItemCreatedBy']; + offeredItemCreatedByName = json['offeredItemCreatedByName']; + offeredItemCreatedOn = json['offeredItemCreatedOn']; + reqOfferImages = json['reqOfferImages']; + isDeliveryAvailable = json['isDeliveryAvailable']; + createdOn = json['createdOn']; + } +} + +class ServiceProviderModel { + int? providerId; + String? providerGUID; + String? firstName; + String? lastName; + String? name; + int? gender; + String? genderName; + String? mobileNo; + String? email; + bool? isEmailVerified; + bool? isCompleted; + int? city; + String? cityName; + int? country; + String? countryName; + int? accountStatus; + String? accountStatusText; + int? activityStatus; + String? activityStatusText; + String? bankName; + String? iBanNo; + bool? isActive; + String? subscriptionDate; + String? createdOn; + String? companyName; + String? currency; + String? branch; + bool? isChatted; + bool? isDealership; + + ServiceProviderModel({ + this.providerId, + this.providerGUID, + this.firstName, + this.lastName, + this.name, + this.gender, + this.genderName, + this.mobileNo, + this.email, + this.isEmailVerified, + this.isCompleted, + this.city, + this.cityName, + this.country, + this.countryName, + this.accountStatus, + this.accountStatusText, + this.activityStatus, + this.activityStatusText, + this.bankName, + this.iBanNo, + this.isActive, + this.subscriptionDate, + this.createdOn, + this.companyName, + this.currency, + this.branch, + this.isChatted, + this.isDealership, + }); + + ServiceProviderModel.fromJson(Map json) { + providerId = json['providerId']; + providerGUID = json['providerGUID']; + firstName = json['firstName']; + lastName = json['lastName']; + name = json['name']; + gender = json['gender']; + genderName = json['genderName']; + mobileNo = json['mobileNo']; + email = json['email']; + isEmailVerified = json['isEmailVerfied']; + isCompleted = json['isCompleted']; + city = json['city']; + cityName = json['cityName']; + country = json['country']; + countryName = json['countryName']; + accountStatus = json['accountStatus']; + accountStatusText = json['accountStatusText']; + activityStatus = json['activityStatus']; + activityStatusText = json['activityStatusText']; + bankName = json['bankName']; + iBanNo = json['iBanNo']; + isActive = json['isActive']; + subscriptionDate = json['subscriptionDate']; + createdOn = json['createdOn']; + companyName = json['companyName']; + currency = json['currency']; + branch = json['branch']; + isChatted = json['isChatted']; + isDealership = json['isDealership']; + } +} diff --git a/lib/models/services_models/service_model.dart b/lib/models/services_models/service_model.dart index fcc54f7..e6f34a9 100644 --- a/lib/models/services_models/service_model.dart +++ b/lib/models/services_models/service_model.dart @@ -78,7 +78,7 @@ class ServiceModel { : List.from(json["branchServiceItems"]!.map((x) => ItemData.fromJson(x))), isExpandedOrSelected: false, providerServiceId: 0, - providerServiceName: "", + providerServiceName: json['serviceName'] ?? "", isHomeSelected: false, isActive: json["isActive"] ?? true, servicelocationInfo: CurrentLocationInfoModel(address: '', distanceToBranch: 0.0, homeChargesInCurrentService: 0.0, latitude: 0.0, longitude: 0.0), diff --git a/lib/repositories/ads_repo.dart b/lib/repositories/ads_repo.dart index 87832a3..559ad27 100644 --- a/lib/repositories/ads_repo.dart +++ b/lib/repositories/ads_repo.dart @@ -23,7 +23,7 @@ abstract class AdsRepo { Future> getSpecialServices({required int specialServiceType, required int cityId, required int countryId}); - Future createOrUpdateAd({required AdsCreationPayloadModel adsCreationPayloadModel, required bool isCreateNew}); + Future createOrUpdateAd({required AdsCreationPayloadModel adsCreationPayloadModel, required bool isCreateNew, required bool isExtendAdEditEnabled}); Future> getAllAds({ required bool isMyAds, @@ -33,13 +33,20 @@ abstract class AdsRepo { int? page, }); + Future> getMyDraftAds({int? page}); + Future> getExploreAdsBasedOnFilters({ + List? adIdsList, List? cityIdsList, List? vehicleModelYearIdsList, + List? vehicleTypeIdsList, List? vehicleBrandIdsList, List? createdByRolesIdsList, List? vehicleAdConditionIdsList, List? vehicleAdCreatedDateList, + List? mobileNumbersList, + int? startPriceDemand, + int? endPriceDemand, int page, bool isMyAds = false, }); @@ -120,9 +127,10 @@ class AdsRepoImp implements AdsRepo { } @override - Future createOrUpdateAd({required AdsCreationPayloadModel adsCreationPayloadModel, required bool isCreateNew}) async { + Future createOrUpdateAd({required AdsCreationPayloadModel adsCreationPayloadModel, required bool isCreateNew, required bool isExtendAdEditEnabled}) async { List vehiclePostingImages = []; + log("isExtendAdEditEnabled: $isExtendAdEditEnabled"); log("adsCreationPayloadModel.ads!.adsDurationID,: ${adsCreationPayloadModel.ads!.adsDurationID}"); adsCreationPayloadModel.vehiclePosting!.vehiclePostingImages?.forEach((element) { var imageMap = { @@ -195,7 +203,11 @@ class AdsRepoImp implements AdsRepo { String token = appState.getUser.data!.accessToken ?? ""; GenericRespModel adsGenericModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), - isCreateNew ? ApiConsts.vehicleAdsSingleStepCreate : ApiConsts.vehicleAdsSingleStepUpdate, + isExtendAdEditEnabled + ? ApiConsts.vehicleAdsSingleStepUpdateExtend + : isCreateNew + ? ApiConsts.vehicleAdsSingleStepCreate + : ApiConsts.vehicleAdsSingleStepUpdate, postParams, token: token, ); @@ -203,6 +215,27 @@ class AdsRepoImp implements AdsRepo { return Future.value(adsGenericModel); } + @override + Future> getMyDraftAds({int? page}) async { + var draftAdsParams = { + "PageSize": "30", + "PageIndex": page != null ? page.toString() : "0", + "userID": appState.getUser.data!.userInfo!.userId ?? "", + }; + + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( + token: appState.getUser.data!.accessToken, + (json) => GenericRespModel.fromJson(json), + ApiConsts.vehicleAdsGet, + queryParameters: draftAdsParams, + ); + List vehicleAdsDetails = List.generate( + adsGenericModel.data.length, + (index) => AdDetailsModel.fromJson(adsGenericModel.data[index], true, adsGenericModel.totalItemsCount ?? 1), + ); + return vehicleAdsDetails; + } + @override Future> getAllAds({ required isMyAds, @@ -251,23 +284,33 @@ class AdsRepoImp implements AdsRepo { @override Future> getExploreAdsBasedOnFilters({ + List? adIdsList, List? cityIdsList, List? vehicleModelYearIdsList, + List? vehicleTypeIdsList, List? vehicleBrandIdsList, List? createdByRolesIdsList, List? vehicleAdConditionIdsList, List? vehicleAdCreatedDateList, + List? mobileNumbersList, + int? startPriceDemand, + int? endPriceDemand, int? page, bool isMyAds = false, }) async { var parameters = { + "AdsIDs": adIdsList ?? [], "CityIDs": cityIdsList ?? [], + "VehicleTypeIDs": vehicleTypeIdsList ?? [], "VehicleBrandIDs": vehicleBrandIdsList ?? [], "VehicleModelYearIDs": vehicleModelYearIdsList ?? [], "CreatedByRoles": createdByRolesIdsList ?? [], "AdsStatuses": ["${AdPostStatus.active.getIdFromAdPostStatusEnum()}"], //only Active ADS - "VehicleNew": vehicleAdConditionIdsList ?? [], - "CreatedOn": (vehicleAdCreatedDateList != null && vehicleAdCreatedDateList.isNotEmpty) ? vehicleAdCreatedDateList.first.toString() : "", + "VehicleNew": vehicleAdConditionIdsList != null && vehicleAdConditionIdsList.isNotEmpty ? vehicleAdConditionIdsList.first.toString() : null, + "CreatedOn": (vehicleAdCreatedDateList != null && vehicleAdCreatedDateList.isNotEmpty) ? vehicleAdCreatedDateList.first.toString() : null, + "MobileNumbers": mobileNumbersList ?? [], + "StartPriceDemand": startPriceDemand?.toString(), + "EndPriceDemand": endPriceDemand?.toString(), "isActive": "true", //only Active ADS "isExplore": (!isMyAds).toString(), "PageSize": "30", @@ -280,6 +323,8 @@ class AdsRepoImp implements AdsRepo { }); } + parameters.removeWhere((key, value) => value == null); + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), diff --git a/lib/repositories/request_repo.dart b/lib/repositories/request_repo.dart index 2293cc9..6a72ab6 100644 --- a/lib/repositories/request_repo.dart +++ b/lib/repositories/request_repo.dart @@ -5,11 +5,9 @@ import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/dependency_injection.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; -import 'package:mc_common_app/main.dart'; -import 'package:mc_common_app/models/chat_models/chat_message_model.dart'; import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; -import 'package:mc_common_app/models/requests_models/offers_model.dart'; import 'package:mc_common_app/models/requests_models/provider_offers_model.dart'; +import 'package:mc_common_app/models/requests_models/providers_offers_chat_model.dart'; import 'package:mc_common_app/models/requests_models/request_model.dart'; import 'package:mc_common_app/utils/enums.dart'; @@ -30,7 +28,7 @@ abstract class RequestRepo { required List requestImages, }); - Future> getOffersByRequest({required int requestId, int serviceProviderId = 0}); + Future> getOffersChatByProvider({int requestId = 0, required int serviceProviderId}); Future getOffersFromProvidersByRequest({required int requestId}); @@ -38,6 +36,7 @@ abstract class RequestRepo { Future> getRequestsBasedOnFilters({ required int requestTypeId, + int requestId = 0, int requestStatusId = 0, int reqOfferStatus = 0, int? cityId = 0, @@ -146,6 +145,7 @@ class RequestRepoImp implements RequestRepo { Future> getRequestsBasedOnFilters({ required int requestTypeId, int requestStatusId = 0, + int requestId = 0, int reqOfferStatus = 0, int? cityId = 0, int? vehicleYearId = 0, @@ -275,7 +275,7 @@ class RequestRepoImp implements RequestRepo { } @override - Future> getOffersByRequest({required int requestId, int serviceProviderId = 0}) async { + Future> getOffersChatByProvider({int requestId = 0, required int serviceProviderId}) async { var queryParameters = { "RequestID": requestId.toString(), "ServiceProviderID": serviceProviderId.toString(), @@ -286,9 +286,9 @@ class RequestRepoImp implements RequestRepo { queryParameters: queryParameters, token: appState.getUser.data!.accessToken, ); - List offersList = List.generate( + List offersList = List.generate( genericRespModel.data.length, - (index) => OffersModel.fromJson(genericRespModel.data[index]), + (index) => ProviderOffersChatsModel.fromJson(genericRespModel.data[index]), ); return offersList; } diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index 8088f17..4173e5f 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -80,9 +80,11 @@ class AdVM extends BaseVM { String adPhoneNumberError = ""; // Edit Variables Amir + bool isExtendAdEditEnabled = false; bool isAdEditEnabled = false; AdDetailsModel? previousAdDetails; + List myDraftAds = []; List exploreAds = []; List exploreAdsFilteredList = []; List myAdsFilteredList = []; @@ -179,6 +181,7 @@ class AdVM extends BaseVM { if (myAdsStatusEnums.isEmpty) { myAdsStatusEnums = await commonRepo.getEnumTypeValues(enumTypeID: AppEnums.myAdsFilterEnumId); } + if (vehicleBrandsForFilters.isEmpty) { vehicleBrandsForFilters = await commonRepo.getVehicleBrands(vehicleTypeId: -1); // to get all the brands } @@ -340,6 +343,44 @@ class AdVM extends BaseVM { setState(ViewState.idle); } + bool hasMoreDataForMyDraftsAds = false; + int pageIndexForMyDrafts = 1; + + Future getMyDraftAds() async { + pageIndexForMyAds = 1; + hasMoreDataForMyAds = true; + setState(ViewState.busy); + myDraftAds = await adsRepo.getMyDraftAds(); + notifyListeners(); + setState(ViewState.idle); + } + + Future fetchMoreDraftAds({required AdPostStatus adsStatus}) async { + if (isLoadingMore) return; + hasMoreDataForMyDraftsAds = true; + isLoadingMore = true; + notifyListeners(); + try { + final List newAds = await adsRepo.getMyDraftAds(page: pageIndexForMyDrafts); + if (newAds.isEmpty) { + hasMoreDataForMyDraftsAds = false; + } else { + myDraftAds.addAll(newAds); + pageIndexForMyDrafts++; + if (myDraftAds.length < myDraftAds.last.totalItemsCount!) { + hasMoreDataForMyDraftsAds = true; + } else { + hasMoreDataForMyDraftsAds = false; + } + } + } catch (error) { + isLoadingMore = false; + Utils.showToast(error.toString()); + } + isLoadingMore = false; + notifyListeners(); + } + Future getMyAds() async { pageIndexForMyAds = 1; hasMoreDataForMyAds = true; @@ -418,7 +459,7 @@ class AdVM extends BaseVM { Utils.showToast(respModel.message ?? LocaleKeys.adMarkedAsSold.tr()); updateIsExploreAds(false); applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.sold); //pending for review - navigateReplaceWithName(context, AppRoutes.dashboard); + navigateReplaceWithName(context, AppRoutes.dashboard, arguments: DashboardRouteEnum.fromAdsSubmit); } Future deleteMyAd(BuildContext context, {required int adId}) async { @@ -431,10 +472,10 @@ class AdVM extends BaseVM { return; } Utils.hideLoading(context); - Utils.showToast(LocaleKeys.AdDeletedSuccessfully.tr()); + Utils.showToast(LocaleKeys.adDeletedSuccessfully.tr()); updateIsExploreAds(false); applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.active); //pending for review - navigateReplaceWithName(context, AppRoutes.dashboard); + navigateReplaceWithName(context, AppRoutes.dashboard, arguments: DashboardRouteEnum.fromAdsSubmit); } Future cancelMyAdReservation(BuildContext context, {required int adId, required String reason}) async { @@ -450,7 +491,7 @@ class AdVM extends BaseVM { Utils.showToast(LocaleKeys.yourReservationCancelled.tr()); updateIsExploreAds(false); applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.active); //pending for review - navigateReplaceWithName(context, AppRoutes.dashboard); + navigateReplaceWithName(context, AppRoutes.dashboard, arguments: DashboardRouteEnum.fromAdsSubmit); } Future deactivateTheAd(BuildContext context, {required int adId, String? comment}) async { @@ -467,7 +508,7 @@ class AdVM extends BaseVM { updateDeactivateAdReasonDescription(''); updateIsExploreAds(false); applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.cancelled); //Cacncelled - navigateReplaceWithName(context, AppRoutes.dashboard); + navigateReplaceWithName(context, AppRoutes.dashboard, arguments: DashboardRouteEnum.fromAdsSubmit); } bool isFetchingLists = false; @@ -570,7 +611,16 @@ class AdVM extends BaseVM { SelectionModel vehicleTypeId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); - void updateSelectionVehicleTypeId(SelectionModel id) async { + void updateSelectionVehicleTypeId(SelectionModel id, {bool isForSearch = false}) async { + if (isForSearch) { + VehicleTypeModel vehicleTypeModel = vehicleTypesForFilters.firstWhere((element) => element.id == id.selectedId); + DropValue vehicleTypeVValue = DropValue(vehicleTypeModel.id ?? 0, vehicleTypeModel.vehicleTypeName ?? "", ""); + if (!ifAlreadyExist(list: vehicleTypesAdSearchHistory, value: vehicleTypeVValue)) { + addToVehicleTypesAdSearchHistory(value: vehicleTypeVValue); + } + notifyListeners(); + return; + } vehicleTypeId = id; await getVehicleBrandsByVehicleTypeId(); notifyListeners(); @@ -580,7 +630,7 @@ class AdVM extends BaseVM { Future updateSelectionVehicleBrandId(SelectionModel id, {bool isForSearch = false}) async { if (isForSearch) { - VehicleBrandsModel brand = vehicleBrands.firstWhere((element) => element.id == id.selectedId); + VehicleBrandsModel brand = vehicleBrandsForFilters.firstWhere((element) => element.id == id.selectedId); DropValue brandValue = DropValue(brand.id ?? 0, brand.vehicleBrandDescription ?? "", ""); if (!ifAlreadyExist(list: vehicleBrandsAdSearchHistory, value: brandValue)) { addToVehicleBrandsAdSearchHistory(value: brandValue); @@ -1218,6 +1268,7 @@ class AdVM extends BaseVM { return; } isAdEditEnabled = false; + isExtendAdEditEnabled = false; Utils.hideLoading(context); currentProgressStep = AdCreationSteps.vehicleDetails; resetValues(); @@ -1544,6 +1595,7 @@ class AdVM extends BaseVM { Utils.showToast("${LocaleKeys.error.tr()}: ${genericRespModel.message}"); } else { resetSpecialServiceBottomSheet(); + pop(context); navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.ads); } } catch (e) { @@ -1630,7 +1682,8 @@ class AdVM extends BaseVM { ); AdsCreationPayloadModel adsCreationPayloadModel = AdsCreationPayloadModel(ads: ads, vehiclePosting: vehiclePosting); - GenericRespModel respModel = await adsRepo.createOrUpdateAd(adsCreationPayloadModel: adsCreationPayloadModel, isCreateNew: !isAdEditEnabled); + + GenericRespModel respModel = await adsRepo.createOrUpdateAd(adsCreationPayloadModel: adsCreationPayloadModel, isCreateNew: !isAdEditEnabled, isExtendAdEditEnabled: isExtendAdEditEnabled); Utils.showToast(respModel.message.toString()); @@ -1686,6 +1739,8 @@ class AdVM extends BaseVM { List vehicleConditionsEnum = []; List adOwnerEnumsFilter = []; + List vehicleTypesForFilters = []; + Future populateDataForAdFilter() async { setState(ViewState.busy); pageIndexForExploreAds = 1; @@ -1702,6 +1757,10 @@ class AdVM extends BaseVM { vehicleConditionsEnum = await commonRepo.getEnumTypeValues(enumTypeID: AppEnums.conditionEnumId); } + if (vehicleTypesForFilters.isEmpty) { + vehicleTypesForFilters = await commonRepo.getVehicleTypes(); + } + if (vehicleBrandsForFilters.isEmpty) { vehicleBrandsForFilters = await commonRepo.getVehicleBrands(vehicleTypeId: -1); // to get all the brands } @@ -1726,6 +1785,127 @@ class AdVM extends BaseVM { return false; } + ifAlreadyExistForStringValue({required List list, required String value}) { + int index = list.indexWhere((element) { + return element == value; + }); + + if (index != -1) { + return true; + } + + return false; + } + + //Ad Demand Price + + String adFilterDemandStartPrice = ""; + + void updateAdFilterDemandStartPrice(String title) { + adFilterDemandStartPrice = title; + } + + String adFilterDemandEndPrice = ""; + + void updateAdFilterDemandEndPrice(String title) { + adFilterDemandEndPrice = title; + } + +// Ads Mobile Number Filter + + String currentAdMobilePhoneFilter = ''; + + void onCurrentAdMobilePhoneFilterChanged(var value) { + currentAdMobilePhoneFilter = value; + notifyListeners(); + } + + List adFilterMobilePhoneSearchHistory = []; + + void removeAdFilterMobilePhoneSearchHistory({bool isClear = false, required int index}) { + if (isClear) { + adFilterMobilePhoneSearchHistory.clear(); + notifyListeners(); + return; + } + adFilterMobilePhoneSearchHistory.removeAt(index); + if (adFilterMobilePhoneSearchHistory.isEmpty) { + updateAdsFiltersCounter(adsFiltersCounter - 1); + } + notifyListeners(); + } + + void addAdFilterMobilePhoneSearchHistory({required String value}) { + if (adFilterMobilePhoneSearchHistory.isEmpty) { + updateAdsFiltersCounter(adsFiltersCounter + 1); + } + + if (!ifAlreadyExistForStringValue(list: adFilterMobilePhoneSearchHistory, value: value)) { + adFilterMobilePhoneSearchHistory.add(value); + currentAdMobilePhoneFilter = ""; + } + notifyListeners(); + } + + // Ad ID + + List adsFilterAdIDSearchHistory = []; + String currentAdIDFilter = ''; + + void onCurrentAdIDFilterChanged(var value) { + currentAdIDFilter = value; + notifyListeners(); + } + + void removeAdFilterAdIDSearchHistory({bool isClear = false, required int index}) { + if (isClear) { + adsFilterAdIDSearchHistory.clear(); + notifyListeners(); + return; + } + adsFilterAdIDSearchHistory.removeAt(index); + if (adsFilterAdIDSearchHistory.isEmpty) { + updateAdsFiltersCounter(adsFiltersCounter - 1); + } + notifyListeners(); + } + + void addAdFilterAdIDSearchHistory({required String value}) { + if (adsFilterAdIDSearchHistory.isEmpty) { + updateAdsFiltersCounter(adsFiltersCounter + 1); + } + + if (!ifAlreadyExistForStringValue(list: adsFilterAdIDSearchHistory, value: value)) { + adsFilterAdIDSearchHistory.add(value); + currentAdIDFilter = ""; + } + notifyListeners(); + } + + //TYPES + List vehicleTypesAdSearchHistory = []; + + void removeVehicleTypesAdSearchHistory({bool isClear = false, required int index}) { + if (isClear) { + vehicleTypesAdSearchHistory.clear(); + notifyListeners(); + return; + } + vehicleTypesAdSearchHistory.removeAt(index); + if (vehicleTypesAdSearchHistory.isEmpty) { + updateAdsFiltersCounter(adsFiltersCounter - 1); + } + notifyListeners(); + } + + void addToVehicleTypesAdSearchHistory({required DropValue value}) { + if (vehicleTypesAdSearchHistory.isEmpty) { + updateAdsFiltersCounter(adsFiltersCounter + 1); + } + vehicleTypesAdSearchHistory.add(value); + notifyListeners(); + } + //BRANDS List vehicleBrandsAdSearchHistory = []; @@ -1862,11 +2042,14 @@ class AdVM extends BaseVM { if (vehicleAdCreatedDateSearchHistory.isEmpty) { updateAdsFiltersCounter(adsFiltersCounter + 1); } - if (vehicleAdCreatedDateSearchHistory.isEmpty) { - vehicleAdCreatedDateSearchHistory.add(value); - } else { - vehicleAdCreatedDateSearchHistory.first = value; - } + + vehicleAdCreatedDateSearchHistory.add(value); + + // if (vehicleAdCreatedDateSearchHistory.isEmpty) { + // vehicleAdCreatedDateSearchHistory.add(value); + // } else { + // vehicleAdCreatedDateSearchHistory.first = value; + // } notifyListeners(); } @@ -1903,12 +2086,16 @@ class AdVM extends BaseVM { } void clearAdsFilters() { - vehicleBrandsAdSearchHistory.clear(); + adsFilterAdIDSearchHistory.clear(); + vehicleTypesAdSearchHistory.clear(); vehicleAdOwnerSearchHistory.clear(); vehicleLocationAdSearchHistory.clear(); vehicleYearAdSearchHistory.clear(); vehicleAdCreatedDateSearchHistory.clear(); vehicleAdConditionSearchHistory.clear(); + adFilterMobilePhoneSearchHistory.clear(); + adFilterDemandStartPrice = ''; + adFilterDemandEndPrice = ''; adsFiltersCounter = 0; getExploreAds(); notifyListeners(); @@ -1929,12 +2116,25 @@ class AdVM extends BaseVM { exploreAdsFilteredList.clear(); setState(ViewState.busy); } + + List adIdsList = []; + if (adsFilterAdIDSearchHistory.isNotEmpty) { + for (var element in adsFilterAdIDSearchHistory) { + adIdsList.add(element.toString()); + } + } List cityIdsList = []; if (vehicleLocationAdSearchHistory.isNotEmpty) { for (var element in vehicleLocationAdSearchHistory) { cityIdsList.add(element.id.toString()); } } + List typesIdsList = []; + if (vehicleTypesAdSearchHistory.isNotEmpty) { + for (var element in vehicleTypesAdSearchHistory) { + typesIdsList.add(element.id.toString()); + } + } List brandsIdsList = []; if (vehicleBrandsAdSearchHistory.isNotEmpty) { for (var element in vehicleBrandsAdSearchHistory) { @@ -1970,13 +2170,43 @@ class AdVM extends BaseVM { } } + List phoneNumbersList = []; + if (adFilterMobilePhoneSearchHistory.isNotEmpty) { + for (var element in adFilterMobilePhoneSearchHistory) { + phoneNumbersList.add(element.toString()); + } + } + + int? startPriceDemand; + int? endPriceDemand; + if (adFilterDemandStartPrice.isNotEmpty) { + try { + startPriceDemand = int.parse(adFilterDemandStartPrice); + } catch (e) { + startPriceDemand = null; + } + } + + if (adFilterDemandEndPrice.isNotEmpty) { + try { + endPriceDemand = int.parse(adFilterDemandEndPrice); + } catch (e) { + endPriceDemand = null; + } + } + List list = await adsRepo.getExploreAdsBasedOnFilters( + adIdsList: adIdsList, cityIdsList: cityIdsList, createdByRolesIdsList: adOwnerIdsList, + vehicleTypeIdsList: typesIdsList, vehicleBrandIdsList: brandsIdsList, vehicleModelYearIdsList: vehicleYearIdsList, vehicleAdConditionIdsList: conditionsIdsList, vehicleAdCreatedDateList: createdDatesList, + mobileNumbersList: phoneNumbersList, + startPriceDemand: startPriceDemand, + endPriceDemand: endPriceDemand, page: pageIndex, ); @@ -1993,6 +2223,7 @@ class AdVM extends BaseVM { void onEditUpdateAdPressed({required BuildContext context, required AdDetailsModel previousDetails, required bool isFromExtendAd}) { isAdEditEnabled = true; + isExtendAdEditEnabled = isFromExtendAd; previousAdDetails = previousDetails; autoFillSelectedVehicleType(); autoFillSelectedVehicleAdsDuration(); @@ -2000,22 +2231,25 @@ class AdVM extends BaseVM { navigateWithName(context, AppRoutes.selectAdTypeView, arguments: [AppState().currentAppType == AppType.provider, isFromExtendAd, previousDetails.id]); } - Future createAdExtensionOrder(BuildContext context, {required int adId, required int adsDurationId}) async { + Future> createAdExtensionOrder(BuildContext context, {required int adId, required int adsDurationId}) async { + // [0] Means API response [1] means isPaymentRequired try { Utils.showLoading(context); GenericRespModel respModel = await adsRepo.createAdExtensionOrder(adID: adId, adsDurationId: adsDurationId, specialServiceIds: []); - if (respModel.messageStatus != 1) { + if (respModel.messageStatus != 1 && respModel.data == null) { Utils.hideLoading(context); Utils.showToast(respModel.message ?? LocaleKeys.somethingWrong.tr()); - return false; + return [false, false]; // api fail response } Utils.hideLoading(context); - return respModel.messageStatus == 1; + + final AdExtensionOrderResponseModel adExtensionOrderResponseModel = AdExtensionOrderResponseModel.fromJson(respModel.data); + return [true, adExtensionOrderResponseModel.isPaymentRequired]; } catch (e) { Utils.hideLoading(context); Utils.showToast(e.toString()); - return false; + return [false, false]; // api fail response } } diff --git a/lib/view_models/appointments_view_model.dart b/lib/view_models/appointments_view_model.dart index 9a1efc0..d194778 100644 --- a/lib/view_models/appointments_view_model.dart +++ b/lib/view_models/appointments_view_model.dart @@ -307,7 +307,9 @@ class AppointmentsVM extends BaseVM { } void removeServiceInCurrentAppointment(int index) { - int serviceId = servicesInCurrentAppointment.elementAt(index).serviceProviderServiceId ?? -1; + int serviceId = servicesInCurrentAppointment + .elementAt(index) + .serviceProviderServiceId ?? -1; allSelectedItemsInAppointments.removeWhere((element) => element.serviceProviderServiceId == serviceId); servicesInCurrentAppointment[index].serviceItems!.clear(); servicesInCurrentAppointment.removeAt(index); @@ -370,8 +372,8 @@ class AppointmentsVM extends BaseVM { if (shouldPopulateUpcoming) { myUpComingAppointments = myFilteredAppointmentsForProvider .where((element) => - (element.appointmentStatusEnum == AppointmentStatusEnum.booked || element.appointmentStatusEnum == AppointmentStatusEnum.confirmed) && - (DateHelper.parseStringToDate(element.appointmentDate!).isAfter(DateTime.now()))) + (element.appointmentStatusEnum == AppointmentStatusEnum.booked || element.appointmentStatusEnum == AppointmentStatusEnum.confirmed) && + (DateHelper.parseStringToDate(element.appointmentDate!).isAfter(DateTime.now()))) .toList(); log("myUpComingAppointments: ${myUpComingAppointments.length}"); } @@ -398,8 +400,8 @@ class AppointmentsVM extends BaseVM { if (shouldPopulateUpcoming) { myUpComingAppointments = myFilteredAppointmentsForCustomers .where((element) => - (element.appointmentStatusEnum == AppointmentStatusEnum.booked || element.appointmentStatusEnum == AppointmentStatusEnum.confirmed) && - (DateHelper.parseStringToDate(element.appointmentDate!).isAfter(DateTime.now()))) + (element.appointmentStatusEnum == AppointmentStatusEnum.booked || element.appointmentStatusEnum == AppointmentStatusEnum.confirmed) && + (DateHelper.parseStringToDate(element.appointmentDate!).isAfter(DateTime.now()))) .toList(); } @@ -414,14 +416,19 @@ class AppointmentsVM extends BaseVM { List groupedList = uniqueCustomerIDs.map((id) { List list = appointments.where((item) => item.customerID == id).toList(); - list.sort((a, b) => DateHelper.parseStringToDate(DateHelper.formatDateT(b.appointmentDate!)) - .millisecondsSinceEpoch - .compareTo(DateHelper.parseStringToDate(DateHelper.formatDateT(a.appointmentDate!)).millisecondsSinceEpoch)); + list.sort((a, b) => + DateHelper + .parseStringToDate(DateHelper.formatDateT(b.appointmentDate!)) + .millisecondsSinceEpoch + .compareTo(DateHelper + .parseStringToDate(DateHelper.formatDateT(a.appointmentDate!)) + .millisecondsSinceEpoch)); AppointmentListModel model = list.first; model.customerAppointmentList = list; return model; }).toList(); + return groupedList; } @@ -500,7 +507,7 @@ class AppointmentsVM extends BaseVM { void updateCheckBoxInMergeRequest(int currentIndex) { myFilteredAppointmentsForProvider[selectedAppointmentIndex].customerAppointmentList![currentIndex].isSelected = - !(myFilteredAppointmentsForProvider[selectedAppointmentIndex].customerAppointmentList?[currentIndex].isSelected ?? false); + !(myFilteredAppointmentsForProvider[selectedAppointmentIndex].customerAppointmentList?[currentIndex].isSelected ?? false); int count = countSelected(myFilteredAppointmentsForProvider[selectedAppointmentIndex].customerAppointmentList ?? []); if (count > 1) { @@ -512,7 +519,10 @@ class AppointmentsVM extends BaseVM { } int countSelected(List appointments) { - return appointments.where((appointment) => appointment.isSelected == true).toList().length; + return appointments + .where((appointment) => appointment.isSelected == true) + .toList() + .length; } updateSelectedAppointmentDate({required int dateIndex, required int scheduleIndex}) { @@ -688,13 +698,14 @@ class AppointmentsVM extends BaseVM { Column( children: List.generate( selectedService.serviceItems!.length, - (index) => Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "${selectedService.serviceItems![index].name}".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), - "${selectedService.serviceItems![index].price} ${LocaleKeys.sar.tr()}".toText(fontSize: 12, isBold: true), - ], - ), + (index) => + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${selectedService.serviceItems![index].name}".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), + "${selectedService.serviceItems![index].price} ${LocaleKeys.sar.tr()}".toText(fontSize: 12, isBold: true), + ], + ), ), ), Row( @@ -913,7 +924,10 @@ class AppointmentsVM extends BaseVM { return InfoBottomSheet( title: LocaleKeys.reportComplaint.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), description: Padding( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + padding: EdgeInsets.only(bottom: MediaQuery + .of(context) + .viewInsets + .bottom), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1248,9 +1262,6 @@ class AppointmentsVM extends BaseVM { ifAlreadyExistForStringValue({required List list, required String value}) { int index = list.indexWhere((element) { - log("element: $element"); - log("value: $value"); - return element == value; }); diff --git a/lib/view_models/requests_view_model.dart b/lib/view_models/requests_view_model.dart index f8f9833..c773ceb 100644 --- a/lib/view_models/requests_view_model.dart +++ b/lib/view_models/requests_view_model.dart @@ -19,6 +19,7 @@ import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/models/general_models/widgets_models.dart'; import 'package:mc_common_app/models/requests_models/offers_model.dart'; import 'package:mc_common_app/models/requests_models/provider_offers_model.dart'; +import 'package:mc_common_app/models/requests_models/providers_offers_chat_model.dart'; import 'package:mc_common_app/models/requests_models/request_model.dart'; import 'package:mc_common_app/repositories/common_repo.dart'; import 'package:mc_common_app/repositories/request_repo.dart'; @@ -784,32 +785,32 @@ class RequestsVM extends BaseVM { }); try { - GenericRespModel respModel = await requestRepo.createRequest( - requestTypeId: requestTypeId.selectedId, - vehicleTypeId: vehicleTypeId.selectedId, - brand: brand, - model: model, - year: vehicleYearId.selectedOption, - isNew: vehicleConditionId.selectedId == 1, - // 1 for new, 2 for Used - countryID: vehicleCountryId.selectedId, - cityID: vehicleCityId.selectedId, - price: price.isEmpty ? "1.0" : price, - description: description, - address: address, - isSpecialServiceNeeded: false, - requestImages: requestImages, - ); - Utils.hideLoading(context); - if (respModel.messageStatus == 1) { - log("requestTypeId.selectedId.toRequestTypeEnum(): ${requestTypeId.selectedId.toRequestTypeEnum()}"); - Utils.showToast(LocaleKeys.requestSuccessfullyCreated.tr()); - Navigator.pop(context); - await applyFilterOnRequestsVM(requestsTypeEnum: requestTypeId.selectedId.toRequestTypeEnum()); - resetRequestCreationForm(); - } else { - Utils.showToast(respModel.message.toString()); - } + GenericRespModel respModel = await requestRepo.createRequest( + requestTypeId: requestTypeId.selectedId, + vehicleTypeId: vehicleTypeId.selectedId, + brand: brand, + model: model, + year: vehicleYearId.selectedOption, + isNew: vehicleConditionId.selectedId == 1, + // 1 for new, 2 for Used + countryID: vehicleCountryId.selectedId, + cityID: vehicleCityId.selectedId, + price: price.isEmpty ? "1.0" : price, + description: description, + address: address, + isSpecialServiceNeeded: false, + requestImages: requestImages, + ); + Utils.hideLoading(context); + if (respModel.messageStatus == 1) { + log("requestTypeId.selectedId.toRequestTypeEnum(): ${requestTypeId.selectedId.toRequestTypeEnum()}"); + Utils.showToast(LocaleKeys.requestSuccessfullyCreated.tr()); + Navigator.pop(context); + await applyFilterOnRequestsVM(requestsTypeEnum: requestTypeId.selectedId.toRequestTypeEnum()); + resetRequestCreationForm(); + } else { + Utils.showToast(respModel.message.toString()); + } } catch (e, s) { Utils.hideLoading(context); log(e.toString()); @@ -842,16 +843,20 @@ class RequestsVM extends BaseVM { return isValid; } - Future> getOffersByRequest({required int requestId, required BuildContext context}) async { + List providerOffersChatsList = []; + + Future getProviderOffersChatsList({required int serviceProviderId, required BuildContext context}) async { try { Utils.showLoading(context); - List respModel = await requestRepo.getOffersByRequest(requestId: requestId); + List respModel = await requestRepo.getOffersChatByProvider(serviceProviderId: serviceProviderId); Utils.hideLoading(context); - return respModel; + providerOffersChatsList.clear(); + providerOffersChatsList = respModel; + notifyListeners(); } catch (e) { Utils.showToast(e.toString()); Utils.hideLoading(context); - return []; + return; } } diff --git a/lib/views/advertisement/ads_detail_view/ads_detail_view.dart b/lib/views/advertisement/ads_detail_view/ads_detail_view.dart index 44b8688..d4e7e62 100644 --- a/lib/views/advertisement/ads_detail_view/ads_detail_view.dart +++ b/lib/views/advertisement/ads_detail_view/ads_detail_view.dart @@ -385,9 +385,26 @@ class _AdsDetailViewState extends State { onPressed: () { return deleteAdBottomSheet(context); }, + ).toContainer( + margin: const EdgeInsets.fromLTRB(0, 8, 21, 8), + paddingAll: 0, + borderRadius: 100, + borderColor: MyColors.lightGreyEFColor, + isEnabledBorder: true, + height: 40, + width: 42, + ); + } else if ((widget.adDetails.adPostStatus != AdPostStatus.pendingForPost)) { + actionWidget = + IconButton(icon: const Icon(Icons.chat_outlined, color: Colors.black), onPressed: () => adVM.onMessagesButtonPressed(context: context, adDetailsModel: widget.adDetails)).toContainer( + margin: const EdgeInsets.fromLTRB(0, 8, 21, 8), + paddingAll: 0, + borderRadius: 100, + borderColor: MyColors.lightGreyEFColor, + isEnabledBorder: true, + height: 40, + width: 42, ); - } else { - actionWidget = IconButton(icon: const Icon(Icons.chat_outlined, color: Colors.black), onPressed: () => adVM.onMessagesButtonPressed(context: context, adDetailsModel: widget.adDetails)); } return Scaffold( appBar: CustomAppBar( @@ -395,17 +412,7 @@ class _AdsDetailViewState extends State { profileImageUrl: MyAssets.bnCar, isRemoveBackButton: false, isDrawerEnabled: false, - actions: [ - actionWidget.toContainer( - margin: const EdgeInsets.fromLTRB(0, 8, 21, 8), - paddingAll: 0, - borderRadius: 100, - borderColor: MyColors.lightGreyEFColor, - isEnabledBorder: true, - height: 40, - width: 42, - ), - ], + actions: [actionWidget], onTap: () {}, ), body: Container( diff --git a/lib/views/advertisement/ads_filter_view.dart b/lib/views/advertisement/ads_filter_view.dart index f1fbaeb..e6c1e4a 100644 --- a/lib/views/advertisement/ads_filter_view.dart +++ b/lib/views/advertisement/ads_filter_view.dart @@ -75,6 +75,33 @@ class _AdsFilterViewState extends State { ListView( children: [ 20.height, + SearchEntityWidget( + title: LocaleKeys.searchByAdID.tr(), + isForString: true, + actionWidget: TxtField( + value: adVM.currentAdIDFilter, + onChanged: (value) => adVM.onCurrentAdIDFilterChanged(value), + hint: LocaleKeys.enterAdID.tr(), + postfixWidget: Consumer( + builder: (BuildContext context, AdVM adVM, Widget? child) { + if (adVM.currentAdIDFilter.isEmpty) { + return const SizedBox(); + } + return IconButton( + onPressed: () => adVM.addAdFilterAdIDSearchHistory(value: adVM.currentAdIDFilter), + icon: const Icon(Icons.done, color: MyColors.lightIconColor), + ); + }, + ), + ), + historyContentString: adVM.adsFilterAdIDSearchHistory, + onHistoryItemDeleted: (index) { + adVM.removeAdFilterAdIDSearchHistory(index: index); + }, + onHistoryItemTapped: (DropValue value) => null, + historyContent: const [], // ignore in the case of TEXT FIELD + ), + const Divider(thickness: 1.2).paddingOnly(top: 5, bottom: 5), SearchEntityWidget( title: LocaleKeys.searchByCity.tr(), actionWidget: Builder(builder: (context) { @@ -95,6 +122,27 @@ class _AdsFilterViewState extends State { onHistoryItemTapped: (DropValue value) => null, ), const Divider(thickness: 1.2).paddingOnly(top: 7, bottom: 7), + SearchEntityWidget( + title: LocaleKeys.searchByVehicleType.tr(), + actionWidget: Builder(builder: (context) { + List vehicleTypesDrop = []; + for (var element in adVM.vehicleTypesForFilters) { + vehicleTypesDrop.add(DropValue(element.id?.toInt() ?? 0, element.vehicleTypeName ?? "", "")); + } + + return DropdownField( + (DropValue value) => adVM.updateSelectionVehicleTypeId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true), + list: vehicleTypesDrop, + dropdownValue: adVM.vehicleTypeId.selectedId != -1 ? DropValue(adVM.vehicleTypeId.selectedId, adVM.vehicleTypeId.selectedOption, "") : null, + hint: LocaleKeys.selectVehicleType.tr(), + errorValue: adVM.vehicleTypeId.errorValue, + ); + }), + historyContent: adVM.vehicleTypesAdSearchHistory, + onHistoryItemDeleted: (index) => adVM.removeVehicleTypesAdSearchHistory(index: index), + onHistoryItemTapped: (DropValue value) => null, + ), + const Divider(thickness: 1.2).paddingOnly(top: 7, bottom: 7), SearchEntityWidget( title: LocaleKeys.searchByBrandName.tr(), actionWidget: Builder(builder: (context) { @@ -160,6 +208,60 @@ class _AdsFilterViewState extends State { onHistoryItemTapped: (DropValue value) => null, ), const Divider(thickness: 1.2).paddingOnly(top: 7, bottom: 7), + LocaleKeys.searchByDemandPrice.tr().toText(fontSize: 16, isBold: true), + 8.height, + Row( + children: [ + Expanded( + child: SearchEntityWidget( + title: '', + isForString: false, + actionWidget: TxtField( + onChanged: (value) => adVM.updateAdFilterDemandStartPrice(value), + value: adVM.adFilterDemandStartPrice, + hint: LocaleKeys.enterStartPrice.tr(), + keyboardType: TextInputType.number, + numbersOnly: true, + postfixWidget: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LocaleKeys.sar.tr().toText(fontWeight: MyFonts.Medium, fontSize: 15, color: borderColor, textAlign: TextAlign.center), + ], + ), + ), + historyContentString: const [], + onHistoryItemDeleted: (index) {}, + onHistoryItemTapped: (DropValue value) => null, + historyContent: const [], // ignore in the case of TEXT FIELD + ), + ), + 20.width, + Expanded( + child: SearchEntityWidget( + title: '', + isForString: false, + actionWidget: TxtField( + onChanged: (value) => adVM.updateAdFilterDemandEndPrice(value), + value: adVM.adFilterDemandEndPrice, + hint: LocaleKeys.enterEndPrice.tr(), + keyboardType: TextInputType.number, + numbersOnly: true, + postfixWidget: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LocaleKeys.sar.tr().toText(fontWeight: MyFonts.Medium, fontSize: 15, color: borderColor, textAlign: TextAlign.center), + ], + ), + ), + historyContentString: [], + onHistoryItemDeleted: (index) {}, + onHistoryItemTapped: (DropValue value) => null, + historyContent: const [], // ignore in the case of TEXT FIELD + ), + ), + ], + ), + const Divider(thickness: 1.2).paddingOnly(top: 7, bottom: 7), SearchEntityWidget( title: LocaleKeys.searchByAdOwner.tr(), actionWidget: Builder( @@ -204,7 +306,34 @@ class _AdsFilterViewState extends State { historyContent: adVM.vehicleAdCreatedDateSearchHistory, onHistoryItemDeleted: (index) => adVM.removeVehicleAdCreatedDateSearchHistory(index: index), onHistoryItemTapped: (DropValue value) => null, - ) + ), + const Divider(thickness: 1.2).paddingOnly(top: 5, bottom: 5), + SearchEntityWidget( + title: LocaleKeys.searchByMobileNumber.tr(), + isForString: true, + actionWidget: TxtField( + onChanged: (value) => adVM.onCurrentAdMobilePhoneFilterChanged(value), + value: adVM.currentAdMobilePhoneFilter, + hint: LocaleKeys.enterMobileNumber.tr(), + postfixWidget: Consumer( + builder: (BuildContext context, AdVM adVM, Widget? child) { + if (adVM.currentAdMobilePhoneFilter.isEmpty) { + return const SizedBox(); + } + return IconButton( + onPressed: () => adVM.addAdFilterMobilePhoneSearchHistory(value: adVM.currentAdMobilePhoneFilter), + icon: const Icon(Icons.done, color: MyColors.lightIconColor), + ); + }, + ), + ), + historyContentString: adVM.adFilterMobilePhoneSearchHistory, + onHistoryItemDeleted: (index) { + adVM.removeAdFilterMobilePhoneSearchHistory(index: index); + }, + onHistoryItemTapped: (DropValue value) => null, + historyContent: const [], // ignore in the case of TEXT FIELD + ), ], ).expand(), Container( diff --git a/lib/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart b/lib/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart index f6d224d..44ce445 100644 --- a/lib/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart +++ b/lib/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart @@ -168,10 +168,15 @@ class AdDurationSelectionSheet extends StatelessWidget { return; } else { if (isFromExtendAd && !isUpdateAdSelected) { - bool status = await adVM.createAdExtensionOrder(context, adId: adsID, adsDurationId: adVM.vehicleAdDurationId.selectedId); - log("hereStatus: $status"); - if (status) { + List statuses = + await adVM.createAdExtensionOrder(context, adId: adsID, adsDurationId: adVM.vehicleAdDurationId.selectedId); // [0] Means API response [1] means isPaymentRequired + if (statuses[0] && statuses[1]) { navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.extendAds); + } else if (statuses[0] == true && statuses[1] == false) { + pop(context); + pop(context); + pop(context); + adVM.applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.active); } } else { navigateReplaceWithName(context, AppRoutes.createAdView); diff --git a/lib/views/advertisement/select_ad_type_view.dart b/lib/views/advertisement/select_ad_type_view.dart index ed64799..06b5602 100644 --- a/lib/views/advertisement/select_ad_type_view.dart +++ b/lib/views/advertisement/select_ad_type_view.dart @@ -82,10 +82,15 @@ class SelectAdTypeView extends StatelessWidget { } if (AppState().currentAppType == AppType.provider) { if (isFromExtendAd && !adVM.isAdEditEnabled) { - bool status = await adVM.createAdExtensionOrder(context, adId: adsId, adsDurationId: adVM.vehicleAdDurationId.selectedId); - log("hereStatus: $status"); - if (status) { + List statuses = + await adVM.createAdExtensionOrder(context, adId: adsId, adsDurationId: adVM.vehicleAdDurationId.selectedId); // [0] Means API response [1] means isPaymentRequired + if (statuses[0] && statuses[1]) { navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.extendAds); + } else if (statuses[0] == true && statuses[1] == false) { + pop(context); + pop(context); + pop(context); + adVM.applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.active); } } else { navigateReplaceWithName(context, AppRoutes.createAdView); diff --git a/lib/views/appointments/appointment_detail_view.dart b/lib/views/appointments/appointment_detail_view.dart index 397d54a..8ef0594 100644 --- a/lib/views/appointments/appointment_detail_view.dart +++ b/lib/views/appointments/appointment_detail_view.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; @@ -7,6 +9,7 @@ import 'package:mc_common_app/models/appointments_models/appointment_list_model. import 'package:mc_common_app/models/chat_models/chat_message_model.dart'; import 'package:mc_common_app/models/services_models/service_model.dart'; import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/date_helper.dart'; import 'package:mc_common_app/utils/dialogs_and_bottomsheets.dart'; import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/navigator.dart'; @@ -79,7 +82,7 @@ class AppointmentDetailView extends StatelessWidget { } } - Widget buildBottomActionButton({required AppointmentStatusEnum appointmentStatusEnum, required BuildContext context}) { + Widget buildBottomActionButton({required AppointmentStatusEnum appointmentStatusEnum, required BuildContext context, required bool shouldShowConfirmButton}) { switch (appointmentStatusEnum) { case AppointmentStatusEnum.booked: return Align( @@ -87,13 +90,15 @@ class AppointmentDetailView extends StatelessWidget { child: Row( children: [ getBaseActionButtonWidget(color: MyColors.redColor, onPressed: () => buildCancelAppointmentReasonsBottomSheet(context: context), text: LocaleKeys.cancel.tr()), - 12.width, - getBaseActionButtonWidget( - color: MyColors.greenColor, - onPressed: () { - context.read().onConfirmAppointmentPressed(context: context, appointmentId: appointmentListModel.id); - }, - text: LocaleKeys.confirm.tr()), + if (shouldShowConfirmButton) ...[ + 12.width, + getBaseActionButtonWidget( + color: MyColors.greenColor, + onPressed: () { + context.read().onConfirmAppointmentPressed(context: context, appointmentId: appointmentListModel.id); + }, + text: LocaleKeys.confirm.tr()), + ] ], ), ); @@ -134,13 +139,15 @@ class AppointmentDetailView extends StatelessWidget { child: Row( children: [ getBaseActionButtonWidget(color: MyColors.redColor, onPressed: () => buildCancelAppointmentReasonsBottomSheet(context: context), text: LocaleKeys.cancel.tr()), - 12.width, - getBaseActionButtonWidget( - color: MyColors.greenColor, - onPressed: () { - context.read().onConfirmAppointmentPressed(context: context, appointmentId: appointmentListModel.id); - }, - text: LocaleKeys.confirm.tr()), + if (shouldShowConfirmButton) ...[ + 12.width, + getBaseActionButtonWidget( + color: MyColors.greenColor, + onPressed: () { + context.read().onConfirmAppointmentPressed(context: context, appointmentId: appointmentListModel.id); + }, + text: LocaleKeys.confirm.tr()), + ], ], ), ); @@ -256,6 +263,12 @@ class AppointmentDetailView extends StatelessWidget { @override Widget build(BuildContext context) { AppointmentsVM appointmentsVM = context.read(); + bool shouldShowConfirmButton = false; + final currentDateTime = DateTime.now(); + if ((appointmentListModel.isPaymentRequiredAtBooking ?? false) && appointmentListModel.appointmentDate != null && appointmentListModel.appointmentDate!.isNotEmpty) { + final DateTime appointmentDatetime = DateHelper.parseStringToDate(appointmentListModel.appointmentDate!); + shouldShowConfirmButton = (appointmentListModel.isPaymentRequiredAtBooking ?? false) && (appointmentDatetime.isAfter(currentDateTime)); + } return Scaffold( appBar: CustomAppBar( title: LocaleKeys.appointment.tr(), @@ -337,7 +350,7 @@ class AppointmentDetailView extends StatelessWidget { }, icon: MyAssets.scheduleAppointmentIcon.buildSvg(), ), - if (appointmentListModel.appointmentStatusEnum == AppointmentStatusEnum.booked) ...[ + if (appointmentListModel.appointmentStatusEnum == AppointmentStatusEnum.booked && (shouldShowConfirmButton)) ...[ 10.width, CardButtonWithIcon( title: LocaleKeys.payforAppointment.tr(), @@ -353,7 +366,7 @@ class AppointmentDetailView extends StatelessWidget { ], ], ).toWhiteContainer(width: double.infinity, allPading: 12), - buildBottomActionButton(appointmentStatusEnum: appointmentListModel.appointmentStatusEnum!, context: context), + buildBottomActionButton(appointmentStatusEnum: appointmentListModel.appointmentStatusEnum!, context: context, shouldShowConfirmButton: shouldShowConfirmButton), ], ), ), diff --git a/lib/views/common_fragments/requests_fragment.dart b/lib/views/common_fragments/requests_fragment.dart index f4ceb10..bcdbf2b 100644 --- a/lib/views/common_fragments/requests_fragment.dart +++ b/lib/views/common_fragments/requests_fragment.dart @@ -80,6 +80,18 @@ class MyRequestsFragment extends StatelessWidget { title: LocaleKeys.manageRequests.tr(), isRemoveBackButton: true, actions: [ + if (AppState().currentAppType == AppType.provider) ...[ + Padding( + padding: const EdgeInsets.only(top: 8, bottom: 8, right: 15), + child: const Icon(Icons.messenger_outline_rounded, color: Colors.black, size: 18).toContainer( + borderRadius: 80, + borderColor: MyColors.lightGreyEFColor, + isEnabledBorder: true, + ), + ).onPress(() { + navigateWithName(context, AppRoutes.providersChatListPage); + }) + ], Padding( padding: EdgeInsets.only(top: requestsVM.requestsFiltersCounter > 0 ? 20 : 0, right: 21), child: Badge( @@ -92,7 +104,7 @@ class MyRequestsFragment extends StatelessWidget { ), ).onPress(() { navigateWithName(context, AppRoutes.requestsFilterView); - }) + }), ], ).toViewOnly(context, onTap: () { navigateWithName(context, AppRoutes.loginWithPassword, arguments: false); @@ -201,8 +213,8 @@ class MyRequestsFragment extends StatelessWidget { color: MyColors.white, ), ).toViewOnly(context, onTap: () { - navigateWithName(context, AppRoutes.loginWithPassword, arguments: false); - }) + navigateWithName(context, AppRoutes.loginWithPassword, arguments: false); + }) : null, ); }); diff --git a/lib/views/requests/providers_chat_list_page.dart b/lib/views/requests/providers_chat_list_page.dart new file mode 100644 index 0000000..ec76d36 --- /dev/null +++ b/lib/views/requests/providers_chat_list_page.dart @@ -0,0 +1,180 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/app_state.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/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/requests_models/offers_model.dart'; +import 'package:mc_common_app/models/requests_models/provider_offers_model.dart'; +import 'package:mc_common_app/models/requests_models/providers_offers_chat_model.dart'; +import 'package:mc_common_app/models/requests_models/request_model.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/utils/utils.dart'; +import 'package:mc_common_app/view_models/chat_view_model.dart'; +import 'package:mc_common_app/view_models/requests_view_model.dart'; +import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:easy_localization/easy_localization.dart'; + +class ProvidersChatListPage extends StatefulWidget { + const ProvidersChatListPage({super.key}); + + @override + State createState() => _ProvidersChatListPageState(); +} + +class _ProvidersChatListPageState extends State { + @override + void initState() { + _onRefresh(); + super.initState(); + } + + _onRefresh() async { + scheduleMicrotask(() async { + RequestsVM requestsVM = context.read(); + await requestsVM.getProviderOffersChatsList(serviceProviderId: AppState().getUser.data!.userInfo!.providerId ?? 0, context: context); + }); + } + + Future onChatTapped({required BuildContext context, required int requestID}) async { + final requestsVM = context.read(); + final chatVM = context.read(); + + int requestIndex = requestsVM.myFilteredRequests.indexWhere((request) => request.id == requestID); + log("requestIndex1: $requestIndex"); + + if (requestIndex == -1) { + RequestsTypeEnum requestTypeEnum = requestsVM.requestsTypeFilterOptions.firstWhere((element) => element.isSelected).id.toRequestTypeEnum(); + if (requestTypeEnum == RequestsTypeEnum.specialCarRequest) { + requestTypeEnum = RequestsTypeEnum.serviceRequest; + } else { + requestTypeEnum = RequestsTypeEnum.specialCarRequest; + } + await requestsVM.applyFilterOnRequestsVM(requestsTypeEnum: requestTypeEnum); + + requestIndex = requestsVM.myFilteredRequests.indexWhere((request) => request.id == requestID); + } + + if (requestIndex != -1) { + RequestModel request = requestsVM.myFilteredRequests[requestIndex]; + requestsVM.updateCurrentSelectedRequest(request); + // if (request.requestStatus == RequestStatusEnum.pending || request.requestStatus == RequestStatusEnum.cancelled || request.requestStatus == RequestStatusEnum.expired) { + // Utils.showToast("${LocaleKeys.requests.tr()} ${request.requestStatusName}"); + // return; + // } + RequestDetailPageArguments requestDetailPageArguments = RequestDetailPageArguments(requestIndex: requestIndex, requestModel: request); + ChatViewArgumentsForRequest chatViewArgumentsForRequest = ChatViewArgumentsForRequest( + chatTypeEnum: ChatTypeEnum.requestOffer, + receiverId: requestDetailPageArguments.requestModel.customerID, + senderId: AppState().getUser.data!.userInfo!.userId.toString(), + requestId: requestDetailPageArguments.requestModel.id, + providerIndex: -1, + // This will be only sent in case of customer + requestModel: requestDetailPageArguments.requestModel, + requestIndex: requestDetailPageArguments.requestIndex, // This will be only sent in case of provider + ); + ChatViewArguments chatViewArguments = ChatViewArguments( + chatTypeEnum: ChatTypeEnum.requestOffer, + chatViewArgumentsForRequest: chatViewArgumentsForRequest, + ); + + log("requestIndex2: $requestIndex"); + + await chatVM + .getRequestsChatMessagesForProvider( + customerId: requestDetailPageArguments.requestModel.customerId, + context: navigatorKey.currentState!.overlay!.context, + requestOfferId: 0, + requestId: requestDetailPageArguments.requestModel.id, + customerRequestIndex: requestDetailPageArguments.requestIndex, + ) + .whenComplete(() => navigateWithName(navigatorKey.currentState!.overlay!.context, AppRoutes.chatView, arguments: chatViewArguments)); + } + } + + @override + Widget build(BuildContext context) { + return Consumer(builder: (context, RequestsVM requestsVM, Widget? child) { + return Scaffold( + appBar: CustomAppBar(title: LocaleKeys.specialRequestChat.tr()), + body: RefreshIndicator( + onRefresh: () async { + _onRefresh(); + }, + child: requestsVM.state == ViewState.busy + ? const Center(child: CircularProgressIndicator()) + : requestsVM.providerOffersChatsList.isEmpty + ? Center( + child: LocaleKeys.noOffersShow.tr().toText( + fontSize: 16, + color: MyColors.lightTextColor, + ), + ) + : ListView.separated( + itemCount: requestsVM.providerOffersChatsList.length, + padding: const EdgeInsets.all(16), + itemBuilder: (context, index) { + ProviderOffersChatsModel providerOffersChatsModel = requestsVM.providerOffersChatsList[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.statusContainerChip( + text: Utils.getNameByRequestOfferStatusEnum(providerOffersChatsModel.requestOfferStatusEnum!), + chipColor: Utils.getChipColorByRequestOfferStatusEnum(providerOffersChatsModel.requestOfferStatusEnum!), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (providerOffersChatsModel.customerName ?? "").toText( + fontSize: 16, + isBold: true, + ), + if (providerOffersChatsModel.createdOn != null && providerOffersChatsModel.createdOn!.isNotEmpty) ...[ + DateTime.parse(providerOffersChatsModel.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor, fontSize: 14), + ], + // if (providerOffersChatsModel. != null && offersModel.offerCount! > 0) ...[ + // Center( + // child: "${providerOffersChatsModel.offerCount}".toText( + // color: Colors.white, + // isBold: true, + // fontSize: 10, + // ), + // ).toContainer( + // backgroundColor: MyColors.cancelledColor, + // borderRadius: 100, + // paddingAll: 1, + // width: 22, + // height: 22, + // ), + // ], + ], + ), + 4.height, + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: "${providerOffersChatsModel.comment}".toText(color: MyColors.lightTextColor, fontSize: 14), + ), + const Icon(Icons.arrow_forward, color: MyColors.darkIconColor, size: 18), + ], + ), + ], + ).onPress(() async => await onChatTapped(context: context, requestID: providerOffersChatsModel.requestID ?? 0)).toContainer(isShadowEnabled: true); + }, + separatorBuilder: (context, index) => 16.height, + ), + ), + ); + }); + } +} diff --git a/lib/widgets/common_widgets/search_entity_widget.dart b/lib/widgets/common_widgets/search_entity_widget.dart index 71bee77..c63e108 100644 --- a/lib/widgets/common_widgets/search_entity_widget.dart +++ b/lib/widgets/common_widgets/search_entity_widget.dart @@ -30,8 +30,10 @@ class SearchEntityWidget extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - title.toText(fontSize: 16, isBold: true), - 8.height, + if (title.isNotEmpty) ...[ + title.toText(fontSize: 16, isBold: true), + 8.height, + ], actionWidget, 10.height, if (isForString && historyContentString != null && historyContentString!.isNotEmpty) ...[