diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index e1eed1a..c48ffc0 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -745,6 +745,26 @@ "active": "نشط", "paymentType": "نوع الدفع", "searchByCreatedDate": "البحث حسب تاريخ الإنشاء", + "dealCompleted": "تم إتمام الصفقة", + "theDealNotCompleted": "لم تكتمل الصفقة", + "cancelRequest": "أريد إلغاء الطلب.", + "highPrice": "سعر مرتفع", + "offerNotMatched": "العرض لم يتطابق مع الطلب.", + "offerRejected": "تم رفض العرض.", + "offerAccepted": "تم قبول العرض.", + "you": "أنتم", + "youCannotDeactivateThisServiceRightNow": "لا يمكنك تعطيل هذه الخدمة الآن لأن لديك مواعيد معلقة لهذه الخدمة. تفاصيل آخر موعد هي:", + "done": "تم", + "notice": "إشعار", + "serviceDeactivated": "تم تعطيل الخدمة", + "totalNumberOfServices": "عدد الخدمات:", + "noAvailableOfficesInCity": "لا توجد مكاتب متاحة في مدينتك لخدمتك.", + "searchByItem": "البحث حسب العنصر", + "selectBranchToCopyServices": "حدد الفرع لنسخ العناصر الخاصة بهم إلى هذا الفرع. يمكنك تعديل الاختيار في أي وقت.", + "tapToSeeItems": "اضغط لرؤية العناصر", + "tapToSelect": "اضغط للاختيار", + "noteCopyItemsExplanation": "ملاحظة: ستتمكن من نسخ العناصر من خدمة إلى أخرى في الفئة المحددة. يجب عليك إنشاء الخدمات أولاً ويجب أن تكون معتمدة. ثم ستتمكن من الحصول على الخدمات المتاحة التي يمكنك نسخ جميع العناصر منها أو تحديد العناصر التي تريد نسخها.", + "requestCreatedOn": "تم إنشاء الطلب في", "cityNameMandatory": "المدينة إلزامية", "genderMandatory": "الجنس إلزامي", "updateCity": "تحديث المدينة", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 8d55a7b..0e4472e 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -739,6 +739,26 @@ "active": "Active", "paymentType": "Payment Type", "searchByCreatedDate": "Search By Created Date", + "dealCompleted": "The Deal Completed", + "theDealNotCompleted": "The Deal Not Completed", + "cancelRequest": "I want to cancel the request.", + "highPrice": "High Price", + "offerNotMatched": "The offer did not match the request.", + "offerRejected": "Offer has been Rejected.", + "offerAccepted": "Offer has been Accepted.", + "you": "You", + "youCannotDeactivateThisServiceRightNow": "You cannot deactivate this service right now because you have pending appointments for this service. The last appointment details are:", + "done": "Done", + "notice": "Notice", + "serviceDeactivated": "Service Deactivated", + "totalNumberOfServices": "Number of services:", + "noAvailableOfficesInCity": "There are no available offices in your city for your service.", + "searchByItem": "Search By Item", + "selectBranchToCopyServices": "Select the branch to copy their items to this branch. You can modify the selection at any time.", + "tapToSelect": "Tap to select", + "tapToSeeItems": "Tap to see items", + "noteCopyItemsExplanation": "Note: You will be able to copy items from one service to another in a selected category. You must create the services first and they should be approved. Then you will be able to get the available services from which you can copy all or selected items.", + "requestCreatedOn": "Request created on", "updateUserDetails": "Update User Details", "enterNewFirstName": "Enter First Name", "enterNewLastName": "Enter Last Name", diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 5a97810..74cd78f 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -56,8 +56,10 @@ class ApiConsts { static String servicesGet = "${baseUrlServices}api/ServiceProviders/Services_Get"; static String serviceProviderServiceCreate = "${baseUrlServices}api/ServiceProviders/ServiceProviderService_Create"; static String serviceProviderServiceUpdate = "${baseUrlServices}api/ServiceProviders/ServiceProviderService_Update"; + static String serviceProviderServiceStatusUpdate = "${baseUrlServices}api/ServiceProviders/CategoryAndServiceDeactivateBySP"; static String getProviderServices = "${baseUrlServices}api/ServiceProviders/ServiceProviderService_Get"; static String setScheduleInactive = "${baseUrlServices}api/ServiceProviders/BranchAppointmentSchedule_IsActiveUpdate"; + static String serviceProviderAppointmentGetByCategoryOrService = "${baseUrlServices}api/ServiceProviders/ServiceProviderAppointment_GetByCategoryOrService"; static String serviceProviderServiceGet = "${baseUrlServices}api/ServiceProviders/ServiceProviderService_Get"; static String branchesAndServices = "${baseUrlServices}api/ServiceProviders/ServiceProviderDetail_Get"; @@ -164,7 +166,8 @@ class ApiConsts { static String createRequest = "${baseUrlServices}api/RequestManagement/Request_Create"; static String getRequest = "${baseUrlServices}api/RequestManagement/Request_Get"; static String getRequestOffers = "${baseUrlServices}api/RequestManagement/ReqOffer_Get"; - static String updateRequestOffer = "${baseUrlServices}api/RequestManagement/RequestOffer_UpdateStatus"; + static String updateRequestOfferStatus = "${baseUrlServices}api/RequestManagement/RequestOffer_UpdateStatus"; + static String updateRequestOffer = "${baseUrlServices}api/RequestManagement/ReqOffer_Update"; static String updateRequestCustomer = "${baseUrlServices}api/RequestManagement/Request_StatusUpdate_Customer"; static String updateRequestProvider = "${baseUrlServices}api/RequestManagement/Request_StatusUpdate_Provider"; static String requestOffersSpsGet = "${baseUrlServices}api/RequestManagement/Request_OfferSPs_Get"; diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 674bf8d..bf4948a 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -796,7 +796,7 @@ extension FormatMonthByNumber on String { extension NameExtensions on String { String getInitials() { final name = trim(); - String initials = "U"; + String initials = ""; if (name.isEmpty) { return initials; } diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index bd7c9f3..c41e084 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -761,6 +761,27 @@ class CodegenLoader extends AssetLoader{ "active": "نشط", "paymentType": "نوع الدفع", "searchByCreatedDate": "البحث حسب تاريخ الإنشاء", + "dealCompleted": "تم إتمام الصفقة", + "theDealNotCompleted": "لم تكتمل الصفقة", + "cancelRequest": "أريد إلغاء الطلب.", + "highPrice": "سعر مرتفع", + "offerNotMatched": "العرض لم يتطابق مع الطلب.", + "offerRejected": "تم رفض العرض.", + "offerAccepted": "تم قبول العرض.", + "you": "أنتم", + "youCannotDeactivateThisServiceRightNow": "لا يمكنك تعطيل هذه الخدمة الآن لأن لديك مواعيد معلقة لهذه الخدمة. تفاصيل آخر موعد هي:", + "done": "تم", + "notice": "إشعار", + "serviceDeactivated": "تم تعطيل الخدمة", + "totalNumberOfServices": "عدد الخدمات:", + "noAvailableOfficesInCity": "لا توجد مكاتب متاحة في مدينتك لخدمتك.", + "searchByItem": "البحث حسب العنصر", + "selectBranchToCopyServices": "حدد الفرع لنسخ العناصر الخاصة بهم إلى هذا الفرع. يمكنك تعديل الاختيار في أي وقت.", + "tapToSeeItems": "اضغط لرؤية العناصر", + "tapToSelect": "اضغط للاختيار", + "noteCopyItemsExplanation": "ملاحظة: ستتمكن من نسخ العناصر من خدمة إلى أخرى في الفئة المحددة. يجب عليك إنشاء الخدمات أولاً ويجب أن تكون معتمدة. ثم ستتمكن من الحصول على الخدمات المتاحة التي يمكنك نسخ جميع العناصر منها أو تحديد العناصر التي تريد نسخها.", + "requestCreatedOn": "تم إنشاء الطلب في" + "searchByCreatedDate": "البحث حسب تاريخ الإنشاء", "cityNameMandatory": "المدينة إلزامية", "genderMandatory": "الجنس إلزامي", "updateCity": "تحديث المدينة", @@ -1513,6 +1534,26 @@ static const Map en_US = { "active": "Active", "paymentType": "Payment Type", "searchByCreatedDate": "Search By Created Date", + "dealCompleted": "The Deal Completed", + "theDealNotCompleted": "The Deal Not Completed", + "cancelRequest": "I want to cancel the request.", + "highPrice": "High Price", + "offerNotMatched": "The offer did not match the request.", + "offerRejected": "Offer has been Rejected.", + "offerAccepted": "Offer has been Accepted.", + "you": "You", + "youCannotDeactivateThisServiceRightNow": "You cannot deactivate this service right now because you have pending appointments for this service. The last appointment details are:", + "done": "Done", + "notice": "Notice", + "serviceDeactivated": "Service Deactivated", + "totalNumberOfServices": "Number of services:", + "noAvailableOfficesInCity": "There are no available offices in your city for your service.", + "searchByItem": "Search By Item", + "selectBranchToCopyServices": "Select the branch to copy their items to this branch. You can modify the selection at any time.", + "tapToSelect": "Tap to select", + "tapToSeeItems": "Tap to see items", + "noteCopyItemsExplanation": "Note: You will be able to copy items from one service to another in a selected category. You must create the services first and they should be approved. Then you will be able to get the available services from which you can copy all or selected items.", + "requestCreatedOn": "Request created on", "updateUserDetails": "Update User Details", "enterNewFirstName": "Enter First Name", "enterNewLastName": "Enter Last Name", diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 16cec03..ba90d60 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -724,6 +724,26 @@ abstract class LocaleKeys { static const active = 'active'; static const paymentType = 'paymentType'; static const searchByCreatedDate = 'searchByCreatedDate'; + static const dealCompleted = 'dealCompleted'; + static const theDealNotCompleted = 'theDealNotCompleted'; + static const cancelRequest = 'cancelRequest'; + static const highPrice = 'highPrice'; + static const offerNotMatched = 'offerNotMatched'; + static const offerRejected = 'offerRejected'; + static const offerAccepted = 'offerAccepted'; + static const you = 'you'; + static const youCannotDeactivateThisServiceRightNow = 'youCannotDeactivateThisServiceRightNow'; + static const done = 'done'; + static const notice = 'notice'; + static const serviceDeactivated = 'serviceDeactivated'; + static const totalNumberOfServices = 'totalNumberOfServices'; + static const noAvailableOfficesInCity = 'noAvailableOfficesInCity'; + static const searchByItem = 'searchByItem'; + static const selectBranchToCopyServices = 'selectBranchToCopyServices'; + static const tapToSeeItems = 'tapToSeeItems'; + static const tapToSelect = 'tapToSelect'; + static const noteCopyItemsExplanation = 'noteCopyItemsExplanation'; + static const requestCreatedOn = 'requestCreatedOn'; static const cityNameMandatory = 'cityNameMandatory'; static const genderMandatory = 'genderMandatory'; static const updateCity = 'updateCity'; diff --git a/lib/models/advertisment_models/ads_duration_model.dart b/lib/models/advertisment_models/ads_duration_model.dart index 4602dcf..c435569 100644 --- a/lib/models/advertisment_models/ads_duration_model.dart +++ b/lib/models/advertisment_models/ads_duration_model.dart @@ -7,6 +7,10 @@ class AdsDurationModel { bool? isActive; String? countryName; String? currency; + double? discountValue; + double? discountPercent; + double? priceAfterDiscount; + int? roleID; bool? isSelected; AdsDurationModel({ @@ -18,6 +22,10 @@ class AdsDurationModel { this.isActive, this.countryName, this.currency, + this.discountValue, + this.discountPercent, + this.priceAfterDiscount, + this.roleID, this.isSelected, }); @@ -30,6 +38,10 @@ class AdsDurationModel { isActive = json['isActive']; countryName = json['countryName']; currency = json['currency']; + discountValue = json['discountValue']; + discountPercent = json['discountPercent']; + priceAfterDiscount = json['priceAfterDiscount']; + roleID = json['roleID']; isSelected = false; } @@ -43,6 +55,10 @@ class AdsDurationModel { data['isActive'] = isActive; data['countryName'] = countryName; data['currency'] = currency; + data['discountValue'] = discountValue; + data['discountPercent'] = discountPercent; + data['priceAfterDiscount'] = priceAfterDiscount; + data['roleID'] = roleID; return data; } } diff --git a/lib/models/advertisment_models/ss_photo_schedule_model.dart b/lib/models/advertisment_models/ss_photo_schedule_model.dart index 6aa7f54..8852e2b 100644 --- a/lib/models/advertisment_models/ss_photo_schedule_model.dart +++ b/lib/models/advertisment_models/ss_photo_schedule_model.dart @@ -12,7 +12,7 @@ class SSPhotoOfficeScheduleModel { String? areaName; String? latitude; String? longitude; - int? distanceKM; + double? distanceKM; int? totalItemsCount; List? photoOfficeScheduleSlots; List? customTimeDateSlotList; diff --git a/lib/models/appointments_models/appointment_basic_detail_model.dart b/lib/models/appointments_models/appointment_basic_detail_model.dart new file mode 100644 index 0000000..041315d --- /dev/null +++ b/lib/models/appointments_models/appointment_basic_detail_model.dart @@ -0,0 +1,45 @@ +class AppointmentBasicDetailsModel { + int? serviceSlotID; + String? slotDate; + String? startTime; + String? endTime; + int? appointmentStatusID; + String? appointmentStatusText; + int? serviceProviderID; + int? customerID; + + AppointmentBasicDetailsModel({ + this.serviceSlotID, + this.slotDate, + this.startTime, + this.endTime, + this.appointmentStatusID, + this.appointmentStatusText, + this.serviceProviderID, + this.customerID, + }); + + AppointmentBasicDetailsModel.fromJson(Map json) { + serviceSlotID = json['serviceSlotID']; + slotDate = json['slotDate']; + startTime = json['startTime']; + endTime = json['endTime']; + appointmentStatusID = json['appointmentStatusID']; + appointmentStatusText = json['appointmentStatusText']; + serviceProviderID = json['serviceProviderID']; + customerID = json['customerID']; + } + + Map toJson() { + final Map data = {}; + data['serviceSlotID'] = serviceSlotID; + data['slotDate'] = slotDate; + data['startTime'] = startTime; + data['endTime'] = endTime; + data['appointmentStatusID'] = appointmentStatusID; + data['appointmentStatusText'] = appointmentStatusText; + data['serviceProviderID'] = serviceProviderID; + data['customerID'] = customerID; + return data; + } +} diff --git a/lib/models/general_models/widgets_models.dart b/lib/models/general_models/widgets_models.dart index d4271c9..ae5e061 100644 --- a/lib/models/general_models/widgets_models.dart +++ b/lib/models/general_models/widgets_models.dart @@ -1,9 +1,10 @@ class FilterListModel { String title; + String iconUrl; int id; bool isSelected; - FilterListModel({required this.id, required this.isSelected, required this.title}); + FilterListModel({required this.id, required this.isSelected, this.iconUrl = "", required this.title}); } class SelectionModel { diff --git a/lib/models/provider_branches_models/profile/categroy.dart b/lib/models/provider_branches_models/profile/categroy.dart index 1d0a0c1..8945da0 100644 --- a/lib/models/provider_branches_models/profile/categroy.dart +++ b/lib/models/provider_branches_models/profile/categroy.dart @@ -51,15 +51,17 @@ class CategoryData extends Equatable { this.services, this.branchId, this.branchName, + this.isDeactivated, }); int? id; String? categoryName; String? categoryNameN; - dynamic? serviceCategoryIconUrl; - dynamic? serviceCategoryImageUrl; + String? serviceCategoryIconUrl; + String? serviceCategoryImageUrl; String? branchId; String? branchName; + bool? isDeactivated; List? services; factory CategoryData.fromJson(Map json) => CategoryData( @@ -69,6 +71,7 @@ class CategoryData extends Equatable { serviceCategoryIconUrl: json["serviceCategoryIconUrl"], serviceCategoryImageUrl: json["serviceCategoryImageUrl"], services: [], + isDeactivated: false, ); Map toJson() => { diff --git a/lib/repositories/ads_repo.dart b/lib/repositories/ads_repo.dart index a76b314..d5d83be 100644 --- a/lib/repositories/ads_repo.dart +++ b/lib/repositories/ads_repo.dart @@ -62,8 +62,10 @@ class AdsRepoImp implements AdsRepo { @override Future> getAdsDuration({required int? countryId}) async { + int roleID = appState.getUser!.data!.userInfo!.roleId ?? 0; var param = { "CountryID": countryId.toString() ?? "0", + "roleID": roleID.toString() ?? "0", }; GenericRespModel adsGenericModel = await apiClient.getJsonForObject( token: appState.getUser.data!.accessToken, @@ -213,9 +215,7 @@ class AdsRepoImp implements AdsRepo { "VehicleModelYearIDs": vehicleModelYearIdsList ?? [], "CreatedByRoles": createdByRolesIdsList ?? [], "AdsStatuses": ["${AdPostStatus.active.getIdFromAdPostStatusEnum()}"], //only Active ADS - - // TODO: This has to be converted into list - "VehicleNew": (vehicleAdConditionIdsList != null && vehicleAdConditionIdsList.isNotEmpty) ? vehicleAdConditionIdsList.first.toString() : "", + "VehicleNew": vehicleAdConditionIdsList ?? [], "CreatedOn": (vehicleAdCreatedDateList != null && vehicleAdCreatedDateList.isNotEmpty) ? vehicleAdCreatedDateList.first.toString() : "", "isActive": "true", //only Active ADS "isExplore": "true", diff --git a/lib/repositories/appointment_repo.dart b/lib/repositories/appointment_repo.dart index f13fb45..cf99abe 100644 --- a/lib/repositories/appointment_repo.dart +++ b/lib/repositories/appointment_repo.dart @@ -19,6 +19,7 @@ abstract class AppointmentRepo { List? providerIdsList, List? categoryIdsList, List? serviceIdsList, + List? itemIdsList, List? branchIdsList, }); @@ -223,6 +224,7 @@ class AppointmentRepoImp implements AppointmentRepo { List? providerIdsList, List? categoryIdsList, List? serviceIdsList, + List? itemIdsList, List? branchIdsList, }) async { var params = { @@ -230,6 +232,7 @@ class AppointmentRepoImp implements AppointmentRepo { "ServiceProviderIDs": providerIdsList ?? [], "ProviderBranchIDs": branchIdsList ?? [], "ServiceIDs": serviceIdsList ?? [], + "ServiceItemIDs": itemIdsList ?? [], "CategoryIDs": categoryIdsList ?? [], }; GenericRespModel genericRespModel = await apiClient.getJsonForObject( diff --git a/lib/repositories/branch_repo.dart b/lib/repositories/branch_repo.dart index 6375ee0..8443a6c 100644 --- a/lib/repositories/branch_repo.dart +++ b/lib/repositories/branch_repo.dart @@ -5,6 +5,8 @@ import 'package:mc_common_app/api/api_client.dart'; 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/models/appointments_models/appointment_basic_detail_model.dart'; import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/models/provider_branches_models/branch_detail_model.dart'; import 'package:mc_common_app/models/provider_branches_models/branch_review_model.dart'; @@ -16,6 +18,7 @@ import 'package:mc_common_app/models/provider_branches_models/provider_model.dar import 'package:mc_common_app/models/provider_branches_models/provider_profile_model.dart'; import 'package:mc_common_app/models/services_models/item_model.dart'; import 'package:mc_common_app/models/services_models/service_model.dart'; +import 'package:mc_common_app/utils/enums.dart'; abstract class BranchRepo { Future createBranch({ @@ -70,6 +73,10 @@ abstract class BranchRepo { Future updateService(List> map); + Future updateServiceStatus({required int branchId, required List serviceIds, required ServiceStatusEnum serviceStatusEnum}); + + Future> getAppointmentsByCategoryOrService({required int branchId, required int serviceId}); + Future getMatchedServices(int oldBranchId, int newBranchId, int categoryId); Future duplicateItems({required String providerBranchID, required List items}); @@ -86,7 +93,7 @@ abstract class BranchRepo { Future> getAllNearBranchAndServices({required double latitude, required double longitude}); - Future> getServiceItems(int serviceId); + Future> getServiceItems({required int serviceId}); Future getBranchAndServicesByProviderId(int providerId, {double? latitude, double? longitude}); @@ -230,7 +237,7 @@ class BranchRepoImp implements BranchRepo { GenericRespModel adsGenericModel = await apiClient.getJsonForObject( token: t, - (json) => GenericRespModel.fromJson(json), + (json) => GenericRespModel.fromJson(json), ApiConsts.serviceProviderDDLGet, ); List providersList = List.generate(adsGenericModel.data.length, (index) => ProviderBasicDataModel.fromJson(adsGenericModel.data[index])); @@ -262,7 +269,8 @@ class BranchRepoImp implements BranchRepo { serviceProviderBranchImages.add(imageMap); } - String lat = "0", long = "0"; + String lat = "0", + long = "0"; try { lat = latitude.toString().substring(0, 9); long = longitude.toString().substring(0, 9); @@ -306,6 +314,45 @@ class BranchRepoImp implements BranchRepo { return await apiClient.postJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.serviceProviderServiceUpdate, map, token: t); } + @override + Future updateServiceStatus({required int branchId, required List serviceIds, required ServiceStatusEnum serviceStatusEnum}) async { + List ids = []; + + for (var id in serviceIds) { + ids.add(id.toString()); + } + + int providerID = AppState().getUser.data!.userInfo!.providerId; + var map = { + "ServiceProviderID": providerID.toString(), + "ProviderBranchID": branchId.toString(), + "ProviderServiceIDs": ids, + "Status": serviceStatusEnum.getIdFromServiceStatusEnum().toString(), + }; + String t = AppState().getUser.data!.accessToken ?? ""; + return await apiClient.getJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.serviceProviderServiceStatusUpdate, queryParameters: map, token: t); + } + + @override + Future> getAppointmentsByCategoryOrService({required int branchId, required int serviceId}) async { + var map = { + "ProviderBranchID": branchId.toString(), + "ServiceProviderServiceID": serviceId.toString(), + }; + + String t = AppState().getUser.data!.accessToken ?? ""; + GenericRespModel genericRespModel = await apiClient.getJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.serviceProviderAppointmentGetByCategoryOrService, + queryParameters: map, + token: t, + ); + + List basicAppointmentList = List.generate(genericRespModel.data.length, (index) => AppointmentBasicDetailsModel.fromJson(genericRespModel.data[index])); + + return basicAppointmentList; + } + @override Future getMatchedServices(int oldBranchId, int newBranchId, int categoryId) async { var postParams = { @@ -370,7 +417,7 @@ class BranchRepoImp implements BranchRepo { String t = AppState().getUser.data!.accessToken ?? ""; GenericRespModel adsGenericModel = await apiClient.getJsonForObject( - (json) => GenericRespModel.fromJson(json), + (json) => GenericRespModel.fromJson(json), ApiConsts.getAllNearBranches, token: appState.getUser.data!.accessToken, queryParameters: queryParameters, @@ -398,7 +445,7 @@ class BranchRepoImp implements BranchRepo { "longitude": longitude.toString(), }; GenericRespModel adsGenericModel = await apiClient.getJsonForObject( - (json) => GenericRespModel.fromJson(json), + (json) => GenericRespModel.fromJson(json), ApiConsts.getAllNearBranches, token: appState.getUser.data!.accessToken, queryParameters: queryParameters, @@ -410,7 +457,7 @@ class BranchRepoImp implements BranchRepo { @override Future> getMyRecentBranchesWithServices() async { GenericRespModel adsGenericModel = await apiClient.getJsonForObject( - (json) => GenericRespModel.fromJson(json), + (json) => GenericRespModel.fromJson(json), ApiConsts.getMyRecentBranches, token: appState.getUser.data!.accessToken, ); @@ -419,16 +466,16 @@ class BranchRepoImp implements BranchRepo { } @override - Future> getServiceItems(int serviceId) async { + Future> getServiceItems({required int serviceId}) async { var queryParameters = { "ServiceProviderServiceID": serviceId.toString(), }; GenericRespModel adsGenericModel = await apiClient.getJsonForObject( - (json) => GenericRespModel.fromJson(json), + (json) => GenericRespModel.fromJson(json), ApiConsts.getServiceItems, token: appState.getUser.data!.accessToken, - queryParameters: queryParameters, + queryParameters: serviceId.toString() != "-1" ? queryParameters : null, ); List serviceItems = List.generate(adsGenericModel.data.length, (index) => ItemData.fromJson(adsGenericModel.data[index])); return serviceItems; @@ -445,7 +492,7 @@ class BranchRepoImp implements BranchRepo { postParams.addAll({"Latitude": "$latitude"}); } GenericRespModel adsGenericModel = - await apiClient.getJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.branchesAndServices, token: appState.getUser.data!.accessToken, queryParameters: postParams); + await apiClient.getJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.branchesAndServices, token: appState.getUser.data!.accessToken, queryParameters: postParams); return ProviderProfileModel.fromJson(adsGenericModel.data); } @@ -490,7 +537,7 @@ class BranchRepoImp implements BranchRepo { }; GenericRespModel adsGenericModel = await apiClient.getJsonForObject( - (json) => GenericRespModel.fromJson(json), + (json) => GenericRespModel.fromJson(json), ApiConsts.getAllNearBranches, token: appState.getUser.data!.accessToken, queryParameters: postParams, @@ -504,7 +551,7 @@ class BranchRepoImp implements BranchRepo { var postParams = {"ServiceProviderBranchID": serviceProviderBranchID.toString()}; GenericRespModel adsGenericModel = await apiClient.getJsonForObject( - (json) => GenericRespModel.fromJson(json), + (json) => GenericRespModel.fromJson(json), ApiConsts.getBranchRatings, token: appState.getUser.data!.accessToken, queryParameters: postParams, @@ -518,7 +565,7 @@ class BranchRepoImp implements BranchRepo { final customerID = appState.getUser.data!.userInfo!.customerId; final parameters = {"title": title, "review": review, "ratNo": ratingNo, "serviceProviderBranchID": serviceProviderBranchID, "customerID": "$customerID"}; GenericRespModel adsGenericModel = await apiClient.postJsonForObject( - (json) => GenericRespModel.fromJson(json), + (json) => GenericRespModel.fromJson(json), ApiConsts.createBranchRatings, parameters, token: appState.getUser.data!.accessToken, @@ -534,7 +581,7 @@ class BranchRepoImp implements BranchRepo { "customerID": customerID.toString(), }; GenericRespModel adsGenericModel = await apiClient.postJsonForObject( - (json) => GenericRespModel.fromJson(json), + (json) => GenericRespModel.fromJson(json), ApiConsts.favouriteServiceProviderCreate, parameters, token: appState.getUser.data!.accessToken, @@ -546,7 +593,7 @@ class BranchRepoImp implements BranchRepo { Future removeProviderFromFavourite({required int providerID}) async { final parameters = {"id": providerID.toString()}; GenericRespModel adsGenericModel = await apiClient.postJsonForObject( - (json) => GenericRespModel.fromJson(json), + (json) => GenericRespModel.fromJson(json), ApiConsts.unFavouriteServiceProvider, parameters, token: appState.getUser.data!.accessToken, @@ -561,7 +608,7 @@ class BranchRepoImp implements BranchRepo { var postParams = {"customerID": customerID.toString()}; GenericRespModel adsGenericModel = await apiClient.getJsonForObject( - (json) => GenericRespModel.fromJson(json), + (json) => GenericRespModel.fromJson(json), ApiConsts.favouriteServiceProviderGet, token: appState.getUser.data!.accessToken, queryParameters: postParams, diff --git a/lib/repositories/common_repo.dart b/lib/repositories/common_repo.dart index 24d07f2..d823a16 100644 --- a/lib/repositories/common_repo.dart +++ b/lib/repositories/common_repo.dart @@ -48,7 +48,7 @@ abstract class CommonRepo { Future getCarCheckServiceScheduleDetails({required double lat, required double long}); - Future> getPhotographyServiceScheduleListByOffices({required double lat, required double long}); + Future> getPhotographyServiceScheduleListByOffices({required double lat, required double long, required int cityID}); //TODO: Needs to remove common methods from AD's repo and delete all repeated methods. Future getVehicleDetails({int? vehicleTypeId, int? vehicleBrandId}); @@ -97,10 +97,11 @@ class CommonRepoImp implements CommonRepo { } @override - Future> getPhotographyServiceScheduleListByOffices({required double lat, required double long}) async { + Future> getPhotographyServiceScheduleListByOffices({required double lat, required double long, required int cityID}) async { var params = { "Latitude": lat.toString(), "Longitude": long.toString(), + "cityID": cityID.toString(), }; GenericRespModel genericRespModel = await apiClient.getJsonForObject( token: appState.getUser.data!.accessToken, diff --git a/lib/repositories/request_repo.dart b/lib/repositories/request_repo.dart index 514299d..4e26420 100644 --- a/lib/repositories/request_repo.dart +++ b/lib/repositories/request_repo.dart @@ -6,6 +6,7 @@ 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'; @@ -54,6 +55,19 @@ abstract class RequestRepo { Future updateRequestStatus({required RequestStatusEnum requestStatusEnum, required int requestId}); Future updateRequestOfferStatus({required RequestOfferStatusEnum requestOfferStatusEnum, required int requestOfferId, required String comments}); + + Future updateRequestOfferFromProvider({ + required String message, + required int requestId, + required int offerId, + required String offerPrice, + required bool isDeliveryAvailable, + required String serviceItemName, + required int manufacturedById, + required String manufacturedOn, + required List requestImages, + required RequestOfferStatusEnum requestOfferStatusEnum, + }); } class RequestRepoImp implements RequestRepo { @@ -322,6 +336,44 @@ class RequestRepoImp implements RequestRepo { "offerStatus": requestOfferStatusEnum.getIdFromRequestOfferStatusEnum().toString(), "comments": comments, }; + GenericRespModel genericRespModel = await apiClient.postJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.updateRequestOfferStatus, + queryParameters, + token: appState.getUser.data!.accessToken, + ); + + return genericRespModel; + } + + @override + Future updateRequestOfferFromProvider({ + required String message, + required int requestId, + required int offerId, + required String offerPrice, + required bool isDeliveryAvailable, + required String serviceItemName, + required int manufacturedById, + required String manufacturedOn, + required List requestImages, + required RequestOfferStatusEnum requestOfferStatusEnum, + }) async { + final providerId = appState.getUser.data!.userInfo!.providerId; + + var queryParameters = { + "id": offerId, + "requestID": requestId, + "serviceProviderID": providerId, + "offerStatus": requestOfferStatusEnum.getIdFromRequestOfferStatusEnum(), + "serviceItem": serviceItemName, + "comment": message, + "price": offerPrice, + "offeredItemCreatedBy": manufacturedById.toString(), + "offeredItemCreatedOn": manufacturedOn.toString(), + "reqOfferImages": requestImages, + "isDeliveryAvailable": isDeliveryAvailable + }; GenericRespModel genericRespModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), ApiConsts.updateRequestOffer, diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index 6f1cf99..af993de 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -32,7 +34,6 @@ import 'package:mc_common_app/utils/utils.dart'; import 'package:mc_common_app/view_models/base_view_model.dart'; import 'package:mc_common_app/view_models/chat_view_model.dart'; import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:provider/provider.dart'; class AdVM extends BaseVM { @@ -218,6 +219,13 @@ class AdVM extends BaseVM { notifyListeners(); } + int currentPageForExploreAds = 1; + int currentPageForMyAds = 1; + bool isLoadingMore = false; + bool hasMoreData = true; + + fetchMoreAds() async {} + Future> getAdsByFilter({AdPostStatus? adPostStatus, required bool isMyAds, CreatedByRoleEnum? createdByRoleEnum}) async { return await adsRepo.getAllAds(isMyAds: isMyAds, adPostStatus: adPostStatus, createdByRoleEnum: createdByRoleEnum); } @@ -1406,8 +1414,9 @@ class AdVM extends BaseVM { Future getPhotographyServiceScheduleListByOffices({required double latitude, required double longitude, bool isNeedToRebuild = false}) async { if (isNeedToRebuild) setState(ViewState.busy); + int cityID = AppState().getUser.data!.userInfo!.cityId ?? 1; try { - photoSSSchedulesByOffices = await commonRepo.getPhotographyServiceScheduleListByOffices(lat: latitude, long: longitude); + photoSSSchedulesByOffices = await commonRepo.getPhotographyServiceScheduleListByOffices(lat: latitude, long: longitude, cityID: cityID); if (isNeedToRebuild) setState(ViewState.idle); } catch (e) { if (isNeedToRebuild) setState(ViewState.idle); @@ -1751,11 +1760,16 @@ class AdVM extends BaseVM { if (vehicleAdCreatedDateSearchHistory.isEmpty) { updateAdsFiltersCounter(adsFiltersCounter + 1); } - vehicleAdCreatedDateSearchHistory.add(value); + if (vehicleAdCreatedDateSearchHistory.isEmpty) { + vehicleAdCreatedDateSearchHistory.add(value); + } else { + vehicleAdCreatedDateSearchHistory.first = value; + } + notifyListeners(); } - // Ad Condition +// Ad Condition List vehicleAdConditionSearchHistory = []; void removeVehicleAdConditionSearchHistory({bool isClear = false, required int index}) { diff --git a/lib/view_models/appointments_view_model.dart b/lib/view_models/appointments_view_model.dart index 7547d28..e5f6606 100644 --- a/lib/view_models/appointments_view_model.dart +++ b/lib/view_models/appointments_view_model.dart @@ -1,6 +1,6 @@ import 'dart:developer'; -import 'package:flutter/cupertino.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -44,7 +44,6 @@ import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; -import 'package:easy_localization/easy_localization.dart'; class AppointmentsVM extends BaseVM { final CommonRepo commonRepo; @@ -252,17 +251,6 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - // String pickedHomeLocation = ""; - // - // void updatePickedHomeLocation(String value) { - // pickedHomeLocation = value; - // pickHomeLocationError = ""; - // if (currentServiceSelection != null) { - // currentServiceSelection!.homeLocation = value; - // } - // notifyListeners(); - // } - SelectionModel branchSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); void updateProviderCategoryId(SelectionModel id) { @@ -271,7 +259,7 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - List branchesFilterOptions = []; + List branchesCategoriesFilterOptions = []; List servicesInCurrentAppointment = []; ServiceModel? currentServiceSelection; @@ -305,9 +293,6 @@ class AppointmentsVM extends BaseVM { return; } - servicesInCurrentAppointment.forEach((i) { - log("hlo1: ${i.serviceDescription} ${i.servicelocationInfo.homeChargesInCurrentService}"); - }); if (!(currentServiceSelection!.isAllowAppointmentHome ?? false)) { updateIsHomeTapped(false); return; @@ -321,19 +306,8 @@ class AppointmentsVM extends BaseVM { distanceToBranch: currentLocationInfoModel!.distanceToBranch, homeChargesInCurrentService: double.parse(currentServiceSelection!.rangePricePerKm ?? "0.0"), ); - - // for (var service in servicesInCurrentAppointment) { - // service.servicelocationInfo.address = currentLocationInfoModel!.address; - // service.servicelocationInfo.latitude = currentLocationInfoModel!.latitude; - // service.servicelocationInfo.longitude = currentLocationInfoModel!.longitude; - // service.servicelocationInfo.distanceToBranch = currentLocationInfoModel!.distanceToBranch; - // } } - servicesInCurrentAppointment.forEach((i) { - log("hlo2: ${i.serviceDescription} ${i.servicelocationInfo.homeChargesInCurrentService}"); - }); - notifyListeners(); } @@ -384,7 +358,7 @@ class AppointmentsVM extends BaseVM { } applyFilterOnAppointmentsVM({required AppointmentStatusEnum appointmentStatusEnum, bool isNeedCustomerFilter = false}) { - log("appointmentStatusEnum: ${appointmentStatusEnum}"); + log("appointmentStatusEnum: $appointmentStatusEnum"); // isNeedCustomerFilter IS ONLY FOR THE PROVIDER APP if (appointmentsFilterOptions.isEmpty) return; for (var value in appointmentsFilterOptions) { @@ -424,10 +398,14 @@ class AppointmentsVM extends BaseVM { // Create a list of CustomerData instances myFilteredAppointments2 = uniqueCustomerIDs.map((id) { List list = myFilteredAppointments.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)); AppointmentListModel model = list.first; model.customerAppointmentList = list; return model; }).toList(); + // customersAppointments = uniqueCustomerIDs.map((id) { // List list = myFilteredAppointments // .where((item) => item.customerID == id) @@ -467,13 +445,15 @@ class AppointmentsVM extends BaseVM { myAppointments = await appointmentRepo.getMyAppointmentsForCustomersByFilters(); // myFilteredAppointments = myAppointments; + + setState(ViewState.idle); + applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments); + myUpComingAppointments = myAppointments .where((element) => (element.appointmentStatusEnum == AppointmentStatusEnum.booked || element.appointmentStatusEnum == AppointmentStatusEnum.confirmed) && (DateHelper.parseStringToDate(element.appointmentDate!).isAfter(DateTime.now()))) .toList(); - setState(ViewState.idle); - applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments); notifyListeners(); } @@ -502,6 +482,7 @@ class AppointmentsVM extends BaseVM { (element.appointmentStatusEnum == AppointmentStatusEnum.confirmed || element.appointmentStatusEnum == AppointmentStatusEnum.booked) && (DateHelper.parseStringToDate(element.appointmentDate!).isAfter(DateTime.now()))) .toList(); + notifyListeners(); applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments, isNeedCustomerFilter: true); setState(ViewState.idle); } @@ -650,20 +631,20 @@ class AppointmentsVM extends BaseVM { } Future applyFilterOnBranches({required int index}) async { - if (branchesFilterOptions.isEmpty) return; - for (var value in branchesFilterOptions) { + if (branchesCategoriesFilterOptions.isEmpty) return; + for (var value in branchesCategoriesFilterOptions) { value.isSelected = false; } - branchesFilterOptions[index].isSelected = true; + branchesCategoriesFilterOptions[index].isSelected = true; - await getBranchesBasedOnCategoryFilters(categoryId: branchesFilterOptions[index].id); + await getBranchesBasedOnCategoryFilters(categoryId: branchesCategoriesFilterOptions[index].id); notifyListeners(); } - Future> getServiceItems(int serviceId) async { + Future> getServiceItems({required int serviceId}) async { setState(ViewState.busy); serviceItemsFromApi.clear(); - serviceItemsFromApi = await branchRepo.getServiceItems(serviceId); + serviceItemsFromApi = await branchRepo.getServiceItems(serviceId: serviceId); selectedSubServicesCounter = 0; for (var item in serviceItemsFromApi) { if (ifItemAlreadySelected(item.id!)) { @@ -1043,14 +1024,14 @@ class AppointmentsVM extends BaseVM { List branchServices = []; Future populateBranchesFilterList() async { - if (branchesFilterOptions.isNotEmpty) return; - branchesFilterOptions.clear(); + if (branchesCategoriesFilterOptions.isNotEmpty) return; + branchesCategoriesFilterOptions.clear(); setOnlyState(ViewState.busy); Category category = await branchRepo.fetchBranchCategory(); category.data?.forEach((element) { - branchesFilterOptions.add(FilterListModel(id: element.id ?? 0, isSelected: false, title: element.categoryName ?? "N/A")); + branchesCategoriesFilterOptions.add(FilterListModel(id: element.id ?? 0, isSelected: false, iconUrl: element.serviceCategoryIconUrl ?? "", title: element.categoryName ?? "N/A")); }); - branchesFilterOptions.insert(0, FilterListModel(id: 0, isSelected: true, title: "All Branches")); + branchesCategoriesFilterOptions.insert(0, FilterListModel(id: 0, isSelected: true, title: "All Branches")); notifyListeners(); setState(ViewState.idle); } @@ -1060,7 +1041,7 @@ class AppointmentsVM extends BaseVM { if (isNeedToRebuild) setState(ViewState.busy); if (isFromRefresh) { - var selectedFilter = branchesFilterOptions.firstWhere((element) => element.isSelected); + var selectedFilter = branchesCategoriesFilterOptions.firstWhere((element) => element.isSelected); log("selectedFilter: ${selectedFilter.id}"); if (selectedFilter.id == 0) { @@ -1301,8 +1282,9 @@ class AppointmentsVM extends BaseVM { } List categoryDropList = []; - List servicesDropList = []; List providersDropList = []; + List servicesDropList = []; + List itemsDropList = []; Future fetchAllProviders() async { if (providersDropList.isNotEmpty) return; @@ -1349,10 +1331,30 @@ class AppointmentsVM extends BaseVM { setState(ViewState.idle); } + Future fetchAllItems() async { + if (itemsDropList.isNotEmpty) return; + itemsDropList.clear(); + setState(ViewState.busy); + List itemsList = await branchRepo.getServiceItems(serviceId: -1); // to get all services + + for (ItemData element in itemsList) { + if (element.name != null && element.name!.isNotEmpty) { + itemsDropList.add( + DropValue( + element.id ?? 0, + element.name ?? "", + "", + ), + ); + } + } + setState(ViewState.idle); + } + Future populateDataForBranchesFilter() async { - await fetchAllProviders(); // saudi arabia - await fetchAllCategories(); // saudi arabia - await fetchAllServices(); // saudi arabia + await fetchAllProviders(); + await fetchAllCategories(); + await fetchAllServices(); updateBranchFilterCurrentDistance(25.0); } @@ -1446,6 +1448,7 @@ class AppointmentsVM extends BaseVM { await fetchAllProviders(); await fetchAllCategories(); await fetchAllServices(); + await fetchAllItems(); } int appointmentFiltersCounter = 0; @@ -1621,14 +1624,53 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } + List appointmentFilterItemsSearchHistory = []; + + void removeAppointmentFilterItemsSearchHistory({bool isClear = false, required int index}) { + if (isClear) { + appointmentFilterItemsSearchHistory.clear(); + notifyListeners(); + return; + } + appointmentFilterItemsSearchHistory.removeAt(index); + if (appointmentFilterItemsSearchHistory.isEmpty) { + updateAppointmentFiltersCounter(appointmentFiltersCounter - 1); + // appointmentFilterSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + } + notifyListeners(); + } + + void addAppointmentFilterItemsSearchHistory({required DropValue value}) { + if (appointmentFilterItemsSearchHistory.isEmpty) { + updateAppointmentFiltersCounter(appointmentFiltersCounter + 1); + } + appointmentFilterItemsSearchHistory.add(value); + notifyListeners(); + } + + SelectionModel appointmentFilterSelectedItemId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + + void updateAppointmentFilterSelectedItemId(SelectionModel id, {bool isForSearch = false}) async { + if (isForSearch) { + DropValue itemsDrop = itemsDropList.firstWhere((element) => element.id == id.selectedId); + if (!ifAlreadyExist(list: appointmentFilterItemsSearchHistory, value: itemsDrop)) { + addAppointmentFilterItemsSearchHistory(value: itemsDrop); + } + } + // appointmentFilterSelectedServiceId = id; + notifyListeners(); + } + void clearAppointmentFilterSelections() { appointmentFilterSelectedProviderId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); appointmentFilterSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); appointmentFilterSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + appointmentFilterSelectedItemId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); appointmentFilterSelectedBranchId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); } void clearAppointmentFilters() { + appointmentFilterItemsSearchHistory.clear(); appointmentFilterServicesSearchHistory.clear(); appointmentFilterCategorySearchHistory.clear(); appointmentFilterProviderSearchHistory.clear(); @@ -1659,6 +1701,12 @@ class AppointmentsVM extends BaseVM { servicesIdsList.add(element.id.toString()); } } + List itemIdsList = []; + if (appointmentFilterItemsSearchHistory.isNotEmpty) { + for (var element in appointmentFilterItemsSearchHistory) { + itemIdsList.add(element.id.toString()); + } + } List branchesIdsList = []; if (appointmentFilterBranchSearchHistory.isNotEmpty) { for (var element in appointmentFilterBranchSearchHistory) { @@ -1670,6 +1718,7 @@ class AppointmentsVM extends BaseVM { providerIdsList: providersIdsList, categoryIdsList: categoryIdsList, serviceIdsList: servicesIdsList, + itemIdsList: itemIdsList, branchIdsList: branchesIdsList, ); applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments); diff --git a/lib/view_models/chat_view_model.dart b/lib/view_models/chat_view_model.dart index 9ea2a21..763b0d3 100644 --- a/lib/view_models/chat_view_model.dart +++ b/lib/view_models/chat_view_model.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -23,7 +24,6 @@ import 'package:mc_common_app/view_models/base_view_model.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; import 'package:provider/provider.dart'; import 'package:signalr_core/signalr_core.dart'; -import 'package:easy_localization/easy_localization.dart'; class ChatVM extends BaseVM { final ChatRepo chatRepo; @@ -76,9 +76,31 @@ class ChatVM extends BaseVM { } } - List indexesForCancelSpecialCarOffer = [0, 1, 6, 7]; - List indexesForCancelSparePartOffer = [2, 7]; - List indexesForRejectOffer = [3, 4, 5, 7]; + List dealOptionsModelList = [ + OfferRequestCommentModel( + index: 0, + isSelected: false, + title: LocaleKeys.dealCompleted.tr(), + ), + OfferRequestCommentModel( + index: 1, + isSelected: false, + title: LocaleKeys.theDealNotCompleted.tr(), + ), + ]; + + updateIsSelectedInDealOptionsModelList(int index, bool value) { + for (var element in dealOptionsModelList) { + element.isSelected = false; + } + dealOptionsModelList[index].isSelected = true; + notifyListeners(); + } + + List indexesForCancelSpecialCarOffer = [0, 1, 5, 6, 10]; + List indexesForCancelSparePartOffer = [2, 5, 6, 10]; + List indexesForRejectOffer = [3, 4, 10]; + List indexesForDealNotCompleted = [7, 4, 8, 9, 10]; List offerRejectModelList = [ OfferRequestCommentModel( @@ -119,6 +141,21 @@ class ChatVM extends BaseVM { OfferRequestCommentModel( index: 7, isSelected: false, + title: LocaleKeys.cancelRequest.tr(), + ), + OfferRequestCommentModel( + index: 8, + isSelected: false, + title: LocaleKeys.offerNotMatched.tr(), + ), + OfferRequestCommentModel( + index: 9, + isSelected: false, + title: LocaleKeys.testTheService.tr(), + ), + OfferRequestCommentModel( + index: 10, + isSelected: false, title: LocaleKeys.otherVar.tr(), ), ]; diff --git a/lib/view_models/requests_view_model.dart b/lib/view_models/requests_view_model.dart index 7df80f6..282c282 100644 --- a/lib/view_models/requests_view_model.dart +++ b/lib/view_models/requests_view_model.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:mc_common_app/classes/app_state.dart'; @@ -215,6 +216,11 @@ class RequestsVM extends BaseVM { List pickedVehicleImages = []; String vehicleImageError = ""; + void addImageToPickedVehicleImages(ImageModel imageModel) { + pickedVehicleImages.add(imageModel); + notifyListeners(); + } + void removeImageFromList(String filePath) { int index = pickedVehicleImages.indexWhere((element) => element.filePath == filePath); if (index == -1) { @@ -714,10 +720,17 @@ class RequestsVM extends BaseVM { brandForSearch = v; } - Future convertFileToRequestPostingImages({required File file}) async { - List imageBytes = await file.readAsBytes(); - String image = base64Encode(imageBytes); - RequestPostingImages vehiclePostingImages = RequestPostingImages(requestImage: image); + Future convertFileToRequestPostingImages({required ImageModel imageModel}) async { + RequestPostingImages vehiclePostingImages; + log("imageModelxx: ${imageModel.isFromNetwork}"); + if (imageModel.isFromNetwork == true) { + vehiclePostingImages = RequestPostingImages(id: imageModel.id, requestImage: imageModel.filePath); + } else { + File file = File(imageModel.filePath!); + List imageBytes = await file.readAsBytes(); + String image = base64Encode(imageBytes); + vehiclePostingImages = RequestPostingImages(requestImage: image); + } return vehiclePostingImages; } @@ -725,7 +738,7 @@ class RequestsVM extends BaseVM { List requestImages = []; for (var image in pickedVehicleImages) { - var value = await convertFileToRequestPostingImages(file: File(image.filePath!)); + var value = await convertFileToRequestPostingImages(imageModel: image); requestImages.add(value); } @@ -742,37 +755,37 @@ class RequestsVM extends BaseVM { requestImages.add(element.toJson()); }); - 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) { - Utils.showToast(LocaleKeys.requestSuccessfullyCreated.tr()); - Navigator.pop(context); - resetRequestCreationForm(); - await applyFilterOnRequestsVM(requestsTypeEnum: requestTypeId.selectedId == 1 ? RequestsTypeEnum.specialCarRequest : RequestsTypeEnum.serviceRequest); - } else { - Utils.showToast(respModel.message.toString()); - } - } catch (e, s) { - Utils.hideLoading(context); - log(e.toString()); - Utils.showToast(e.toString()); + // 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) { + 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()); + // Utils.showToast(e.toString()); + // } } } @@ -888,17 +901,23 @@ class RequestsVM extends BaseVM { notifyListeners(); } - Future onActionRequestTapped({required BuildContext context, required RequestStatusEnum requestStatusEnum, required int requestId}) async { - Utils.showLoading(context); + Future onActionRequestTapped({required BuildContext context, required RequestStatusEnum requestStatusEnum, required int requestId, bool needLoading = true}) async { + if (needLoading) { + Utils.showLoading(context); + } try { GenericRespModel genericRespModel = await requestRepo.updateRequestStatus(requestStatusEnum: requestStatusEnum, requestId: requestId); Utils.showToast(genericRespModel.message.toString()); - Utils.hideLoading(context); + if (needLoading) { + Utils.hideLoading(context); + } return genericRespModel.messageStatus == 1; } catch (e) { logger.i(e.toString()); - Utils.showToast(e.toString()); - Utils.hideLoading(context); + // Utils.showToast(e.toString()); + if (needLoading) { + Utils.hideLoading(context); + } return false; } } @@ -938,17 +957,52 @@ class RequestsVM extends BaseVM { notifyListeners(); } - MessageImageModel convertFileToMessageImageModel({required File file, required int latestOfferId}) { - List imageBytes = file.readAsBytesSync(); - String image = base64Encode(imageBytes); - MessageImageModel vehiclePostingImages = MessageImageModel(imageStr: image, id: 0, isFromNetwork: false, reqOfferID: latestOfferId, imagePath: file.path); - return vehiclePostingImages; + bool isBase64String(String input) { + // This regex checks if the string is a valid Base64 string + final base64Regex = RegExp(r'^[A-Za-z0-9+/=]+$'); + + // Check if the string matches the Base64 pattern + return base64Regex.hasMatch(input) && (input.length % 4 == 0); + } + + MessageImageModel convertFileToMessageImageModel({required ImageModel imageModel, required int offerId}) { + MessageImageModel offerImages; + + if (imageModel.isFromNetwork ?? false) { + offerImages = MessageImageModel( + id: imageModel.id, + imageStr: imageModel.filePath, + isFromNetwork: true, + reqOfferID: offerId, + ); + } else { + File? file; + List imageBytes = []; + String image = ''; + if (!isBase64String(imageModel.filePath!)) { + file = File(imageModel.filePath!); + imageBytes = file.readAsBytesSync(); + image = base64Encode(imageBytes); + } else { + image = imageModel.filePath!; + } + + offerImages = MessageImageModel( + id: 0, + imageStr: image, + isFromNetwork: false, + reqOfferID: offerId, + imagePath: file?.path, + ); + } + + return offerImages; } - List getMessagesImageList({required int latestOfferId}) { + List getMessagesImageList({required int offerId}) { List requestImages = []; for (var image in pickedVehicleImages) { - var value = convertFileToMessageImageModel(file: File(image.filePath!), latestOfferId: latestOfferId); + var value = convertFileToMessageImageModel(imageModel: image, offerId: offerId); requestImages.add(value); } return requestImages; @@ -961,6 +1015,66 @@ class RequestsVM extends BaseVM { notifyListeners(); } + Future updateOfferFromProvider({ + required String message, + required int requestId, + required int offerId, + required String offerPrice, + required bool isDeliveryAvailable, + required String serviceItemName, + required int manufacturedById, + required String manufacturedOn, + required int receiverId, + required int customerRequestIndex, + required BuildContext context, + }) async { + try { + if (isSendOfferValidated()) { + Utils.showLoading(context); + + List images = getMessagesImageList(offerId: offerId); + List offerImages = []; + images.forEach((element) { + offerImages.add(element.toJson()); + }); + GenericRespModel genericRespModel = await requestRepo.updateRequestOfferFromProvider( + message: message, + requestId: requestId, + offerId: offerId, + offerPrice: offerPrice, + isDeliveryAvailable: isDeliveryAvailable, + serviceItemName: serviceItemName, + manufacturedById: manufacturedById, + manufacturedOn: manufacturedOn, + requestImages: offerImages, + requestOfferStatusEnum: RequestOfferStatusEnum.offer, + ); + + Utils.hideLoading(context); + + if (genericRespModel.messageStatus == 1) { + Utils.showToast(genericRespModel.message ?? ""); + final chatVM = context.read(); + + await chatVM.getRequestsChatMessagesForProvider( + context: context, + customerId: receiverId, + requestOfferId: 0, + // to get all the messages + requestId: requestId, + customerRequestIndex: customerRequestIndex, + ); + resetSendOfferBottomSheet(); + pop(context); + } + } + } catch (e, s) { + Utils.hideLoading(context); + log(e.toString()); + Utils.showToast(e.toString()); + } + } + Future onSendOfferPressed({ required BuildContext context, required String receiverId, @@ -974,10 +1088,11 @@ class RequestsVM extends BaseVM { required bool isDeliveryAvailable, required int requestIndex, required bool isFromChatScreen, + required int? offerId, }) async { if (isSendOfferValidated()) { final chatVM = context.read(); - List images = getMessagesImageList(latestOfferId: context.read().latestOfferId); + List images = getMessagesImageList(offerId: context.read().latestOfferId); List offerImages = []; images.forEach((element) { offerImages.add(element.toJson()); diff --git a/lib/view_models/service_view_model.dart b/lib/view_models/service_view_model.dart index 933cc2c..75c5109 100644 --- a/lib/view_models/service_view_model.dart +++ b/lib/view_models/service_view_model.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; @@ -6,8 +7,10 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.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/appointments_models/appointment_basic_detail_model.dart'; import 'package:mc_common_app/models/general_models/enums_model.dart'; import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/models/general_models/widgets_models.dart'; @@ -24,12 +27,17 @@ import 'package:mc_common_app/models/user_models/country.dart'; import 'package:mc_common_app/repositories/branch_repo.dart'; import 'package:mc_common_app/repositories/common_repo.dart'; import 'package:mc_common_app/services/common_services.dart'; +import 'package:mc_common_app/theme/colors.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'; import 'package:mc_common_app/utils/utils.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/view_models/base_view_model.dart'; +import 'package:mc_common_app/widgets/button/show_fill_button.dart'; +import 'package:mc_common_app/widgets/common_widgets/info_bottom_sheet.dart'; import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; class ServiceVM extends BaseVM { final BranchRepo branchRepo; @@ -160,6 +168,7 @@ class ServiceVM extends BaseVM { final BranchDetailModel currentBranch = branches!.data!.serviceProviderBranch!.firstWhere((element) => element.id == selectedBranchId); for (var element in currentBranch.branchServices!) { + // TODO: Here , we need to add the category deactivated status. categories.add( CategoryData( id: element.categoryId, @@ -527,6 +536,92 @@ class ServiceVM extends BaseVM { return await branchRepo.updateService(map); } + Future> getAppointmentsByServiceID({required BuildContext context, required int branchId, required int serviceId}) async { + try { + Utils.showLoading(context); + List appointmentList = await branchRepo.getAppointmentsByCategoryOrService(branchId: branchId, serviceId: serviceId); + // log("list: ${mResponse.}"); + Utils.hideLoading(context); + + return appointmentList; + } catch (e) { + Utils.hideLoading(context); + log(e.toString()); + Utils.showToast(e.toString() ?? ""); + return []; + } + } + + Future updateServiceStatus({required BuildContext context, required ServiceStatusEnum serviceStatusEnum, required int branchId, required List providerServiceIds}) async { + try { + Utils.showLoading(context); + GenericRespModel genericRespModel = await branchRepo.updateServiceStatus(branchId: branchId, serviceIds: providerServiceIds, serviceStatusEnum: serviceStatusEnum); + Utils.hideLoading(context); + Utils.showToast(genericRespModel.message ?? ""); + return genericRespModel.messageStatus == 1; + } catch (e) { + Utils.hideLoading(context); + log(e.toString()); + Utils.showToast(e.toString()); + return false; + } + } + + Future buildDealNotCompletedBottomSheetOptions({required BuildContext mainContext, required List appointments, required String branchName}) async { + return actionConfirmationBottomSheet( + isOnlyOneButton: true, + context: mainContext, + title: LocaleKeys.notice.tr().toText(fontSize: 26, isBold: true, letterSpacing: -1.44), + subtitle: LocaleKeys.youCannotDeactivateThisServiceRightNow.tr(), + checkBoxConfirmationWidget: ListView.builder( + shrinkWrap: true, + itemCount: appointments.length, + itemBuilder: (BuildContext context, int index) { + AppointmentBasicDetailsModel appointmentBasicDetailsModel = appointments[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (appointmentBasicDetailsModel.appointmentStatusText != null && appointmentBasicDetailsModel.appointmentStatusText!.isNotEmpty) ...[ + Utils.statusContainerChip(text: appointmentBasicDetailsModel.appointmentStatusText!, chipColor: MyColors.black), + ], + 6.height, + "${appointmentBasicDetailsModel.customerID}".toText(fontSize: 16, letterSpacing: -0.64), + showItem("${LocaleKeys.branchName.tr()}:", "${appointmentBasicDetailsModel.slotDate}"), + showItem("${LocaleKeys.date.tr()}:", "${appointmentBasicDetailsModel.slotDate}"), + showItem("${LocaleKeys.time.tr()}:", "${appointmentBasicDetailsModel.startTime} - ${appointmentBasicDetailsModel.startTime}"), + ], + ).toContainer(isShadowEnabled: true); + }), + actionButtonYes: Expanded( + child: ShowFillButton( + maxHeight: 55, + title: LocaleKeys.done.tr(), + fontSize: 15, + onPressed: () => Navigator.pop(mainContext), + ), + ), + actionButtonNo: Expanded( + child: ShowFillButton( + maxHeight: 55, + title: LocaleKeys.done.tr(), + fontSize: 15, + onPressed: () => Navigator.pop(mainContext), + ), + ), + ); + } + + Widget showItem(String title, String value) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText(color: MyColors.lightTextColor, letterSpacing: -0.48), + 3.width, + Flexible(child: value.toText(isBold: true, overflow: TextOverflow.ellipsis)), + ], + ); + } + void updateSelectedBranchType(int status) { selectedBranchStatus = status; notifyListeners(); @@ -535,7 +630,7 @@ class ServiceVM extends BaseVM { List? matchedServices; bool isAllSelected = false; - Future getAllMatchedServices(int oldBranchId, int newBranchId, int categoryId) async { + Future getAllMatchedServices({required int oldBranchId, required int newBranchId, required int categoryId}) async { matchedServices = null; final GenericRespModel response = await branchRepo.getMatchedServices(oldBranchId, newBranchId, categoryId); matchedServices = []; diff --git a/lib/views/advertisement/ads_detail_view/components.dart b/lib/views/advertisement/ads_detail_view/components.dart index a2eddec..fd3be94 100644 --- a/lib/views/advertisement/ads_detail_view/components.dart +++ b/lib/views/advertisement/ads_detail_view/components.dart @@ -293,24 +293,24 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { 20.height, adVM.state == ViewState.busy ? const Center(child: CircularProgressIndicator()) - : Builder( - builder: (context) { - List vehicleCitiesDrop = []; - for (int i = 0; i < adVM.photoSSSchedulesByOffices.length; i++) { - var element = adVM.photoSSSchedulesByOffices[i]; - vehicleCitiesDrop.add(DropValue(element.photoOfficeID?.toInt() ?? 0, element.photoOfficeName ?? "", i.toString())); - } - - return DropdownField( - (DropValue value) => adVM.updatePhotoOfficeSelectedId(SelectionModel(selectedId: value.id, selectedOption: value.value, itemPrice: value.subValue)), - // here the item price is the index of the selected option - list: vehicleCitiesDrop, - dropdownValue: adVM.photoOfficeSelectedId.selectedId != -1 ? DropValue(adVM.photoOfficeSelectedId.selectedId, adVM.photoOfficeSelectedId.selectedOption, "") : null, - hint: LocaleKeys.selectOffice.tr(), - errorValue: adVM.photoOfficeSelectedId.errorValue, - ); - }, - ), + : adVM.photoSSSchedulesByOffices.isEmpty + ? LocaleKeys.noAvailableOfficesInCity.tr().toText(fontSize: 14, height: 1.2) + : Builder( + builder: (context) { + List vehicleCitiesDrop = []; + for (int i = 0; i < adVM.photoSSSchedulesByOffices.length; i++) { + var element = adVM.photoSSSchedulesByOffices[i]; + vehicleCitiesDrop.add(DropValue(element.photoOfficeID?.toInt() ?? 0, element.photoOfficeName ?? "", i.toString())); + } + return DropdownField( + (DropValue value) => adVM.updatePhotoOfficeSelectedId(SelectionModel(selectedId: value.id, selectedOption: value.value, itemPrice: value.subValue)), + list: vehicleCitiesDrop, + dropdownValue: adVM.photoOfficeSelectedId.selectedId != -1 ? DropValue(adVM.photoOfficeSelectedId.selectedId, adVM.photoOfficeSelectedId.selectedOption, "") : null, + hint: LocaleKeys.selectOffice.tr(), + errorValue: adVM.photoOfficeSelectedId.errorValue, + ); + }, + ), if (adVM.photoOfficeSelectedId.selectedId != -1) ...[ 9.height, CustomCalenderAppointmentWidget( diff --git a/lib/views/advertisement/ads_filter_view.dart b/lib/views/advertisement/ads_filter_view.dart index 705ccc0..479195b 100644 --- a/lib/views/advertisement/ads_filter_view.dart +++ b/lib/views/advertisement/ads_filter_view.dart @@ -117,19 +117,21 @@ class _AdsFilterViewState extends State { const Divider(thickness: 1.2).paddingOnly(top: 7, bottom: 7), SearchEntityWidget( title: LocaleKeys.searchByVehicleYear.tr(), - 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: LocaleKeys.selectYear.tr(), - errorValue: adVM.vehicleModelYearId.errorValue, - ); - }), + 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: LocaleKeys.selectYear.tr(), + errorValue: adVM.vehicleModelYearId.errorValue, + ); + }, + ), historyContent: adVM.vehicleYearAdSearchHistory, onHistoryItemDeleted: (index) => adVM.removeVehicleYearAdSearchHistory(index: index), onHistoryItemTapped: (DropValue value) => null, @@ -167,7 +169,10 @@ class _AdsFilterViewState extends State { isNeedClickAll: true, postFixDataColor: MyColors.darkTextColor, onTap: () async { - final formattedDate = await Utils.pickDateFromDatePicker(context); + final formattedDate = await Utils.pickDateFromDatePicker(context, firstDate: DateTime(2020), lastDate: DateTime.now()); + if (formattedDate.isEmpty) { + return; + } adVM.updateSelectionDurationStartDate(formattedDate); DropValue value = DropValue(0, formattedDate, ""); adVM.addToVehicleAdCreatedDateSearchHistory(value: value); 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 c4fe29b..f6d224d 100644 --- a/lib/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart +++ b/lib/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart @@ -1,11 +1,11 @@ import 'dart:developer'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/main.dart'; import 'package:mc_common_app/models/advertisment_models/ads_duration_model.dart'; import 'package:mc_common_app/models/general_models/widgets_models.dart'; import 'package:mc_common_app/theme/colors.dart'; @@ -15,7 +15,6 @@ 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/extensions/extensions_widget.dart'; import 'package:provider/provider.dart'; -import 'package:easy_localization/easy_localization.dart'; class AdDurationSelectionSheet extends StatelessWidget { final bool isFromExtendAd; @@ -66,17 +65,54 @@ class AdDurationSelectionSheet extends StatelessWidget { ), 12.height, LocaleKeys.adCharges.tr().toText(fontSize: 14, color: MyColors.lightTextColor, isBold: true), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - "${adDuration.price}".toText(fontSize: 22, isBold: true), - 2.width, - Padding( - padding: const EdgeInsets.only(bottom: 4), - child: (adDuration.currency ?? LocaleKeys.sar.tr()).toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), - ), - ], - ), + if (adDuration.discountPercent != null && adDuration.discountPercent! > 0.0) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + CustomPaint( + painter: DiagonalLinePainter(), + child: "${adDuration.price}".toText( + fontSize: 20, + isBold: true, + )), + 2.width, + Padding( + padding: const EdgeInsets.only(bottom: 4), + child: (adDuration.currency ?? LocaleKeys.sar.tr()).toText(fontSize: 10, color: MyColors.lightTextColor, isBold: true), + ), + 14.width, + "${adDuration.priceAfterDiscount}".toText(fontSize: 20, isBold: true), + 2.width, + Padding( + padding: const EdgeInsets.only(bottom: 4), + child: (adDuration.currency ?? LocaleKeys.sar.tr()).toText(fontSize: 10, color: MyColors.lightTextColor, isBold: true), + ), + ], + ), + "${adDuration.discountPercent} % OFF".toText(color: MyColors.white, fontSize: 10).toContainer( + marginAll: 5, + paddingAll: 5, + borderRadius: 4, + backgroundColor: MyColors.greenColor, + ), + ], + ), + ] else ...[ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + "${adDuration.price}".toText(fontSize: 22, isBold: true), + 2.width, + Padding( + padding: const EdgeInsets.only(bottom: 4), + child: (adDuration.currency ?? LocaleKeys.sar.tr()).toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), + ), + ], + ), + ] ], ), ), @@ -92,7 +128,7 @@ class AdDurationSelectionSheet extends StatelessWidget { SelectionModel( selectedId: adDuration.id ?? 0, selectedOption: ("${adDuration.days} ${LocaleKeys.daysVar.tr()}"), - itemPrice: adDuration.price!.toInt().toString(), + itemPrice: adDuration.priceAfterDiscount!.toInt().toString(), ), ); return; @@ -101,7 +137,7 @@ class AdDurationSelectionSheet extends StatelessWidget { SelectionModel( selectedId: adDuration.id ?? 0, selectedOption: ("${adDuration.days} ${LocaleKeys.daysVar.tr()}"), - itemPrice: adDuration.price!.toString(), + itemPrice: adDuration.priceAfterDiscount!.toString(), currency: adDuration.currency!.toString(), ), ); @@ -160,3 +196,20 @@ class AdDurationSelectionSheet extends StatelessWidget { ); } } + +class DiagonalLinePainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final Paint paint = Paint() + ..color = MyColors.redColor + ..strokeWidth = 2.5; + + // Draw the diagonal line from top-left to bottom-right + canvas.drawLine(const Offset(0, 0), Offset(size.width, size.height), paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return false; + } +} diff --git a/lib/views/advertisement/components/ads_list_widget.dart b/lib/views/advertisement/components/ads_list_widget.dart index fd14790..fff78fb 100644 --- a/lib/views/advertisement/components/ads_list_widget.dart +++ b/lib/views/advertisement/components/ads_list_widget.dart @@ -1,3 +1,6 @@ +import 'dart:developer'; + +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -12,21 +15,22 @@ import 'package:mc_common_app/utils/utils.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:provider/provider.dart'; -import 'package:easy_localization/easy_localization.dart'; class AdsListWidget extends StatelessWidget { final List adsList; final ScrollPhysics? scrollPhysics; final bool isAdsFragment; final bool shouldShowAdStatus; + final Function()? onFetchMoreAds; const AdsListWidget({ - Key? key, + super.key, required this.adsList, - this.scrollPhysics, required this.shouldShowAdStatus, + this.onFetchMoreAds, + this.scrollPhysics, this.isAdsFragment = false, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -38,21 +42,31 @@ class AdsListWidget extends StatelessWidget { ], ); } - return ListView.separated( - itemCount: adsList.length, - shrinkWrap: true, - physics: scrollPhysics, - itemBuilder: (BuildContext context, int index) { - return AdCard( - adDetails: adsList[index], - isAdsFragment: isAdsFragment, - shouldShowAdStatus: shouldShowAdStatus, - ).onPress(() => navigateWithName(context, AppRoutes.adsDetailView, arguments: adsList[index])); - }, - separatorBuilder: (BuildContext context, int index) { - return 12.height; + return NotificationListener( + onNotification: (ScrollNotification scrollInfo) { + if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && context.read().hasMoreData) { + if (onFetchMoreAds != null) { + onFetchMoreAds!(); + } + } + return false; }, - padding: const EdgeInsets.symmetric(horizontal: 21), + child: ListView.separated( + itemCount: adsList.length, + shrinkWrap: true, + physics: scrollPhysics, + itemBuilder: (BuildContext context, int index) { + return AdCard( + adDetails: adsList[index], + isAdsFragment: isAdsFragment, + shouldShowAdStatus: shouldShowAdStatus, + ).onPress(() => navigateWithName(context, AppRoutes.adsDetailView, arguments: adsList[index])); + }, + separatorBuilder: (BuildContext context, int index) { + return 12.height; + }, + padding: const EdgeInsets.symmetric(horizontal: 21), + ), ); } } @@ -62,7 +76,7 @@ class AdCard extends StatelessWidget { final bool isAdsFragment; final bool shouldShowAdStatus; - const AdCard({Key? key, required this.adDetails, required this.isAdsFragment, required this.shouldShowAdStatus}) : super(key: key); + const AdCard({super.key, required this.adDetails, required this.isAdsFragment, required this.shouldShowAdStatus}); @override Widget build(BuildContext context) { diff --git a/lib/views/advertisement/components/damage_pictures_bottom_sheet.dart b/lib/views/advertisement/components/damage_pictures_bottom_sheet.dart index c3572d2..6e4e55a 100644 --- a/lib/views/advertisement/components/damage_pictures_bottom_sheet.dart +++ b/lib/views/advertisement/components/damage_pictures_bottom_sheet.dart @@ -24,7 +24,7 @@ class _DamagePicturesListState extends State { @override void initState() { super.initState(); - context.read().getServiceItems(widget.serviceId); + context.read().getServiceItems(serviceId: widget.serviceId); } @override diff --git a/lib/views/advertisement/components/picked_images_container_widget.dart b/lib/views/advertisement/components/picked_images_container_widget.dart index 1e134bc..91e3454 100644 --- a/lib/views/advertisement/components/picked_images_container_widget.dart +++ b/lib/views/advertisement/components/picked_images_container_widget.dart @@ -1,4 +1,5 @@ -import 'dart:io'; +import 'dart:convert'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -18,7 +19,7 @@ class PickedFilesContainer extends StatelessWidget { final bool allowAdButton; const PickedFilesContainer({ - Key? key, + super.key, required this.pickedFiles, this.onCrossPressedPrimary, this.onCrossPressedSecondary, @@ -28,7 +29,7 @@ class PickedFilesContainer extends StatelessWidget { this.isPdf = false, this.isFromNetwork = false, this.allowAdButton = true, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -82,17 +83,29 @@ class BuildFilesContainer extends StatelessWidget { final bool isPdf; const BuildFilesContainer({ - Key? key, + super.key, required this.image, this.onCrossPressedPrimary, this.onCrossPressedSecondary, this.index, this.isReview = false, this.isPdf = false, - }) : super(key: key); + }); + + bool isBase64String(String input) { + // This regex checks if the string is a valid Base64 string + final base64Regex = RegExp(r'^[A-Za-z0-9+/=]+$'); + + // Check if the string matches the Base64 pattern + return base64Regex.hasMatch(input) && (input.length % 4 == 0); + } @override Widget build(BuildContext context) { + Uint8List? bytes; + if ((image.isFromNetwork == null || image.isFromNetwork == false) && isBase64String(image.filePath ?? "")) { + bytes = (base64Decode(image.filePath!)) as Uint8List?; + } return Stack( children: [ SizedBox( @@ -120,13 +133,20 @@ class BuildFilesContainer extends StatelessWidget { width: 73, ) .paddingAll(8) - : image.filePath! - .buildFileImage( - fit: BoxFit.fill, - height: 72, - width: 70, - ) - .paddingAll(8), + : isBase64String(image.filePath!) + ? Image.memory( + bytes!, + fit: BoxFit.fill, + height: 72, + width: 70, + ).paddingAll(8) + : image.filePath! + .buildFileImage( + fit: BoxFit.fill, + height: 72, + width: 70, + ) + .paddingAll(8), if (!isReview) ...[ Align( alignment: Alignment.topRight, diff --git a/lib/views/appointments/appointments_filter_view.dart b/lib/views/appointments/appointments_filter_view.dart index 45b2faf..3523174 100644 --- a/lib/views/appointments/appointments_filter_view.dart +++ b/lib/views/appointments/appointments_filter_view.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; @@ -13,7 +14,6 @@ import 'package:mc_common_app/view_models/appointments_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:easy_localization/easy_localization.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'; @@ -33,14 +33,14 @@ class _AppointmentsFilterViewState extends State { void initState() { appointmentsVM = context.read(); scheduleMicrotask(() async { - await appointmentsVM.populateDataForBranchesFilter(); + await appointmentsVM.populateDataForAppointmentsFilter(); }); super.initState(); } @override void dispose() { - appointmentsVM.clearBranchFilterSelections(); + appointmentsVM.clearAppointmentFilterSelections(); super.dispose(); } @@ -96,7 +96,7 @@ class _AppointmentsFilterViewState extends State { children: [ SizedBox( height: 5.h, - width: 5.h, + width: 5.h, child: const CircularProgressIndicator(), ), ], @@ -165,6 +165,23 @@ class _AppointmentsFilterViewState extends State { onHistoryItemDeleted: (index) => appointmentsVM.removeAppointmentFilterServicesSearchHistory(index: index), onHistoryItemTapped: (DropValue value) => null, ), + const Divider(thickness: 1.2).paddingOnly(top: 5, bottom: 5), + SearchEntityWidget( + title: LocaleKeys.searchByItem.tr(), + actionWidget: Builder(builder: (context) { + return DropdownField( + (DropValue value) => appointmentsVM.updateAppointmentFilterSelectedItemId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true), + list: appointmentsVM.itemsDropList, + dropdownValue: appointmentsVM.appointmentFilterSelectedItemId.selectedId != -1 + ? DropValue(appointmentsVM.appointmentFilterSelectedItemId.selectedId, appointmentsVM.appointmentFilterSelectedItemId.selectedOption, "") + : null, + hint: LocaleKeys.selectItems.tr(), + ); + }), + historyContent: appointmentsVM.appointmentFilterItemsSearchHistory, + onHistoryItemDeleted: (index) => appointmentsVM.removeAppointmentFilterItemsSearchHistory(index: index), + onHistoryItemTapped: (DropValue value) => null, + ), ], ).expand(), Container( @@ -193,10 +210,10 @@ class _AppointmentsFilterViewState extends State { InkWell( onTap: () => appointmentsVM.clearAppointmentFilters(), child: LocaleKeys.clearFilters.tr().toText( - fontSize: 14, - isBold: true, - color: MyColors.darkPrimaryColor, - ), + fontSize: 14, + isBold: true, + color: MyColors.darkPrimaryColor, + ), ), 10.height, ], diff --git a/lib/views/appointments/book_appointment_services_view.dart b/lib/views/appointments/book_appointment_services_view.dart index eb9c4bc..95d5fea 100644 --- a/lib/views/appointments/book_appointment_services_view.dart +++ b/lib/views/appointments/book_appointment_services_view.dart @@ -1,5 +1,4 @@ -import 'dart:developer'; - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; @@ -17,7 +16,6 @@ import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/common_widgets/custom_add_button_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:provider/provider.dart'; -import 'package:easy_localization/easy_localization.dart'; class BookAppointmentServicesView extends StatelessWidget { const BookAppointmentServicesView({super.key}); @@ -73,7 +71,7 @@ class BookAppointmentServicesView extends StatelessWidget { itemPrice: "", ), ); - appointmentsVM.getServiceItems(serviceData.serviceProviderServiceId!); + appointmentsVM.getServiceItems(serviceId: serviceData.serviceProviderServiceId!); navigateWithName(context, AppRoutes.bookAppointmentsItemView); }), 10.width, diff --git a/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart b/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart index 29ed87d..a3d3c1f 100644 --- a/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart +++ b/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart @@ -238,7 +238,7 @@ class AppointmentServicePickBottomSheet extends StatelessWidget { onPressed: () { bool isValidated = appointmentsVM.isServiceSelectionValidated(); if (isValidated) { - appointmentsVM.getServiceItems(appointmentsVM.branchSelectedServiceId.selectedId); + appointmentsVM.getServiceItems(serviceId: appointmentsVM.branchSelectedServiceId.selectedId); Navigator.pop(context); navigateWithName(context, AppRoutes.bookAppointmentsItemView); } diff --git a/lib/views/appointments/widgets/common_appointment_slider_widget.dart b/lib/views/appointments/widgets/common_appointment_slider_widget.dart index ce7cb80..fc08420 100644 --- a/lib/views/appointments/widgets/common_appointment_slider_widget.dart +++ b/lib/views/appointments/widgets/common_appointment_slider_widget.dart @@ -26,7 +26,7 @@ import 'package:sizer/sizer.dart'; class CommonAppointmentSliderWidget extends StatelessWidget { final Function(AppointmentListModel)? onAppointmentClick; - const CommonAppointmentSliderWidget({Key? key, this.onAppointmentClick}) : super(key: key); + const CommonAppointmentSliderWidget({super.key, this.onAppointmentClick}); Widget getCarouselSliderWidget(AppType appType, AppointmentsVM appointmentsVM) { return CarouselSlider.builder( @@ -95,7 +95,7 @@ class AppointmentHomeTileWidget extends StatelessWidget { final AppointmentListModel? appointmentListModel; final Function()? onTapped; - const AppointmentHomeTileWidget({Key? key, this.isForProvider = false, this.onTapped, required this.appointmentListModel, this.isForCustomerHome = false}) : super(key: key); + const AppointmentHomeTileWidget({super.key, this.isForProvider = false, this.onTapped, required this.appointmentListModel, this.isForCustomerHome = false}); Widget showServices(String title, String icon, {bool isMoreText = false}) { return Row( @@ -227,7 +227,7 @@ class AppointmentFragmentTileWidget extends StatelessWidget { final AppointmentListModel? appointmentListModel; final Function()? onTapped; - const AppointmentFragmentTileWidget({Key? key, this.onTapped, required this.appointmentListModel}) : super(key: key); + const AppointmentFragmentTileWidget({super.key, this.onTapped, required this.appointmentListModel}); Widget showServices(String title, String icon, {bool isMoreText = false}) { return Row( diff --git a/lib/views/chat/chat_view.dart b/lib/views/chat/chat_view.dart index 448eb52..60861cc 100644 --- a/lib/views/chat/chat_view.dart +++ b/lib/views/chat/chat_view.dart @@ -1,10 +1,6 @@ -import 'dart:developer'; - -import 'package:flutter/cupertino.dart'; +import 'package:easy_localization/easy_localization.dart' as lcl; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:mc_common_app/classes/app_state.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'; @@ -21,14 +17,11 @@ import 'package:mc_common_app/views/advertisement/components/picked_images_conta import 'package:mc_common_app/views/chat/widgets/chat_bottom_sheets.dart'; import 'package:mc_common_app/views/chat/widgets/chat_message_widget.dart'; import 'package:mc_common_app/views/requests/request_bottomsheets.dart'; -import 'package:mc_common_app/views/setting_options/widgets/custom_setting_options_tile.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/info_bottom_sheet.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; -import 'package:easy_localization/easy_localization.dart' as lcl; class ChatView extends StatefulWidget { final ChatViewArguments chatViewArguments; @@ -165,6 +158,7 @@ class _ChatViewState extends State { ChatMessageModel chatMessageModel = chatMessages[index]; return ChatMessageCustomWidget( chatMessageModel: chatMessageModel, + requestModel: chatTypeEnum == ChatTypeEnum.requestOffer ? chatViewArgumentsForRequest!.requestModel! : null, requestStatusEnum: requestVM.currentSelectedRequest?.requestStatus, chatTypeEnum: chatTypeEnum, requestsTypeEnum: chatTypeEnum == ChatTypeEnum.requestOffer ? chatViewArgumentsForRequest!.requestModel!.requestType.toRequestTypeEnum() : RequestsTypeEnum.specialCarRequest, @@ -193,32 +187,32 @@ class _ChatViewState extends State { }, ), ), - // Center( - // child: LocaleKeys.requestAlreadyInProgress.tr().toText( - // textAlign: TextAlign.center, - // color: MyColors.lightTextColor, - // fontSize: 14, - // ), - // ).paddingAll(15) - ] else if (chatTypeEnum == ChatTypeEnum.requestOffer && - requestVM.currentSelectedRequest!.requestType.toRequestTypeEnum() == RequestsTypeEnum.specialCarRequest && - requestVM.currentSelectedRequest!.requestStatus == RequestStatusEnum.inProgress && - requestVM.currentSelectedOffer!.requestOfferStatusEnum == RequestOfferStatusEnum.accepted && - AppState().currentAppType == AppType.customer) ...[ - Expanded( - child: ShowFillButton( - maxWidth: double.infinity, - margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - maxHeight: 55, - title: LocaleKeys.completeDeal.tr(), - isBold: false, - onPressed: () { - return dealCompletedConfirmationBottomSheet(mainContext: context, requestStatusEnum: RequestStatusEnum.completed, requestId: requestVM.currentSelectedRequest!.id); - }, - ), - ), - ] else ...[ - if (AppState().currentAppType == AppType.provider && chatTypeEnum == ChatTypeEnum.requestOffer && requestVM.currentSelectedRequest!.requestStatus == RequestStatusEnum.submitted && chatVM.pickedImagesForMessage.isEmpty) ...[ + ] + + // else if (chatTypeEnum == ChatTypeEnum.requestOffer && + // requestVM.currentSelectedRequest!.requestType.toRequestTypeEnum() == RequestsTypeEnum.specialCarRequest && + // requestVM.currentSelectedRequest!.requestStatus == RequestStatusEnum.inProgress && + // requestVM.currentSelectedOffer!.requestOfferStatusEnum == RequestOfferStatusEnum.accepted && + // AppState().currentAppType == AppType.customer) ...[ + // Expanded( + // child: ShowFillButton( + // maxWidth: double.infinity, + // margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + // maxHeight: 55, + // title: LocaleKeys.completeDeal.tr(), + // isBold: false, + // onPressed: () { + // return dealCompletedConfirmationBottomSheet(mainContext: context, requestStatusEnum: RequestStatusEnum.completed, requestId: requestVM.currentSelectedRequest!.id); + // }, + // ), + // ), + // ] + // + else ...[ + if (AppState().currentAppType == AppType.provider && + chatTypeEnum == ChatTypeEnum.requestOffer && + requestVM.currentSelectedRequest!.requestStatus == RequestStatusEnum.submitted && + chatVM.pickedImagesForMessage.isEmpty) ...[ Expanded( flex: 1, child: const Icon( @@ -227,8 +221,17 @@ class _ChatViewState extends State { size: 30, ).onPress( () { - RequestDetailPageArguments requestDetailArguments = RequestDetailPageArguments(requestIndex: chatViewArgumentsForRequest!.requestIndex, requestModel: chatViewArgumentsForRequest!.requestModel!); - buildSendOfferBottomSheet(context: context, requestDetailPageArguments: requestDetailArguments, isFromChatScreen: true); + requestVM.resetSendOfferBottomSheet(); + RequestDetailPageArguments requestDetailArguments = RequestDetailPageArguments( + requestIndex: chatViewArgumentsForRequest!.requestIndex, + requestModel: chatViewArgumentsForRequest!.requestModel!, + ); + buildSendOfferBottomSheet( + context: context, + requestDetailPageArguments: requestDetailArguments, + isFromChatScreen: true, + offerId: null, // null means creating new offer + ); }, ), ), diff --git a/lib/views/chat/widgets/chat_bottom_sheets.dart b/lib/views/chat/widgets/chat_bottom_sheets.dart index 2c9bc16..1d59e4c 100644 --- a/lib/views/chat/widgets/chat_bottom_sheets.dart +++ b/lib/views/chat/widgets/chat_bottom_sheets.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -9,7 +11,6 @@ import 'package:mc_common_app/theme/colors.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'; -import 'package:mc_common_app/view_models/base_view_model.dart'; import 'package:mc_common_app/view_models/chat_view_model.dart'; import 'package:mc_common_app/view_models/dashboard_view_model_customer.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; @@ -17,43 +18,79 @@ import 'package:mc_common_app/widgets/button/show_fill_button.dart'; import 'package:mc_common_app/widgets/checkbox_with_title_desc.dart'; import 'package:provider/provider.dart'; -void dealCompletedConfirmationBottomSheet({required BuildContext mainContext, required RequestStatusEnum requestStatusEnum, required int requestId}) { +void dealCompletedConsentBottomSheet({required BuildContext mainContext, required RequestStatusEnum requestStatusEnum, required int requestId, Function()? acceptRequestOffer}) { final requestVM = mainContext.read(); return actionConfirmationBottomSheet( - isOnlyOneButton: true, + isOnlyOneButton: AppState().currentAppType == AppType.customer, context: mainContext, title: LocaleKeys.doYouWantToCompleteThisDeal.tr().toText(fontSize: 26, isBold: true, letterSpacing: -1.44), subtitle: AppState().currentAppType == AppType.provider ? LocaleKeys.providerCompletingDealMeansThat.tr() : LocaleKeys.customerCompletingDealMeansThat.tr(), - checkBoxConfirmationWidget: Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) { - return Row( - children: [ - Expanded( - child: CircleCheckBoxWithTitle( - isChecked: chatVM.acknowledgePaymentToMowaterStatus, - title: LocaleKeys.acknowledgePaymentToMowater.tr(), - onSelected: () { - chatVM.updateAcknowledgePaymentToMowaterStatus(!chatVM.acknowledgePaymentToMowaterStatus); - }, - selectedColor: MyColors.darkPrimaryColor, - ), - ), - ], - ); - }), + checkBoxConfirmationWidget: AppState().currentAppType == AppType.customer + ? Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) { + return Row( + children: [ + Expanded( + child: CircleCheckBoxWithTitle( + isChecked: chatVM.acknowledgePaymentToMowaterStatus, + title: LocaleKeys.acknowledgePaymentToMowater.tr(), + onSelected: () { + chatVM.updateAcknowledgePaymentToMowaterStatus(!chatVM.acknowledgePaymentToMowaterStatus); + }, + selectedColor: MyColors.darkPrimaryColor, + ), + ), + ], + ); + }) + : null, actionButtonYes: Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) { return Expanded( child: ShowFillButton( maxHeight: 55, isDisabled: !chatVM.acknowledgePaymentToMowaterStatus, - title: LocaleKeys.submit.tr(), + title: AppState().currentAppType == AppType.customer ? LocaleKeys.submit.tr() : LocaleKeys.yes.tr(), fontSize: 15, onPressed: () async { - Navigator.pop(context); - bool status = await requestVM.onActionRequestTapped(context: mainContext, requestStatusEnum: requestStatusEnum, requestId: requestId); - if (status) { - chatVM.updateAcknowledgePaymentToMowaterStatus(false); - mainContext.read().onNavbarTapped(4); - navigateReplaceWithName(mainContext, AppRoutes.dashboard); + pop(context); + bool statusForReqAccept = false; + + if (acceptRequestOffer != null) { + statusForReqAccept = await acceptRequestOffer(); + if (statusForReqAccept) { + bool status = await requestVM.onActionRequestTapped( + context: mainContext, + requestStatusEnum: requestStatusEnum, + requestId: requestId, + needLoading: false, + ); + log("status: $status"); + if (status) { + int index = requestVM.myFilteredRequests.indexWhere((element) => element.id == requestId); + if (index != -1) { + requestVM.myFilteredRequests[index].requestStatus = requestStatusEnum; + } + chatVM.updateAcknowledgePaymentToMowaterStatus(false); + mainContext.read().onNavbarTapped(4); + navigateReplaceWithNameUntilRoute(mainContext, AppRoutes.dashboard); + } + } + } else { + bool status = await requestVM.onActionRequestTapped( + context: mainContext, + requestStatusEnum: requestStatusEnum, + requestId: requestId, + needLoading: false, + ); + log("status: $status"); + if (status) { + int index = requestVM.myFilteredRequests.indexWhere((element) => element.id == requestId); + if (index != -1) { + requestVM.myFilteredRequests[index].requestStatus = requestStatusEnum; + } + chatVM.updateAcknowledgePaymentToMowaterStatus(false); + mainContext.read().onNavbarTapped(4); + navigateReplaceWithNameUntilRoute(mainContext, AppRoutes.dashboard); + } } }, ), diff --git a/lib/views/chat/widgets/chat_message_widget.dart b/lib/views/chat/widgets/chat_message_widget.dart index d4d2e3e..05ef366 100644 --- a/lib/views/chat/widgets/chat_message_widget.dart +++ b/lib/views/chat/widgets/chat_message_widget.dart @@ -1,6 +1,7 @@ import 'dart:developer'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:easy_localization/easy_localization.dart' as lcl; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -9,34 +10,265 @@ 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/chat_models/chat_message_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/dialogs_and_bottomsheets.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/ad_view_model.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/views/chat/widgets/chat_bottom_sheets.dart'; +import 'package:mc_common_app/views/requests/request_bottomsheets.dart'; import 'package:mc_common_app/widgets/button/show_fill_button.dart'; import 'package:mc_common_app/widgets/checkbox_with_title_desc.dart'; import 'package:mc_common_app/widgets/common_widgets/info_bottom_sheet.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; -import 'package:easy_localization/easy_localization.dart' as lcl; class ChatMessageCustomWidget extends StatefulWidget { final ChatMessageModel chatMessageModel; final RequestStatusEnum? requestStatusEnum; final ChatTypeEnum chatTypeEnum; final RequestsTypeEnum requestsTypeEnum; + final RequestModel? requestModel; - const ChatMessageCustomWidget({super.key, required this.chatMessageModel, required this.requestStatusEnum, required this.chatTypeEnum, this.requestsTypeEnum = RequestsTypeEnum.specialCarRequest}); + const ChatMessageCustomWidget({ + super.key, + required this.chatMessageModel, + required this.requestStatusEnum, + required this.chatTypeEnum, + this.requestModel, + this.requestsTypeEnum = RequestsTypeEnum.specialCarRequest, + }); @override State createState() => _ChatMessageCustomWidgetState(); } class _ChatMessageCustomWidgetState extends State { + Future buildDealNotCompletedBottomSheetOptions({required ChatMessageModel chatMessageModel}) { + return showModalBottomSheet( + context: context, + isScrollControlled: true, + enableDrag: true, + builder: (BuildContext context) { + return Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) { + return InfoBottomSheet( + title: LocaleKeys.pleaseSpecify.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), + description: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 12.height, + ListView.separated( + shrinkWrap: true, + itemCount: chatVM.offerRejectModelList.length, + separatorBuilder: (BuildContext context, int index) { + bool indexContains = chatVM.indexesForDealNotCompleted.indexWhere((i) => i == index) != -1; + if ((!indexContains)) { + return const SizedBox(); + } + + return const Divider(thickness: 0.5); + }, + itemBuilder: (BuildContext context, int index) { + bool indexContains = chatVM.indexesForDealNotCompleted.indexWhere((i) => i == index) != -1; + + if (!indexContains) { + return const SizedBox(); + } + + OfferRequestCommentModel offerRequestCommentModel = chatVM.offerRejectModelList[index]; + return CircleCheckBoxWithTitle( + isChecked: offerRequestCommentModel.isSelected ?? false, + title: '${offerRequestCommentModel.title}', + onSelected: () { + chatVM.updateSelectionInOfferRejectModelList(index); + }, + selectedColor: MyColors.darkPrimaryColor, + ); + }, + ), + if (chatVM.offerRejectModelList.last.isSelected ?? false) ...[ + // comparing if the "other" is selected + 12.height, + TxtField( + maxLines: 5, + value: chatVM.rejectOfferDescription, + errorValue: chatVM.rejectOfferDescriptionError, + keyboardType: TextInputType.text, + hint: LocaleKeys.description.tr(), + onChanged: (v) => chatVM.updateRejectOfferDescription(v), + ), + ], + 25.height, + ShowFillButton( + title: LocaleKeys.submit.tr(), + onPressed: () async { + String comments = ""; + if (chatVM.offerRejectModelList.last.isSelected ?? false) { + comments = chatVM.rejectOfferDescription; + } else { + comments = chatVM.offerRejectModelList.firstWhere((element) => element.isSelected!).title ?? ""; + } + if (!chatVM.isRejectOfferButtonValidated()) { + return; + } + Navigator.pop(context); + bool status = await chatVM.onSendMessageForActionOnRequestOffer( + receiverId: (chatMessageModel.isMyMessage ?? false) ? chatMessageModel.receiverUserID ?? "" : chatMessageModel.senderUserID ?? "", + chatMessageType: ChatMessageTypeEnum.offer, + comments: comments, + requestId: chatMessageModel.reqOffer!.requestID ?? -1, + serviceProviderID: chatMessageModel.serviceProviderID ?? 0, + requestOfferID: chatMessageModel.reqOffer!.id ?? -1, + offerPrice: chatMessageModel.reqOffer!.price.toString(), + serviceItemName: chatMessageModel.reqOffer!.serviceItemName ?? "", + manufacturedOn: chatMessageModel.reqOffer!.manufacturedOn ?? "", + manufacturedById: chatMessageModel.reqOffer!.manufacturedById ?? 0, + requestOfferStatusEnum: RequestOfferStatusEnum.rejected, + context: context, + ); + + if (status) { + final requestVM = context.read(); + chatMessageModel.reqOffer!.requestOfferStatusEnum = RequestOfferStatusEnum.rejected; + int index = chatVM.serviceProviderOffersList + .indexWhere((element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId)); + + if (index != -1) { + chatVM.serviceProviderOffersList[index].requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum; + } + setState(() {}); + chatVM.updateRejectOfferDescription(''); + Utils.showToast(LocaleKeys.offerRejected.tr()); + // navigateReplaceWithName(context, AppRoutes.dashboard); + } + }, + maxWidth: double.infinity, + ), + 19.height, + ], + ), + )); + }); + }, + ); + } + + Future buildDealBottomSheetOptionsForSpecialCar({required ChatMessageModel chatMessageModel, required RequestOfferStatusEnum requestOfferStatusEnum, required bool fromCancelOffer}) { + return showModalBottomSheet( + context: context, + isScrollControlled: true, + enableDrag: true, + builder: (BuildContext sheetContext) { + return Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) { + return InfoBottomSheet( + title: LocaleKeys.pleaseSpecify.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), + description: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 12.height, + CheckBoxWithTitleDescription( + isSelected: chatVM.dealOptionsModelList[0].isSelected!, + title: '${chatVM.dealOptionsModelList[0].title}', + description: '', + onSelection: (bool value) { + chatVM.updateIsSelectedInDealOptionsModelList(0, value); + }, + ), + CheckBoxWithTitleDescription( + isSelected: chatVM.dealOptionsModelList[1].isSelected ?? false, + title: '${chatVM.dealOptionsModelList[1].title}', + description: '', + onSelection: (bool value) { + chatVM.updateIsSelectedInDealOptionsModelList(1, value); + }, + ), + ], + ), + 25.height, + ShowFillButton( + title: LocaleKeys.continu.tr(), + onPressed: () async { + pop(sheetContext); + if (chatVM.dealOptionsModelList[0].isSelected ?? false) { + if (fromCancelOffer) { + final requestVM = context.read(); + return dealCompletedConsentBottomSheet( + mainContext: context, + requestStatusEnum: RequestStatusEnum.completed, + requestId: requestVM.currentSelectedRequest!.id, + ); + } else { + final requestVM = context.read(); + return dealCompletedConsentBottomSheet( + mainContext: context, + requestStatusEnum: RequestStatusEnum.completed, + requestId: requestVM.currentSelectedRequest!.id, + acceptRequestOffer: () async { + bool status = await chatVM.onSendMessageForActionOnRequestOffer( + receiverId: chatMessageModel.senderUserID ?? "", + chatMessageType: ChatMessageTypeEnum.offer, + comments: GlobalConsts.acceptingThisOffer, + requestId: chatMessageModel.reqOffer!.requestID ?? -1, + serviceProviderID: chatMessageModel.serviceProviderID ?? 0, + requestOfferID: chatMessageModel.reqOffer!.id ?? -1, + offerPrice: chatMessageModel.reqOffer!.price.toString(), + serviceItemName: chatMessageModel.reqOffer!.serviceItemName ?? "", + manufacturedOn: chatMessageModel.reqOffer!.manufacturedOn ?? "", + manufacturedById: chatMessageModel.reqOffer!.manufacturedById ?? 0, + requestOfferStatusEnum: RequestOfferStatusEnum.accepted, + context: context, + ); + + if (status) { + chatMessageModel.reqOffer!.requestOfferStatusEnum = RequestOfferStatusEnum.accepted; + requestVM.currentSelectedRequest!.requestStatus = RequestStatusEnum.inProgress; + requestVM.updateAcceptedReqOffer(chatMessageModel.reqOffer!); + requestVM.updateAcceptedRequestOfferProviderName(chatMessageModel.senderName ?? ""); + int index = chatVM.serviceProviderOffersList.indexWhere( + (element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId)); + + if (index != -1) { + chatVM.serviceProviderOffersList[index].requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum; + } + setState(() {}); + Utils.showToast(LocaleKeys.offerAccepted.tr()); + return true; + } else { + return false; + } + }); + } + } else { + if (fromCancelOffer) { + buildRejectOrCancelOfferBottomSheet(chatMessageModel: chatMessageModel, requestOfferStatusEnum: RequestOfferStatusEnum.cancel); + } else { + buildDealNotCompletedBottomSheetOptions(chatMessageModel: chatMessageModel); + } + } + }, + maxWidth: double.infinity, + ), + 19.height, + ], + ), + )); + }); + }, + ); + } + Future buildRejectOrCancelOfferBottomSheet({required ChatMessageModel chatMessageModel, required RequestOfferStatusEnum requestOfferStatusEnum}) { return showModalBottomSheet( context: context, @@ -149,7 +381,8 @@ class _ChatMessageCustomWidgetState extends State { if (status) { final requestVM = context.read(); chatMessageModel.reqOffer!.requestOfferStatusEnum = requestOfferStatusEnum; - int index = chatVM.serviceProviderOffersList.indexWhere((element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId)); + int index = chatVM.serviceProviderOffersList + .indexWhere((element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId)); if (index != -1) { chatVM.serviceProviderOffersList[index].requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum; @@ -207,14 +440,15 @@ class _ChatMessageCustomWidgetState extends State { requestVM.currentSelectedRequest!.requestStatus = RequestStatusEnum.inProgress; requestVM.updateAcceptedReqOffer(chatMessageModel.reqOffer!); requestVM.updateAcceptedRequestOfferProviderName(chatMessageModel.senderName ?? ""); - int index = chatVM.serviceProviderOffersList.indexWhere((element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId)); + int index = chatVM.serviceProviderOffersList + .indexWhere((element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId)); if (index != -1) { chatVM.serviceProviderOffersList[index].requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum; } setState(() {}); // Navigator.pop(context); - Utils.showToast("Offer Accepted"); + Utils.showToast(LocaleKeys.offerAccepted.tr()); // navigateReplaceWithName(context, AppRoutes.dashboard); } }, @@ -281,7 +515,8 @@ class _ChatMessageCustomWidgetState extends State { fontSize: 12, isItalic: true, ), - ).toContainer(borderRadius: 40, width: double.infinity, backgroundColor: chatMessageModel.isMyMessage! ? MyColors.adPendingStatusColor.withOpacity(0.16) : MyColors.grey98Color.withOpacity(0.1)), + ).toContainer( + borderRadius: 40, width: double.infinity, backgroundColor: chatMessageModel.isMyMessage! ? MyColors.adPendingStatusColor.withOpacity(0.16) : MyColors.grey98Color.withOpacity(0.1)), ], ); } @@ -294,7 +529,8 @@ class _ChatMessageCustomWidgetState extends State { Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - "${(chatMessageModel.reqOffer!.price ?? 0.0).toInt()}".toText(fontSize: 19, isBold: true, color: AppState().currentAppType == AppType.provider ? MyColors.white : MyColors.darkTextColor), + "${(chatMessageModel.reqOffer!.price ?? 0.0).toInt()}" + .toText(fontSize: 19, isBold: true, color: AppState().currentAppType == AppType.provider ? MyColors.white : MyColors.darkTextColor), 5.width, LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, height: 2.2, fontSize: 10, isBold: true), ], @@ -311,7 +547,11 @@ class _ChatMessageCustomWidgetState extends State { borderColor: MyColors.greenColor, isFilled: false, onPressed: () { - offerAcceptConfirmationBottomSheet(chatMessageModel: chatMessageModel); + if (widget.requestsTypeEnum == RequestsTypeEnum.specialCarRequest) { + buildDealBottomSheetOptionsForSpecialCar(chatMessageModel: chatMessageModel, requestOfferStatusEnum: requestOfferStatusEnum, fromCancelOffer: false); + } else { + offerAcceptConfirmationBottomSheet(chatMessageModel: chatMessageModel); + } }, backgroundColor: MyColors.white, txtColor: MyColors.greenColor, @@ -338,6 +578,23 @@ class _ChatMessageCustomWidgetState extends State { 10.height, Row( children: [ + if (widget.requestsTypeEnum == RequestsTypeEnum.specialCarRequest) ...[ + Expanded( + child: ShowFillButton( + maxHeight: 27, + title: LocaleKeys.more.tr(), + fontSize: 9, + borderColor: MyColors.lightTextColor, + isFilled: false, + onPressed: () { + buildDealBottomSheetOptionsForSpecialCar(chatMessageModel: chatMessageModel, requestOfferStatusEnum: requestOfferStatusEnum, fromCancelOffer: true); + }, + backgroundColor: MyColors.white, + txtColor: MyColors.lightTextColor, + ), + ), + 20.width, + ], Expanded( child: ShowFillButton( maxHeight: 27, @@ -468,11 +725,42 @@ class _ChatMessageCustomWidgetState extends State { return widget.toCircle(borderRadius: 100); } + Future onOfferEditIconPressed() async { + int index = context.read().myFilteredRequests.indexWhere((request) => request.id == widget.requestModel!.id); + RequestDetailPageArguments requestDetailArguments = RequestDetailPageArguments( + requestIndex: index, // Not getting used in case of update offer + requestModel: widget.requestModel!, + ); + RequestsVM requestVM = context.read(); + requestVM.resetSendOfferBottomSheet(); + ReqOffer offer = widget.chatMessageModel.reqOffer!; + requestVM.updateOfferPrice((offer.price ?? "").toString()); + requestVM.updateServiceItem((offer.serviceItemName ?? "").toString()); + requestVM.updateItemManufacturer((offer.manufacturedById ?? "").toString()); + requestVM.updateServiceItemCreatedOn((offer.manufacturedOn ?? "").toString()); + requestVM.updateOfferDescription((widget.chatMessageModel.chatText ?? "").toString()); + requestVM.updateIsDeliveryAvailableStatus((offer.isDeliveryAvailable ?? false)); + if (offer.reqOfferImages != null && offer.reqOfferImages!.isNotEmpty) { + for (var element in offer.reqOfferImages!) { + if (element.imageUrl != null || element.imageStr != null) { + ImageModel imageModel = ImageModel( + id: element.id, + filePath: element.imageStr != null && element.imageStr!.isNotEmpty ? element.imageStr : element.imageUrl, + isFromNetwork: element.imageStr != null && element.imageStr!.isNotEmpty ? false : true); + + log("imageModelimageModel: ${imageModel.toString()}"); + requestVM.addImageToPickedVehicleImages(imageModel); + } + } + } + + buildSendOfferBottomSheet(context: context, requestDetailPageArguments: requestDetailArguments, isFromChatScreen: true, offerId: offer.id); + } + Widget buildFreeTextDetailsInMessage({required ChatMessageTypeEnum chatMessageTypeEnum}) { return Row( - // crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (chatMessageTypeEnum == ChatMessageTypeEnum.offer && (widget.chatMessageModel.isMyMessage == true) && widget.chatMessageModel.isRead == true) ...[ + if (chatMessageTypeEnum == ChatMessageTypeEnum.offer && (widget.chatMessageModel.isMyMessage == true) && (widget.chatMessageModel.isRead ?? false)) ...[ Row( children: [ const Icon(Icons.remove_red_eye_outlined, size: 12, color: MyColors.lightTextColor), @@ -480,14 +768,18 @@ class _ChatMessageCustomWidgetState extends State { LocaleKeys.viewed.tr().toText(fontSize: 10, color: MyColors.lightTextColor, fontWeight: MyFonts.Medium), ], ), + ] else if (AppState().currentAppType == AppType.provider && + chatMessageTypeEnum == ChatMessageTypeEnum.offer && + widget.chatMessageModel.reqOffer!.requestOfferStatusEnum == RequestOfferStatusEnum.offer && + (widget.chatMessageModel.isMyMessage == true)) ...[ + MyAssets.icEdit.buildSvg(color: MyColors.white, height: 15).onPress(() => onOfferEditIconPressed()), ], Expanded( child: Directionality( - textDirection: (widget.chatMessageModel.isMyMessage ?? false) ? TextDirection.ltr : TextDirection.rtl, + textDirection: TextDirection.ltr, child: (widget.chatMessageModel.chatText ?? "").toText( color: (widget.chatMessageModel.isMyMessage ?? false) ? MyColors.white : MyColors.lightTextColor, fontSize: 12, - // isBold: true, ), ), ), @@ -518,12 +810,12 @@ class _ChatMessageCustomWidgetState extends State { }); } return SizedBox( - // height: (gridItemSize * 2) + (spacing * 2), // Fixed height for 2 rows including spacing +// height: (gridItemSize * 2) + (spacing * 2), // Fixed height for 2 rows including spacing child: GridView.builder( physics: const NeverScrollableScrollPhysics(), - // Prevent scrolling inside grid +// Prevent scrolling inside grid shrinkWrap: true, - // Shrink size to fit the content +// Shrink size to fit the content gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, // Show 2 images per row crossAxisSpacing: spacing, @@ -609,19 +901,18 @@ class _ChatMessageCustomWidgetState extends State { case ChatMessageTypeEnum.file: case ChatMessageTypeEnum.offer: messageTypeWidget = Column( + crossAxisAlignment: (widget.chatMessageModel.isMyMessage ?? false) ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ buildFreeTextDetailsInMessage(chatMessageTypeEnum: chatMessageTypeEnum), if (widget.requestsTypeEnum == RequestsTypeEnum.serviceRequest) ...[ 2.height, - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - "${LocaleKeys.deliveryAvailable.tr()} : ${(widget.chatMessageModel.reqOffer!.isDeliveryAvailable ?? false) ? LocaleKeys.yes.tr() : LocaleKeys.no.tr()}".toText( - fontSize: 10, - color: (widget.chatMessageModel.isMyMessage ?? false) ? MyColors.white : MyColors.lightTextColor, - fontWeight: MyFonts.Medium, - ), - ], + Directionality( + textDirection: TextDirection.ltr, + child: "${LocaleKeys.deliveryAvailable.tr()} : ${(widget.chatMessageModel.reqOffer!.isDeliveryAvailable ?? false) ? LocaleKeys.yes.tr() : LocaleKeys.no.tr()}".toText( + fontSize: 10, + color: (widget.chatMessageModel.isMyMessage ?? false) ? MyColors.white : MyColors.lightTextColor, + fontWeight: MyFonts.Medium, + ), ), ], if (widget.chatMessageModel.reqOffer!.reqOfferImages != null && widget.chatMessageModel.reqOffer!.reqOfferImages!.isNotEmpty) ...[ @@ -671,7 +962,10 @@ class _ChatMessageCustomWidgetState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - ((widget.chatMessageModel.isMyMessage ?? false) ? "You" : widget.chatMessageModel.senderName ?? "").toText(fontSize: 16, isBold: true), + ((widget.chatMessageModel.isMyMessage ?? false) ? LocaleKeys.you.tr() : widget.chatMessageModel.senderName ?? "").toText( + fontSize: 16, + isBold: true, + ), ], ), 5.height, diff --git a/lib/views/common_fragments/ads_fragment.dart b/lib/views/common_fragments/ads_fragment.dart index 0a61fa2..b0e29d4 100644 --- a/lib/views/common_fragments/ads_fragment.dart +++ b/lib/views/common_fragments/ads_fragment.dart @@ -1,3 +1,6 @@ +import 'dart:developer'; + +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -18,10 +21,9 @@ import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/common_widgets/categories_list.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:provider/provider.dart'; -import 'package:easy_localization/easy_localization.dart'; class AdsFragment extends StatelessWidget { - const AdsFragment({Key? key}) : super(key: key); + const AdsFragment({super.key}); List getAdsList(AdVM adVM) { if (adVM.isExploreAdsTapped) { @@ -135,7 +137,14 @@ class AdsFragment extends StatelessWidget { }, child: adVM.state == ViewState.busy ? const Center(child: CircularProgressIndicator()) - : AdsListWidget(isAdsFragment: true, shouldShowAdStatus: !adVM.isExploreAdsTapped, adsList: getAdsList(adVM)), + : AdsListWidget( + isAdsFragment: true, + shouldShowAdStatus: !adVM.isExploreAdsTapped, + adsList: getAdsList(adVM), + onFetchMoreAds: () { + log("fetch more ads"); + }, + ), ), ) ], @@ -143,6 +152,7 @@ class AdsFragment extends StatelessWidget { ), floatingActionButton: FloatingActionButton( onPressed: () async { + adVM.getVehicleAdsDuration(); if (AppState().userType != UserType.providerDealer) { if (adVM.vehicleAdDurationId.selectedId == -1 && adVM.vehicleAdsDurations.isNotEmpty) { SelectionModel selection = SelectionModel( diff --git a/lib/views/common_fragments/requests_fragment.dart b/lib/views/common_fragments/requests_fragment.dart index 7ff9a4b..3d7544c 100644 --- a/lib/views/common_fragments/requests_fragment.dart +++ b/lib/views/common_fragments/requests_fragment.dart @@ -3,6 +3,7 @@ import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/requests_models/request_model.dart'; import 'package:mc_common_app/utils/dialogs_and_bottomsheets.dart'; import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; @@ -156,11 +157,25 @@ class MyRequestsFragment extends StatelessWidget { return; }, child: RequestItem( - request: requestsVM.myFilteredRequests[index], - appType: AppState().currentAppType, - requestIndex: index, - shouldShowStatuses: AppState().currentAppType == AppType.customer, - ), + request: requestsVM.myFilteredRequests[index], + shouldShowStatuses: AppState().currentAppType == AppType.customer, + onTap: () async { + RequestModel request = requestsVM.myFilteredRequests[index]; + requestsVM.updateCurrentSelectedRequest(request); + if (request.requestStatus == RequestStatusEnum.pending || + request.requestStatus == RequestStatusEnum.cancelled || + request.requestStatus == RequestStatusEnum.expired) { + return; + } + if (AppState().currentAppType == AppType.provider) { + RequestDetailPageArguments requestDetailPageArguments = RequestDetailPageArguments(requestIndex: index, requestModel: request); + navigateWithName(context, AppRoutes.requestsDetailPage, arguments: requestDetailPageArguments); + } else { + requestsVM.myFilteredRequests[index].offerCount = 0; + requestsVM.notifyListeners(); + navigateWithName(context, AppRoutes.offersListPage, arguments: request.id); + } + }), ); }, separatorBuilder: (context, index) { diff --git a/lib/views/requests/offer_list_page.dart b/lib/views/requests/offer_list_page.dart index 785d3f3..dab5a41 100644 --- a/lib/views/requests/offer_list_page.dart +++ b/lib/views/requests/offer_list_page.dart @@ -80,19 +80,21 @@ class _OfferListPageState extends State { fontSize: 16, isBold: true, ), - Center( - child: "${offersModel.offerCount}".toText( - color: Colors.white, - isBold: true, - fontSize: 10, + if (offersModel.offerCount != null && offersModel.offerCount! > 0) ...[ + Center( + child: "${offersModel.offerCount}".toText( + color: Colors.white, + isBold: true, + fontSize: 10, + ), + ).toContainer( + backgroundColor: MyColors.cancelledColor, + borderRadius: 100, + paddingAll: 1, + width: 22, + height: 22, ), - ).toContainer( - backgroundColor: MyColors.cancelledColor, - borderRadius: 100, - paddingAll: 1, - width: 22, - height: 22, - ), + ], ], ), 8.height, @@ -131,13 +133,16 @@ class _OfferListPageState extends State { await chatVM .getRequestsChatMessagesForCustomer( - context: context, - providerId: offersModel.providerId ?? 0, - requestOfferId: 0, - requestId: widget.requestId, - providerOfferIndex: index, - ) - .whenComplete(() => navigateWithName(context, AppRoutes.chatView, arguments: chatViewArguments)); + context: context, + providerId: offersModel.providerId ?? 0, + requestOfferId: 0, + requestId: widget.requestId, + providerOfferIndex: index, + ) + .whenComplete(() { + chatVM.serviceProviderOffersList[index].offerCount = 0; + navigateWithName(context, AppRoutes.chatView, arguments: chatViewArguments); + }); }).toContainer(isShadowEnabled: true); }, separatorBuilder: (context, index) => 16.height, diff --git a/lib/views/requests/request_bottomsheets.dart b/lib/views/requests/request_bottomsheets.dart index 01ad4be..725760e 100644 --- a/lib/views/requests/request_bottomsheets.dart +++ b/lib/views/requests/request_bottomsheets.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -21,8 +23,14 @@ import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; -Future buildSendOfferBottomSheet({required BuildContext context, required RequestDetailPageArguments requestDetailPageArguments, required bool isFromChatScreen}) { +Future buildSendOfferBottomSheet({ + required BuildContext context, + required RequestDetailPageArguments requestDetailPageArguments, + required bool isFromChatScreen, + required int? offerId, +}) { final requestDetail = requestDetailPageArguments.requestModel; + final requestIndex = requestDetailPageArguments.requestIndex; return showModalBottomSheet( context: context, isScrollControlled: true, @@ -134,24 +142,42 @@ Future buildSendOfferBottomSheet({required BuildContext context, required Reques ), 25.height, ShowFillButton( - title: LocaleKeys.submit.tr(), + title: offerId == null ? LocaleKeys.submit.tr() : LocaleKeys.update.tr(), maxHeight: 55, onPressed: () { - requestsVM.onSendOfferPressed( - context: context, - receiverId: requestDetail.customerID, - message: requestsVM.offerDescription, - requestId: requestDetail.id, - offerPrice: requestsVM.offerPrice, - requestModel: requestDetail, - isDeliveryAvailable: requestsVM.isDeliveryAvailableStatus, - requestIndex: requestDetailPageArguments.requestIndex, - isFromChatScreen: isFromChatScreen, - manufacturedById: 1, - // Todo: It should be the ID of the manufacturer - manufacturedOn: requestsVM.serviceItemCreatedOn, - serviceItemName: requestsVM.serviceItem, - ); + if (offerId == null) { + requestsVM.onSendOfferPressed( + context: context, + offerId: offerId, + receiverId: requestDetail.customerID, + message: requestsVM.offerDescription, + requestId: requestDetail.id, + offerPrice: requestsVM.offerPrice, + requestModel: requestDetail, + isDeliveryAvailable: requestsVM.isDeliveryAvailableStatus, + requestIndex: requestDetailPageArguments.requestIndex, + isFromChatScreen: isFromChatScreen, + manufacturedById: 1, + // Todo: It should be the ID of the manufacturer + manufacturedOn: requestsVM.serviceItemCreatedOn, + serviceItemName: requestsVM.serviceItem, + ); + } else { + requestsVM.updateOfferFromProvider( + message: requestsVM.offerDescription, + requestId: requestDetail.id, + offerId: offerId, + offerPrice: requestsVM.offerPrice, + isDeliveryAvailable: requestsVM.isDeliveryAvailableStatus, + serviceItemName: requestsVM.serviceItem, + manufacturedById: 1, + // Todo: It should be the ID of the manufacturer + manufacturedOn: requestsVM.serviceItemCreatedOn, + receiverId: requestDetail.customerId, + customerRequestIndex: requestIndex, + context: context, + ); + } }, maxWidth: double.infinity, ), diff --git a/lib/views/requests/request_detail_page.dart b/lib/views/requests/request_detail_page.dart index f1230ff..28ce552 100644 --- a/lib/views/requests/request_detail_page.dart +++ b/lib/views/requests/request_detail_page.dart @@ -77,7 +77,12 @@ class RequestDetailPage extends StatelessWidget { maxHeight: 55, title: LocaleKeys.completeDeal.tr(), onPressed: () { - return dealCompletedConfirmationBottomSheet(mainContext: context, requestStatusEnum: RequestStatusEnum.completed, requestId: requestId); + return dealCompletedConsentBottomSheet( + mainContext: context, + requestStatusEnum: RequestStatusEnum.completed, + requestId: requestId, + acceptRequestOffer: () {}, + ); }, ); } @@ -159,7 +164,12 @@ class RequestDetailPage extends StatelessWidget { title: LocaleKeys.provideOffer.tr(), isBold: false, fontSize: 18, - onPressed: () => buildSendOfferBottomSheet(context: context, requestDetailPageArguments: requestDetailPageArguments, isFromChatScreen: false), + onPressed: () => buildSendOfferBottomSheet( + context: context, + requestDetailPageArguments: requestDetailPageArguments, + isFromChatScreen: false, + offerId: null, // null means creating new offer + ), ), ] else ...[ buildRequestDetailActionFooter( diff --git a/lib/views/requests/review_request_offer.dart b/lib/views/requests/review_request_offer.dart index 9af95fe..4fef205 100644 --- a/lib/views/requests/review_request_offer.dart +++ b/lib/views/requests/review_request_offer.dart @@ -94,20 +94,25 @@ class _ReviewRequestOfferState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.locationInformation.tr().toText(fontSize: 18), - MyAssets.icEdit.buildSvg().onPress(() => buildLocationInformationEditBottomSheet(context, requestVM)), - ], - ), + LocaleKeys.locationInformation.tr().toText(fontSize: 18), + + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // LocaleKeys.locationInformation.tr().toText(fontSize: 18), + // MyAssets.icEdit.buildSvg().onPress(() => buildLocationInformationEditBottomSheet(context, requestVM)), + // ], + // ), 8.height, Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SingleDetailWidget(text: requestVM.currentSelectedRequest!.address ?? "", type: LocaleKeys.location.tr()), - 16.height, - SingleDetailWidget(text: requestVM.additionalAddressSparePartRequestDelivery.isNotEmpty ? requestVM.additionalAddressSparePartRequestDelivery : "N/A", type: LocaleKeys.additionalAddressDetails.tr()), + // 16.height, + // SingleDetailWidget( + // text: requestVM.additionalAddressSparePartRequestDelivery.isNotEmpty ? requestVM.additionalAddressSparePartRequestDelivery : "N/A", + // type: LocaleKeys.additionalAddressDetails.tr(), + // ), ], ), ], @@ -117,9 +122,13 @@ class _ReviewRequestOfferState extends State { Widget buildServiceInformation(BuildContext context) { final requestVM = context.read(); - String formattedDate = ""; + String manufacturedOnFormattedDate = ""; if (requestVM.acceptedRequestOffer!.manufacturedOn != null) { - formattedDate = DateHelper.formatAsDayMonthYear(DateHelper.parseStringToDate(DateHelper.formatDateT(requestVM.acceptedRequestOffer!.manufacturedOn.toString() ?? ""))); + manufacturedOnFormattedDate = DateHelper.formatAsDayMonthYear(DateHelper.parseStringToDate(DateHelper.formatDateT(requestVM.acceptedRequestOffer!.manufacturedOn.toString() ?? ""))); + } + String requestCreatedOn = ""; + if (requestVM.currentSelectedRequest!.createdOn != null) { + requestCreatedOn = DateHelper.formatAsDayMonthYear(DateHelper.parseStringToDate(DateHelper.formatDateT(requestVM.currentSelectedRequest!.createdOn.toString() ?? ""))); } return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -148,8 +157,8 @@ class _ReviewRequestOfferState extends State { 16.height, SingleDetailWidget(text: (requestVM.acceptedRequestOffer!.manufacturedById ?? "").toString(), type: LocaleKeys.manufacturedBy.tr()), ], - 16.height, - SingleDetailWidget(text: "${requestVM.acceptedRequestOffer!.price.toString()} SAR", type: LocaleKeys.offerPrice.tr()), + // 16.height, + // SingleDetailWidget(text: "${requestVM.acceptedRequestOffer!.price.toString()} ${LocaleKeys.sar.tr()}", type: LocaleKeys.offerPrice.tr()), 16.height, SingleDetailWidget(text: requestVM.acceptedRequestOfferProviderName ?? "", type: LocaleKeys.providerName.tr()), 16.height, @@ -165,12 +174,18 @@ class _ReviewRequestOfferState extends State { SingleDetailWidget(text: requestVM.currentSelectedRequest!.brand, type: LocaleKeys.vehicleBrand.tr()), 16.height, SingleDetailWidget(text: requestVM.acceptedRequestOffer!.serviceItemName ?? "", type: LocaleKeys.serviceName.tr()), - if (formattedDate.isNotEmpty) ...[ + if (manufacturedOnFormattedDate.isNotEmpty) ...[ 16.height, - SingleDetailWidget(text: formattedDate, type: LocaleKeys.manufacturedOn.tr()), + SingleDetailWidget(text: manufacturedOnFormattedDate, type: LocaleKeys.manufacturedOn.tr()), ], 16.height, - SingleDetailWidget(text: "${requestVM.currentSelectedRequest!.price.toString()} SAR", type: LocaleKeys.totalPrice.tr()), + // SingleDetailWidget(text: "${requestVM.currentSelectedRequest!.price.toString()} ${LocaleKeys.sar.tr()}", type: LocaleKeys.totalPrice.tr()), + SingleDetailWidget(text: "${requestVM.acceptedRequestOffer!.price.toString()} ${LocaleKeys.sar.tr()}", type: LocaleKeys.offerPrice.tr()), + + if (requestCreatedOn.isNotEmpty) ...[ + 16.height, + SingleDetailWidget(text: requestCreatedOn, type: LocaleKeys.requestCreatedOn.tr()), + ], ], ), ), diff --git a/lib/views/requests/widget/request_item.dart b/lib/views/requests/widget/request_item.dart index 57e3c4d..724abda 100644 --- a/lib/views/requests/widget/request_item.dart +++ b/lib/views/requests/widget/request_item.dart @@ -2,27 +2,22 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:mc_common_app/config/routes.dart'; +import 'package:mc_common_app/classes/app_state.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/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/extensions/extensions_widget.dart'; -import 'package:provider/provider.dart'; class RequestItem extends StatelessWidget { final RequestModel request; - final AppType appType; - final int requestIndex; final bool shouldShowStatuses; + final Function() onTap; - const RequestItem({super.key, required this.request, required this.appType, required this.requestIndex, this.shouldShowStatuses = true}); + const RequestItem({super.key, required this.request, this.shouldShowStatuses = true, required this.onTap}); @override Widget build(BuildContext context) { @@ -65,7 +60,7 @@ class RequestItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (request.offerCount > 0 && appType == AppType.customer && request.requestStatus == RequestStatusEnum.submitted) ...[ + if (request.offerCount > 0 && AppState().currentAppType == AppType.customer && request.requestStatus == RequestStatusEnum.submitted) ...[ Center( child: "${request.offerCount}".toText( color: Colors.white, @@ -112,17 +107,7 @@ class RequestItem extends StatelessWidget { // ), ], ).toContainer(isShadowEnabled: true).onPress(() async { - RequestsVM requestsVM = context.read(); - requestsVM.updateCurrentSelectedRequest(request); - if (request.requestStatus == RequestStatusEnum.pending || request.requestStatus == RequestStatusEnum.cancelled || request.requestStatus == RequestStatusEnum.expired) { - return; - } - if (appType == AppType.provider) { - RequestDetailPageArguments requestDetailPageArguments = RequestDetailPageArguments(requestIndex: requestIndex, requestModel: request); - navigateWithName(context, AppRoutes.requestsDetailPage, arguments: requestDetailPageArguments); - } else { - navigateWithName(context, AppRoutes.offersListPage, arguments: request.id); - } + onTap(); }); } diff --git a/lib/views/setting_options/provider_accepted_requests_view.dart b/lib/views/setting_options/provider_accepted_requests_view.dart index b6c8b51..ce38770 100644 --- a/lib/views/setting_options/provider_accepted_requests_view.dart +++ b/lib/views/setting_options/provider_accepted_requests_view.dart @@ -5,6 +5,7 @@ import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/requests_models/request_model.dart'; import 'package:mc_common_app/utils/dialogs_and_bottomsheets.dart'; import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; @@ -80,7 +81,24 @@ class _ProviderAcceptedRequestsViewState extends State