diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 0237366..24c5b32 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -698,7 +698,6 @@ "explore": "استكشاف", "manageRequests": "إدارة الطلبات", "serviceNotAvailableAtHomeLocation": "الخدمة المختارة غير متوفرة في الموقع المحدد.", - "noAvailableItems": "لا توجد عناصر متاحة.", "wantToRescheduleAppointment": "أرغب في إعادة جدولة الموعد.", "noNeedForService": "لا أحتاج هذه الخدمة بعد الآن.", "testTheService": "اختبر الخدمة.", @@ -706,9 +705,44 @@ "customerLocation": "موقع العميل", "deliveryAvailable": "التوصيل متاح", "viewed": "تم المشاهدة", - "itemNoLongerAvailable": "لم يعد هذا العنصر متاحًا.", "updateUserDetails": "تحديث تفاصيل المستخدم", "enterNewFirstName": "أدخل الاسم الأول", "enterNewLastName": "أدخل الاسم الأخير", "userDetailsUpdated": "يتم تحديث تفاصيل المستخدم" + "itemNoLongerAvailable": "لم يعد هذا العنصر متاحًا.", + "reactivateAd": "إعادة تنشيط الإعلان", + "dealOutsideApp": "تمت الصفقة خارج التطبيق مع عميل آخر.", + "noAgreementCustomer": "لا يوجد اتفاق من جانب العميل.", + "dealNotCompleted": "لم تكتمل الصفقة", + "changedDesireToSell": "غيرت رغبتي في بيع المركبة.", + "vehicleDealOutsideApp": "تمت صفقة المركبة خارج التطبيق.", + "markAdAsSoldDesc": "سيتم وضع علامة 'تم البيع' على هذا الإعلان، ولن يتمكن المستخدمون الآخرون من التواصل معك.", + "ownerInformation": "معلومات المالك", + "acceptedRequests": "الطلبات المقبولة", + "specialRequestChat": "دردشة الطلب الخاص", + "companyName": "اسم الشركة", + "noAvailableItems": "لا توجد عناصر متاحة.", + "serviceDeliveryType": "نوع تقديم الخدمة", + "noImagesToShow": "لا توجد صور للعرض", + "updateGroupServices": "تحديث خدمات المجموعة", + "addCommentsHere": "أضف التعليقات هنا", + "waitForBranchVerification": "يرجى الانتظار حتى يتم التحقق من الفرع.", + "reportComplaint": "تقديم شكوى", + "descriptionCannotBeEmpty": "وصف لا يمكن أن يكون فارغًا.", + "adMarkedAsSold": "تم وسم الإعلان كـ 'مباع' بنجاح!", + "chargesAndServiceRangeGreaterThanZero": "يجب أن تكون الرسوم ونطاق الخدمة أكبر من الصفر.", + "cannotSelectAllDaysAsOff": "لا يمكنك اختيار جميع الأيام كأيام عطلة.", + "endDateAfterStartDate": "يجب أن تكون تاريخ النهاية بعد تاريخ البدء.", + "pleaseEnterStartDateFirst": "من فضلك أدخل تاريخ البدء أولاً.", + "endTimeAfterStartTime": "يجب أن يكون وقت الانتهاء بعد وقت البدء.", + "pickedLocationNotice": "سيتم استخدام هذا الموقع لجميع الخدمات المنزلية في هذه الموعد", + "serviceNotProvided": "لسوء الحظ، هذه الخدمة غير متوفرة في الفرع الذي اخترته في هذا الموقع المحدد", + "pleaseSelectService": "يرجى اختيار خدمة واحدة على الأقل", + "operationalIssue": "مشكلة تشغيلية", + "materialIssue": "مشكلة في المواد", + "selectReasonBeforeCancel": "يرجى اختيار سبب قبل إلغاء هذا الموعد. لا يمكن التراجع عن هذا الإجراء.", + "blockedByAdmin": "تم حظرها من قبل المسؤول", + "active": "نشط", + "paymentType": "نوع الدفع", + "searchByCreatedDate": "البحث حسب تاريخ الإنشاء" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 086972a..3eb6430 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -626,7 +626,7 @@ "priceRange": "Price Range", "cancelOffer": "Cancel Offer", "pleaseSpecify": "Please Specify", - "customerNotResponding": "Customer Not Responding", + "customerNotResponding": "The customer is not responding.", "cancelRequestPrompt": "Do you want to cancel this request?", "requestPermanentlyCancelled": "Your request will be permanently cancelled. You cannot undo this action.", "awaitingResponseFromCustomer": "Awaiting Response From Customer", @@ -646,7 +646,7 @@ "customerCarePrompt": "Got any question? We are here to service. Click here to talk to our customer care right now.", "callNow": "Call Now", "condition": "Condition", - "servicesNotAvailableHomeLocation": "Some services are not available on home location.", + "servicesNotAvailableHomeLocation": "This location will be same for all services", "additionalChargesNotice": "These charges are additional to the actual service charges. For heavy items the charges may vary.", "selectServiceLocation": "Select Service Location", "apptOn": "Appt On", @@ -695,7 +695,6 @@ "explore": "Explore", "manageRequests": "Manage Requests", "serviceNotAvailableAtHomeLocation": "The Selected Service is not available at Home Location.", - "noAvailableItems": "There are no available items.", "wantToRescheduleAppointment": "I want to reschedule the appointment.", "noNeedForService": "I do not need this service anymore.", "testTheService": "Test the service.", @@ -704,6 +703,43 @@ "deliveryAvailable": "Delivery Available", "viewed": "Viewed", "itemNoLongerAvailable": "This item is no longer available.", + "reactivateAd": "Reactivate Ad", + "dealOutsideApp": "The deal was done outside the app with another customer.", + "noAgreementCustomer": "There is no agreement from the customer side.", + "dealNotCompleted": "Deal not completed", + "changedDesireToSell": "I changed my desire to sell the vehicle.", + "vehicleDealOutsideApp": "The vehicle deal was done outside the app.", + "markAdAsSoldDesc": "This ad will be marked as sold, and other users will no longer be able to contact you.", + "ownerInformation": "Owner Information", + "noItemsToShow": "There are no Items no show.", + "acceptedRequests": "Accepted Requests", + "specialRequestChat": "Special Request Chat", + "companyName": "Company Name", + "noAvailableItems": "There are no available items.", + "serviceDeliveryType": "Service Delivery Type", + "noImagesToShow": "No Images to Show", + "updateGroupServices": "Update Group Services", + "addCommentsHere": "Add comments here", + "waitForBranchVerification": "Please wait for the branch verification.", + "reportComplaint": "Report a complaint", + "descriptionCannotBeEmpty": "Description cannot be empty.", + "adMarkedAsSold": "Ad has been marked as sold successfully!", + "chargesAndServiceRangeGreaterThanZero": "Charges and Service range should be greater than zero.", + "cannotSelectAllDaysAsOff": "You cannot select all days as Off days.", + "endDateAfterStartDate": "End Date should be after start date.", + "pleaseEnterStartDateFirst": "Please enter start date first.", + "endTimeAfterStartTime": "End Time should be after start time.", + "pickedLocationNotice": "This picked location will be used for all home-based services in this appointment", + "serviceNotProvided": "Unfortunately, this service is not provided by your selected branch in this picked location.", + "pleaseSelectService": "Please select at least one service.", + "operationalIssue": "Operational Issue", + "materialIssue": "Material Issue", + "selectReasonBeforeCancel": "Please select a reason before cancelling this appointment. This action cannot be undone.", + "blockedByAdmin": "Blocked by admin", + "active": "Active", + "paymentType": "Payment Type", + "searchByCreatedDate": "Search By Created Date" + "itemNoLongerAvailable": "This item is no longer available.", "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 8f9d27b..2ae3e1d 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -14,30 +14,32 @@ class ApiConsts { static String basicComplete = "${baseUrlServices}api/Register/BasicComplete"; static String refreshToken = "${baseUrlServices}api/Account/RefreshToken"; + static String signalRUrl = "${baseUrl}McHub"; + //User - static String Login_V1 = "${baseUrlServices}api/Account/Login_V1"; - static String Login_V2_OTP = "${baseUrlServices}api/Account/Login_V2_OTP"; - static String Login_V2_OTPVerify = "${baseUrlServices}api/Account/Login_V2_OTPVerify"; + static String loginV1 = "${baseUrlServices}api/Account/Login_V1"; + static String loginV2OTP = "${baseUrlServices}api/Account/Login_V2_OTP"; + static String loginV2OTPVerify = "${baseUrlServices}api/Account/Login_V2_OTPVerify"; static String user = "${baseUrlServices}api/User/"; - static String GetAllCountry = "${baseUrlServices}api/Master/Country_Get"; - static String GetProviderRoles = "${baseUrlServices}api/Master/RoleServiceProvider_Get"; - static String GetAllCities = "${baseUrlServices}api/Master/City_Get"; - static String ForgetPasswordOTPRequest = "${baseUrlServices}api/Account/ForgetPasswordOTPRequest"; - static String ForgetPasswordOTPCompare = "${baseUrlServices}api/Account/ForgetPasswordOTPCompare"; - static String ForgetPassword = "${baseUrlServices}api/Account/ForgetPassword"; - static String Login_Email_OTP = "${baseUrlServices}api/Account/EmailVerify"; - static String Login_Email_OTPVerify = "${baseUrlServices}api/Account/EmailVerifyOTPVerify"; - static String ChangePassword = "${baseUrlServices}api/Account/ChangePassword"; - static String ChangeMobileNoOTPRequest = "${baseUrlServices}api/Account/ChangeMobileNoOTPRequest"; - static String ChangeMobileNo = "${baseUrlServices}api/Account/ChangeMobileNo"; - static String ChangeEmailOTPRequest = "${baseUrlServices}api/Account/ChangeEmailOTPRequest"; - static String ChangeEmail = "${baseUrlServices}api/Account/ChangeEmail"; - static String EmailVerify = "${baseUrlServices}api/Account/EmailVerify"; - static String EmailVerifyOTPVerify = "${baseUrlServices}api/Account/EmailVerifyOTPVerify"; - static String LogoutUser = "${baseUrlServices}api/Account/Logout"; - static String UpdateUserImage = "${baseUrlServices}api/User_UpdateProfileImage"; - static String GetUserImage = "${baseUrlServices}api/ProfileImage"; - static String userUpdate = "${baseUrlServices}api/User_Update"; + static String getAllCountry = "${baseUrlServices}api/Master/Country_Get"; + static String getProviderRoles = "${baseUrlServices}api/Master/RoleServiceProvider_Get"; + static String getAllCities = "${baseUrlServices}api/Master/City_Get"; + static String forgetPasswordOTPRequest = "${baseUrlServices}api/Account/ForgetPasswordOTPRequest"; + static String forgetPasswordOTPCompare = "${baseUrlServices}api/Account/ForgetPasswordOTPCompare"; + static String forgetPassword = "${baseUrlServices}api/Account/ForgetPassword"; + static String loginEmailOTP = "${baseUrlServices}api/Account/EmailVerify"; + static String loginEmailOTPVerify = "${baseUrlServices}api/Account/EmailVerifyOTPVerify"; + static String changePassword = "${baseUrlServices}api/Account/ChangePassword"; + static String changeMobileNoOTPRequest = "${baseUrlServices}api/Account/ChangeMobileNoOTPRequest"; + static String changeMobileNo = "${baseUrlServices}api/Account/ChangeMobileNo"; + static String changeEmailOTPRequest = "${baseUrlServices}api/Account/ChangeEmailOTPRequest"; + static String changeEmail = "${baseUrlServices}api/Account/ChangeEmail"; + static String emailVerify = "${baseUrlServices}api/Account/EmailVerify"; + static String emailVerifyOTPVerify = "${baseUrlServices}api/Account/EmailVerifyOTPVerify"; + static String logoutUser = "${baseUrlServices}api/Account/Logout"; + static String updateUserImage = "${baseUrlServices}api/User_UpdateProfileImage"; + static String getUserImage = "${baseUrlServices}api/ProfileImage"; + static String providerComplaintCreate = "${baseUrlServices}api/ServiceProviders/ProviderComplaint_Create"; //Profile static String fetProviderDocument = "${baseUrlServices}api/ServiceProviders/ServiceProviderDocument_Get"; @@ -97,6 +99,7 @@ class ApiConsts { static String updateSchedule = "${baseUrlServices}api/ServiceProviders/BranchAppointmentSchedule_Update"; static String createGroup = "${baseUrlServices}api/ServiceProviders/BranchScheduleGroupService_Create"; static String updateGroup = "${baseUrlServices}api/ServiceProviders/BranchScheduleGroupService_Update"; + static String checkGroupServiceInBranchSchedule = "${baseUrlServices}api/ServiceProviders/BranchScheduleGroupService_Check"; //Advertisement APIs static String vehicleTypeGet = "${baseUrlServices}api/ServiceProviders/VehicleType_Get"; @@ -149,7 +152,6 @@ class ApiConsts { //Branch Users static String getAllProviderDealers = "${baseUrlServices}api/ServiceProviders/DealershipUserBranchWise_Get"; - static String getBranchUser = "${baseUrlServices}api/ServiceProviders/BranchUser_Get"; static String assignDealerToBranch = "${baseUrlServices}api/ServiceProviders/BranchUser_Create"; static String removeDealerFromBranch = "${baseUrlServices}api/ServiceProviders/BranchUser_Update"; @@ -228,7 +230,8 @@ class GlobalConsts { static String homeLocationEmptyError = "Home location cannot be empty"; static String fillAllFields = "Please fill out all the fields."; static String requestTypeCannotBeEmpty = "Request type cannot be empty."; - static String reserveAdPriceInfo = "Some dummy description to explain the following concept. This price will be for 24 hours and if a user cancels the reservations before 24 hours then the amount will be automatically refunded to the buyer."; + static String reserveAdPriceInfo = + "Some dummy description to explain the following concept. This price will be for 24 hours and if a user cancels the reservations before 24 hours then the amount will be automatically refunded to the buyer."; static String appInvitationMessageEn = "🚗 Hey , check out MOWATER to book car services, buy/sell vehicles, and more—join here! [Invite Link]"; static String getAppInvitationLink() { diff --git a/lib/config/routes.dart b/lib/config/routes.dart index e77d260..4af01bf 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -18,6 +18,7 @@ import 'package:mc_common_app/views/requests/offer_list_page.dart'; import 'package:mc_common_app/views/requests/request_detail_page.dart'; import 'package:mc_common_app/views/requests/requests_filter_view.dart'; import 'package:mc_common_app/views/requests/review_request_offer.dart'; +import 'package:mc_common_app/views/setting_options/provider_accepted_requests_view.dart'; import 'package:mc_common_app/views/setting_options/provider_license_page.dart'; import 'package:mc_common_app/views/setting_options/setting_option_help.dart'; import 'package:mc_common_app/views/setting_options/setting_options_app_info.dart'; @@ -146,6 +147,7 @@ class AppRoutes { static const String createRequestPage = "/createRequestPage"; static const String offersListPage = "/offersListPage"; static const String reviewRequestOffer = "/reviewRequestOffer"; + static const String providerAcceptedRequestsView = "/providerAcceptedRequestsView"; //Setting Options static const String settingOptionsFaqs = "/settingOptionsFaqs"; @@ -211,7 +213,7 @@ class AppRoutes { //Requests AppRoutes.requestsDetailPage: (context) => RequestDetailPage(requestDetailPageArguments: ModalRoute.of(context)!.settings.arguments as RequestDetailPageArguments), AppRoutes.createRequestPage: (context) => const CreateRequestPage(), - AppRoutes.offersListPage: (context) => OfferListPage(offerListPageArguments: ModalRoute.of(context)!.settings.arguments as OfferListPageArguments), + AppRoutes.offersListPage: (context) => OfferListPage(requestId: ModalRoute.of(context)!.settings.arguments as int), AppRoutes.reviewRequestOffer: (context) => const ReviewRequestOffer(), AppRoutes.requestsFilterView: (context) => const RequestsFilterView(), @@ -219,7 +221,7 @@ class AppRoutes { AppRoutes.mediaViewerScreen: (context) => MediaViewerScreen(images: ModalRoute.of(context)!.settings.arguments as List), // ChatsList Provider - AppRoutes.generalChatsListForProvider: (context) => OfferListPage(offerListPageArguments: ModalRoute.of(context)!.settings.arguments as OfferListPageArguments), + AppRoutes.generalChatsListForProvider: (context) => OfferListPage(requestId: ModalRoute.of(context)!.settings.arguments as int), //Shipping AppRoutes.shippingManagementView: (context) => const ShippingManagementView(), diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 15572cb..674bf8d 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -238,6 +238,10 @@ extension AdPostEnum on int { return AdPostStatus.buyingService; } else if (this == 11) { return AdPostStatus.reserveCancel; + } else if (this == 12) { + return AdPostStatus.deActive; + } else if (this == 13) { + return AdPostStatus.pendingForActive; } else if (this == 0) { return AdPostStatus.allAds; } else { @@ -264,15 +268,21 @@ extension AppointmentStatusToInt on AppointmentStatusEnum { case AppointmentStatusEnum.rescheduled: return 5; - case AppointmentStatusEnum.upcoming: - return 6; - case AppointmentStatusEnum.workStarted: return 7; case AppointmentStatusEnum.visitCompleted: return 8; + case AppointmentStatusEnum.today: + return 9; + + case AppointmentStatusEnum.past: + return 10; + + case AppointmentStatusEnum.upcoming: + return 11; + case AppointmentStatusEnum.allAppointments: return 0; @@ -334,14 +344,18 @@ extension AppointmentStatusToString on AppointmentStatusEnum { case AppointmentStatusEnum.visitCompleted: return "Visit Completed"; + case AppointmentStatusEnum.past: + return "Past"; + + case AppointmentStatusEnum.today: + return "Today"; + default: return "Booked"; } } } -//TODO: Need to verify Enum on upcoming and inprogress with the database -//TODO: 6 is service Deactivated extension AppointmentEnum on int { AppointmentStatusEnum toAppointmentStatusEnum() { if (this == 1) { @@ -354,12 +368,16 @@ extension AppointmentEnum on int { return AppointmentStatusEnum.cancelled; } else if (this == 5) { return AppointmentStatusEnum.rescheduled; - } else if (this == 6) { - return AppointmentStatusEnum.upcoming; } else if (this == 7) { return AppointmentStatusEnum.workStarted; } else if (this == 8) { return AppointmentStatusEnum.visitCompleted; + } else if (this == 9) { + return AppointmentStatusEnum.today; + } else if (this == 10) { + return AppointmentStatusEnum.past; + } else if (this == 11) { + return AppointmentStatusEnum.upcoming; } else { return AppointmentStatusEnum.allAppointments; } @@ -409,7 +427,7 @@ extension AppointmentPaymentEnum on int { } extension RequestTypeTypEnum on int { - RequestsTypeEnum toRequestTypeStatusEnum() { + RequestsTypeEnum toRequestTypeEnum() { if (this == 1) { return RequestsTypeEnum.specialCarRequest; } else if (this == 2) { @@ -420,7 +438,7 @@ extension RequestTypeTypEnum on int { } extension RequestTypeStatusToInt on RequestsTypeEnum { - int getIdFromRequestTypeStatusEnum() { + int getIdFromRequestTypeEnum() { switch (this) { case RequestsTypeEnum.specialCarRequest: return 1; @@ -439,6 +457,7 @@ extension AdPostStatusToInt on AdPostStatus { switch (this) { case AdPostStatus.pendingForReview: return 1; + case AdPostStatus.pendingForPayment: return 2; @@ -468,6 +487,13 @@ extension AdPostStatusToInt on AdPostStatus { case AdPostStatus.reserveCancel: return 11; + + case AdPostStatus.deActive: + return 12; + + case AdPostStatus.pendingForActive: + return 13; + default: return 0; } @@ -616,6 +642,33 @@ extension ServiceEnum on int { } } +extension BranchServiceEnumStatusToInt on ServiceStatusEnum { + int getIdFromServiceStatusEnum() { + switch (this) { + case ServiceStatusEnum.pending: + return 1; + + case ServiceStatusEnum.review: + return 2; + + case ServiceStatusEnum.approvedOrActive: + return 3; + + case ServiceStatusEnum.rejected: + return 4; + + case ServiceStatusEnum.blocked: + return 5; + + case ServiceStatusEnum.deactivated: + return 6; + + default: + return 1; + } + } +} + extension DateTimeConversions on DateTime { String getTimeAgo({bool numericDates = true}) { final date2 = DateTime.now(); @@ -910,8 +963,10 @@ extension SubscriptionTypeEnumToString on SubscriptionTypeEnum { extension ShippingStatusEnumExt on int { ShippingRequestStatusEnum toShippingStatusEnum() { - if (this == 0) { + if (this == -1) { return ShippingRequestStatusEnum.allRequests; + } else if (this == 0) { + return ShippingRequestStatusEnum.pending; } else if (this == 1) { return ShippingRequestStatusEnum.initiated; } else if (this == 2) { @@ -921,13 +976,15 @@ extension ShippingStatusEnumExt on int { } else if (this == 4) { return ShippingRequestStatusEnum.delivered; } - return ShippingRequestStatusEnum.initiated; + return ShippingRequestStatusEnum.pending; } } extension ShippingStatusEnumToInt on ShippingRequestStatusEnum { int getIdFromShippingStatusEnum() { switch (this) { + case ShippingRequestStatusEnum.pending: + return 0; case ShippingRequestStatusEnum.initiated: return 1; case ShippingRequestStatusEnum.inTransit: @@ -936,7 +993,6 @@ extension ShippingStatusEnumToInt on ShippingRequestStatusEnum { return 3; case ShippingRequestStatusEnum.delivered: return 4; - default: return 0; } diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index c4a4e19..5a4d936 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -714,7 +714,6 @@ class CodegenLoader extends AssetLoader{ "explore": "استكشاف", "manageRequests": "إدارة الطلبات", "serviceNotAvailableAtHomeLocation": "الخدمة المختارة غير متوفرة في الموقع المحدد.", - "noAvailableItems": "لا توجد عناصر متاحة.", "wantToRescheduleAppointment": "أرغب في إعادة جدولة الموعد.", "noNeedForService": "لا أحتاج هذه الخدمة بعد الآن.", "testTheService": "اختبر الخدمة.", @@ -726,7 +725,42 @@ class CodegenLoader extends AssetLoader{ "updateUserDetails": "تحديث تفاصيل المستخدم", "enterNewFirstName": "أدخل الاسم الأول", "enterNewLastName": "أدخل الاسم الأخير", - "userDetailsUpdated": "يتم تحديث تفاصيل المستخدم" + "userDetailsUpdated": "يتم تحديث تفاصيل المستخدم", + "reactivateAd": "إعادة تنشيط الإعلان", + "dealOutsideApp": "تمت الصفقة خارج التطبيق مع عميل آخر.", + "noAgreementCustomer": "لا يوجد اتفاق من جانب العميل.", + "dealNotCompleted": "لم تكتمل الصفقة", + "changedDesireToSell": "غيرت رغبتي في بيع المركبة.", + "vehicleDealOutsideApp": "تمت صفقة المركبة خارج التطبيق.", + "markAdAsSoldDesc": "سيتم وضع علامة 'تم البيع' على هذا الإعلان، ولن يتمكن المستخدمون الآخرون من التواصل معك.", + "ownerInformation": "معلومات المالك", + "acceptedRequests": "الطلبات المقبولة", + "specialRequestChat": "دردشة الطلب الخاص", + "companyName": "اسم الشركة", + "noAvailableItems": "لا توجد عناصر متاحة.", + "serviceDeliveryType": "نوع تقديم الخدمة", + "noImagesToShow": "لا توجد صور للعرض", + "updateGroupServices": "تحديث خدمات المجموعة", + "addCommentsHere": "أضف التعليقات هنا", + "waitForBranchVerification": "يرجى الانتظار حتى يتم التحقق من الفرع.", + "reportComplaint": "تقديم شكوى", + "descriptionCannotBeEmpty": "وصف لا يمكن أن يكون فارغًا.", + "adMarkedAsSold": "تم وسم الإعلان كـ 'مباع' بنجاح!", + "chargesAndServiceRangeGreaterThanZero": "يجب أن تكون الرسوم ونطاق الخدمة أكبر من الصفر.", + "cannotSelectAllDaysAsOff": "لا يمكنك اختيار جميع الأيام كأيام عطلة.", + "endDateAfterStartDate": "يجب أن تكون تاريخ النهاية بعد تاريخ البدء.", + "pleaseEnterStartDateFirst": "من فضلك أدخل تاريخ البدء أولاً.", + "endTimeAfterStartTime": "يجب أن يكون وقت الانتهاء بعد وقت البدء.", + "pickedLocationNotice": "سيتم استخدام هذا الموقع لجميع الخدمات المنزلية في هذه الموعد", + "serviceNotProvided": "لسوء الحظ، هذه الخدمة غير متوفرة في الفرع الذي اخترته في هذا الموقع المحدد", + "pleaseSelectService": "يرجى اختيار خدمة واحدة على الأقل", + "operationalIssue": "مشكلة تشغيلية", + "materialIssue": "مشكلة في المواد", + "selectReasonBeforeCancel": "يرجى اختيار سبب قبل إلغاء هذا الموعد. لا يمكن التراجع عن هذا الإجراء.", + "blockedByAdmin": "تم حظرها من قبل المسؤول", + "active": "نشط", + "paymentType": "نوع الدفع", + "searchByCreatedDate": "البحث حسب تاريخ الإنشاء" }; static const Map en_US = { "firstTimeLogIn": "First Time Log In", @@ -1425,7 +1459,6 @@ static const Map en_US = { "explore": "Explore", "manageRequests": "Manage Requests", "serviceNotAvailableAtHomeLocation": "The Selected Service is not available at Home Location.", - "noAvailableItems": "There are no available items.", "wantToRescheduleAppointment": "I want to reschedule the appointment.", "noNeedForService": "I do not need this service anymore.", "testTheService": "Test the service.", @@ -1438,6 +1471,42 @@ static const Map en_US = { "enterNewFirstName": "Enter First Name", "enterNewLastName": "Enter Last Name", "userDetailsUpdated": "User Details is Updated" + "reactivateAd": "Reactivate Ad", + "dealOutsideApp": "The deal was done outside the app with another customer.", + "noAgreementCustomer": "There is no agreement from the customer side.", + "dealNotCompleted": "Deal not completed", + "changedDesireToSell": "I changed my desire to sell the vehicle.", + "vehicleDealOutsideApp": "The vehicle deal was done outside the app.", + "markAdAsSoldDesc": "This ad will be marked as sold, and other users will no longer be able to contact you.", + "ownerInformation": "Owner Information", + "noItemsToShow": "There are no Items no show.", + "acceptedRequests": "Accepted Requests", + "specialRequestChat": "Special Request Chat", + "companyName": "Company Name", + "noAvailableItems": "There are no available items.", + "serviceDeliveryType": "Service Delivery Type", + "noImagesToShow": "No Images to Show", + "updateGroupServices": "Update Group Services", + "addCommentsHere": "Add comments here", + "waitForBranchVerification": "Please wait for the branch verification.", + "reportComplaint": "Report a complaint", + "descriptionCannotBeEmpty": "Description cannot be empty.", + "adMarkedAsSold": "Ad has been marked as sold successfully!", + "chargesAndServiceRangeGreaterThanZero": "Charges and Service range should be greater than zero.", + "cannotSelectAllDaysAsOff": "You cannot select all days as Off days.", + "endDateAfterStartDate": "End Date should be after start date.", + "pleaseEnterStartDateFirst": "Please enter start date first.", + "endTimeAfterStartTime": "End Time should be after start time.", + "pickedLocationNotice": "This picked location will be used for all home-based services in this appointment", + "serviceNotProvided": "Unfortunately, this service is not provided by your selected branch in this picked location.", + "pleaseSelectService": "Please select at least one service.", + "operationalIssue": "Operational Issue", + "materialIssue": "Material Issue", + "selectReasonBeforeCancel": "Please select a reason before cancelling this appointment. This action cannot be undone.", + "blockedByAdmin": "Blocked by admin", + "active": "Active", + "paymentType": "Payment Type", + "searchByCreatedDate": "Search By Created Date" }; static const Map> mapLocales = {"ar_SA": ar_SA, "en_US": en_US}; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 6e23a69..4d6b505 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -677,7 +677,6 @@ abstract class LocaleKeys { static const explore = 'explore'; static const manageRequests = 'manageRequests'; static const serviceNotAvailableAtHomeLocation = 'serviceNotAvailableAtHomeLocation'; - static const noAvailableItems = 'noAvailableItems'; static const wantToRescheduleAppointment = 'wantToRescheduleAppointment'; static const noNeedForService = 'noNeedForService'; static const testTheService = 'testTheService'; @@ -686,6 +685,41 @@ abstract class LocaleKeys { static const deliveryAvailable = 'deliveryAvailable'; static const viewed = 'viewed'; static const itemNoLongerAvailable = 'itemNoLongerAvailable'; + static const reactivateAd = 'reactivateAd'; + static const dealOutsideApp = 'dealOutsideApp'; + static const noAgreementCustomer = 'noAgreementCustomer'; + static const dealNotCompleted = 'dealNotCompleted'; + static const changedDesireToSell = 'changedDesireToSell'; + static const vehicleDealOutsideApp = 'vehicleDealOutsideApp'; + static const markAdAsSoldDesc = 'markAdAsSoldDesc'; + static const ownerInformation = 'ownerInformation'; + static const acceptedRequests = 'acceptedRequests'; + static const specialRequestChat = 'specialRequestChat'; + static const companyName = 'companyName'; + static const noAvailableItems = 'noAvailableItems'; + static const serviceDeliveryType = 'serviceDeliveryType'; + static const noImagesToShow = 'noImagesToShow'; + static const updateGroupServices = 'updateGroupServices'; + static const addCommentsHere = 'addCommentsHere'; + static const waitForBranchVerification = 'waitForBranchVerification'; + static const reportComplaint = 'reportComplaint'; + static const descriptionCannotBeEmpty = 'descriptionCannotBeEmpty'; + static const adMarkedAsSold = 'adMarkedAsSold'; + static const chargesAndServiceRangeGreaterThanZero = 'chargesAndServiceRangeGreaterThanZero'; + static const cannotSelectAllDaysAsOff = 'cannotSelectAllDaysAsOff'; + static const endDateAfterStartDate = 'endDateAfterStartDate'; + static const pleaseEnterStartDateFirst = 'pleaseEnterStartDateFirst'; + static const endTimeAfterStartTime = 'endTimeAfterStartTime'; + static const pickedLocationNotice = 'pickedLocationNotice'; + static const serviceNotProvided = 'serviceNotProvided'; + static const pleaseSelectService = 'pleaseSelectService'; + static const operationalIssue = 'operationalIssue'; + static const materialIssue = 'materialIssue'; + static const selectReasonBeforeCancel = 'selectReasonBeforeCancel'; + static const blockedByAdmin = 'blockedByAdmin'; + static const active = 'active'; + static const paymentType = 'paymentType'; + static const searchByCreatedDate = 'searchByCreatedDate'; static const updateUserDetails = 'updateUserDetails'; static const enterNewFirstName = 'enterNewFirstName'; static const enterNewLastName = 'enterNewLastName'; diff --git a/lib/main.dart b/lib/main.dart index 4d3fd7e..85a9d62 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,3 +12,4 @@ bool disableThingsForQA = true; // todo terminal command to genertate translation keys // flutter pub run easy_localization:generate --source-dir ./assets/langs -f keys -o locale_keys.g.dart // command to generate languages data from json + diff --git a/lib/models/advertisment_models/ad_details_model.dart b/lib/models/advertisment_models/ad_details_model.dart index f239903..fb9ba56 100644 --- a/lib/models/advertisment_models/ad_details_model.dart +++ b/lib/models/advertisment_models/ad_details_model.dart @@ -45,6 +45,8 @@ class AdDetailsModel { String? phoneNo; String? whatsAppNo; String? adOwnerName; + String? adOwnerEmail; + AdOwnerDetails? adOwnerDetails; String? warrantyYears; CreatedByRoleEnum? createdByRoleEnum; List? adMessages; @@ -87,6 +89,8 @@ class AdDetailsModel { this.phoneNo, this.whatsAppNo, this.adOwnerName, + this.adOwnerEmail, + this.adOwnerDetails, this.warrantyYears, this.createdByRoleEnum, this.modifiedOn, @@ -134,11 +138,13 @@ class AdDetailsModel { priceExcludingDiscount = json['priceExcludingDiscount']; reservePrice = json['reservePrice']; isMCHandled = json['isMCHandled']; - modifiedOn = json['modifiedOn']; + modifiedOn = json['modifiedOn']; phoneNo = json['vehicle'] != null ? (json['vehicle']['mobileNo'] ?? "") : ""; whatsAppNo = json['vehicle'] != null ? (json['vehicle']['whatsAppNo'] ?? "") : ""; warrantyYears = json['vehicle'] != null ? (json['vehicle']['warantyYears'] != null ? ((json['vehicle']['warantyYears']).toString()) : "") : ""; - adOwnerName = json['adOwnerName'] ?? ""; + adOwnerName = json['vehicle'] != null ? (json['vehicle']['adOwnerName'] ?? "") : ""; + adOwnerEmail = json['vehicle'] != null ? (json['vehicle']['adOwnerEmail'] ?? "") : ""; + adOwnerDetails = (json['vehicle'] != null && json['vehicle']['aDsUser'] != null) ? (AdOwnerDetails.fromJson(json['vehicle']['aDsUser'])) : null; adPostStatus = (json['statusID'] as int).toAdPostEnum(); adReserveStatus = AdReserveStatus.defaultStatus; createdByRoleEnum = (json['createdByRole'] as int).toCreatedByRoleEnum(); @@ -155,7 +161,7 @@ class Vehicle { bool? isActive; bool? isFinanceAvailable; int? status; - String? statustext; + String? statusText; Category? category; Category? color; Condition? condition; @@ -174,31 +180,32 @@ class Vehicle { int? countryID; String? currency; - Vehicle({this.id, - this.cityID, - this.cityName, - this.demandAmount, - this.isActive, - this.isFinanceAvailable, - this.status, - this.statustext, - this.category, - this.color, - this.condition, - this.mileage, - this.model, - this.modelyear, - this.sellertype, - this.transmission, - this.duration, - this.image, - this.damagereport, - this.vehicleDescription, - this.vehicleTitle, - this.vehicleType, - this.vehicleVIN, - this.countryID, - this.currency}); + Vehicle( + {this.id, + this.cityID, + this.cityName, + this.demandAmount, + this.isActive, + this.isFinanceAvailable, + this.status, + this.statusText, + this.category, + this.color, + this.condition, + this.mileage, + this.model, + this.modelyear, + this.sellertype, + this.transmission, + this.duration, + this.image, + this.damagereport, + this.vehicleDescription, + this.vehicleTitle, + this.vehicleType, + this.vehicleVIN, + this.countryID, + this.currency}); Vehicle.fromJson(Map json) { id = json['id']; @@ -208,7 +215,7 @@ class Vehicle { isActive = json['isActive']; isFinanceAvailable = json['isFinanceAvailable']; status = json['status']; - statustext = json['statustext']; + statusText = json['statustext']; category = json['category'] != null ? Category.fromJson(json['category']) : null; color = json['color'] != null ? Category.fromJson(json['color']) : null; condition = json['condition'] != null ? Condition.fromJson(json['condition']) : null; @@ -404,6 +411,7 @@ class DamageReport { isActive = json['isActive']; vehicleDamagePartID = json['vehicleDamagePartID']; partName = json['partName']; + comment = json['comment']; } Map toJson() { @@ -417,3 +425,28 @@ class DamageReport { return data; } } + +class AdOwnerDetails { + String? name; + String? email; + String? mobileNo; + String? profilePic; + + AdOwnerDetails({this.name, this.email, this.mobileNo, this.profilePic}); + + AdOwnerDetails.fromJson(Map json) { + name = json['name']; + email = json['email']; + mobileNo = json['mobileNo']; + profilePic = json['profilePic']; + } + + Map toJson() { + final Map data = {}; + data['name'] = name; + data['email'] = email; + data['mobileNo'] = mobileNo; + data['profilePic'] = profilePic; + return data; + } +} diff --git a/lib/models/advertisment_models/ads_duration_model.dart b/lib/models/advertisment_models/ads_duration_model.dart index aaf3b5c..4602dcf 100644 --- a/lib/models/advertisment_models/ads_duration_model.dart +++ b/lib/models/advertisment_models/ads_duration_model.dart @@ -6,9 +6,20 @@ class AdsDurationModel { int? countryID; bool? isActive; String? countryName; + String? currency; bool? isSelected; - AdsDurationModel({this.id, this.name, this.days, this.price, this.countryID, this.isActive, this.countryName, this.isSelected}); + AdsDurationModel({ + this.id, + this.name, + this.days, + this.price, + this.countryID, + this.isActive, + this.countryName, + this.currency, + this.isSelected, + }); AdsDurationModel.fromJson(Map json) { id = json['id']; @@ -18,6 +29,7 @@ class AdsDurationModel { countryID = json['countryID']; isActive = json['isActive']; countryName = json['countryName']; + currency = json['currency']; isSelected = false; } @@ -30,6 +42,7 @@ class AdsDurationModel { data['countryID'] = countryID; data['isActive'] = isActive; data['countryName'] = countryName; + data['currency'] = currency; return data; } } diff --git a/lib/models/advertisment_models/special_service_model.dart b/lib/models/advertisment_models/special_service_model.dart index 73204b5..ab9a882 100644 --- a/lib/models/advertisment_models/special_service_model.dart +++ b/lib/models/advertisment_models/special_service_model.dart @@ -81,7 +81,7 @@ class Office { String? officeAreaName; String? officeAreaNameN; int? cityID; - String? city; + CitySSModel? city; int? specialServiceID; Office({this.id, this.officeAreaName, this.officeAreaNameN, this.cityID, this.city, this.specialServiceID}); @@ -91,11 +91,41 @@ class Office { officeAreaName = json['officeAreaName']; officeAreaNameN = json['officeAreaNameN']; cityID = json['cityID']; - city = json['city']; + if (json['city'] != null) { + city = json['city'] != null ? CitySSModel.fromJson(json['city']) : null; + } specialServiceID = json['specialServiceID']; } } +class CitySSModel { + int? id; + String? cityName; + String? cityNameN; + int? countryID; + String? country; + bool? isActive; + int? createdBy; + String? createdOn; + String? modifiedBy; + String? modifiedOn; + + CitySSModel({this.id, this.cityName, this.cityNameN, this.countryID, this.country, this.isActive, this.createdBy, this.createdOn, this.modifiedBy, this.modifiedOn}); + + CitySSModel.fromJson(Map json) { + id = json['id']; + cityName = json['cityName']; + cityNameN = json['cityNameN']; + countryID = json['countryID']; + country = json['country']; + isActive = json['isActive']; + createdBy = json['createdBy']; + createdOn = json['createdOn']; + modifiedBy = json['modifiedBy']; + modifiedOn = json['modifiedOn']; + } +} + class SpecialServiceModelForAds { int? adsID; int? specialServiceID; @@ -126,7 +156,6 @@ class SpecialServiceModelForAds { appointmentStatusEnum = (json['appointmentStatus'] as int).toAppointmentStatusEnum(); appointmentDate = json['appointmentDate']; appointmentStatusId = json['appointmentStatus']; - } @override diff --git a/lib/models/appointments_models/appointment_list_model.dart b/lib/models/appointments_models/appointment_list_model.dart index bb8353f..b31255b 100644 --- a/lib/models/appointments_models/appointment_list_model.dart +++ b/lib/models/appointments_models/appointment_list_model.dart @@ -21,6 +21,10 @@ class AppointmentListModel { String? providerName; String? duration; String? appointmentDate; + String? paymentType; + String? appointmentAddress; + String? appointmentLatitude; + String? appointmentLongitude; String? branchName; int? branchId; int? servicePaymentStatus; @@ -34,7 +38,8 @@ class AppointmentListModel { List? customerAppointmentList; List? mergeAppointmentList; - AppointmentListModel({this.id, + AppointmentListModel({ + this.id, this.serviceSlotID, this.appointmentStatusID, this.appointmentStatusText, @@ -49,7 +54,10 @@ class AppointmentListModel { this.appointmentType, this.appointmentTypeEnum, this.providerName, - this.duration, + this.paymentType, + this.appointmentAddress, + this.appointmentLatitude, + this.appointmentLongitude, this.branchName, this.branchId, this.servicePaymentStatus, @@ -58,7 +66,9 @@ class AppointmentListModel { this.isSelected, this.appointmentDate, this.appointmentServicesList, - this.customerAppointmentList, this.mergeAppointmentList,}); + this.customerAppointmentList, + this.mergeAppointmentList, + }); @override String toString() { @@ -83,28 +93,25 @@ class AppointmentListModel { providerName = json['providerName']; duration = json['duration']; appointmentDate = json['appointmentDate']; + paymentType = json['paymentType']; + appointmentAddress = json['address'] ?? ""; + appointmentLatitude = json['branchLatitude']; + appointmentLongitude = json['branchLongitude']; branchName = json['branchName']; branchId = json['branchID']; servicePaymentStatus = json['servicePaymentStatus']; totalAmount = json['amountTotal']; remainingAmount = json['amountRem']; isSelected = false; - appointmentStatusEnum = - (json['appointmentStatusID'] as int).toAppointmentStatusEnum(); - appointmentPaymentStatusEnum = - (json['servicePaymentStatus'] as int).toAppointmentPaymentStatusEnum(); - + appointmentStatusEnum = (json['appointmentStatusID'] as int).toAppointmentStatusEnum(); + appointmentPaymentStatusEnum = (json['servicePaymentStatus'] as int).toAppointmentPaymentStatusEnum(); if (json['serviceList'] != null) { appointmentServicesList = []; json['serviceList'].forEach((v) { - appointmentServicesList! - .add(ServiceModel.fromJson(v, isForAppointment: true)); + appointmentServicesList!.add(ServiceModel.fromJson(v, isForAppointment: true)); }); } - mergeAppointmentList = - json["mergeAppointmentList"] == null ? [] : List.from( - json["mergeAppointmentList"]!.map((x) => - MergeAppointmentList.fromJson(x))); + mergeAppointmentList = json["mergeAppointmentList"] == null ? [] : List.from(json["mergeAppointmentList"]!.map((x) => MergeAppointmentList.fromJson(x))); } } @@ -114,10 +121,7 @@ class ServiceAppointmentItems { String? serviceItemName; String? serviceItemDescription; - ServiceAppointmentItems({this.id, - this.serviceItemID, - this.serviceItemName, - this.serviceItemDescription}); + ServiceAppointmentItems({this.id, this.serviceItemID, this.serviceItemName, this.serviceItemDescription}); ServiceAppointmentItems.fromJson(Map json) { id = json['id']; @@ -142,12 +146,9 @@ class CustomerData { final String customerName; final List appointmentList; - CustomerData({required this.customerID, - required this.customerName, - required this.appointmentList}); + CustomerData({required this.customerID, required this.customerName, required this.appointmentList}); } - class MergeAppointmentList { int? id; int? mergeAppointmentId; @@ -159,17 +160,15 @@ class MergeAppointmentList { this.serviceAppointmentId, }); - factory MergeAppointmentList.fromJson(Map json) => - MergeAppointmentList( + factory MergeAppointmentList.fromJson(Map json) => MergeAppointmentList( id: json["id"], mergeAppointmentId: json["mergeAppointmentID"], serviceAppointmentId: json["serviceAppointmentID"], ); - Map toJson() => - { + Map toJson() => { "id": id, "mergeAppointmentID": mergeAppointmentId, "serviceAppointmentID": serviceAppointmentId, }; -} \ No newline at end of file +} diff --git a/lib/models/appointments_models/service_schedule_model.dart b/lib/models/appointments_models/service_schedule_model.dart index a515097..87967b2 100644 --- a/lib/models/appointments_models/service_schedule_model.dart +++ b/lib/models/appointments_models/service_schedule_model.dart @@ -11,6 +11,7 @@ class CustomTimeDateSlotModel { } class ServiceAppointmentScheduleModel { + String? scheduleName; List? serviceSlotList; List? servicesListInAppointment; int? selectedDateIndex; @@ -20,9 +21,12 @@ class ServiceAppointmentScheduleModel { double? amountTotal; double? amountRem; int? appointmentType; + double? totalLocationCharges; + CurrentLocationInfoModel? locationInfoModel; AppointmentTypeEnum? appointmentTypeEnum; ServiceAppointmentScheduleModel({ + this.scheduleName, this.serviceSlotList, this.servicesListInAppointment, this.selectedDateIndex, @@ -32,6 +36,8 @@ class ServiceAppointmentScheduleModel { this.amountTotal, this.amountRem, this.appointmentType, + this.totalLocationCharges, + this.locationInfoModel, this.appointmentTypeEnum, }); @@ -72,7 +78,7 @@ class ServiceAppointmentScheduleModel { .toList(); List slotTime = []; for (var element in slotTimeData) { - slotTime.add(TimeSlotModel(isSelected: false, slotId: element.id!, slot: element.startTime ?? "", date: element.slotDate!.toFormattedDateWithoutTime() ?? "")); + slotTime.add(TimeSlotModel(isSelected: false, slotId: element.id!, slot: element.startTime ?? "", date: element.slotDate!.toFormattedDateWithoutTime())); } return slotTime; @@ -90,6 +96,8 @@ class ServiceAppointmentScheduleModel { } ServiceAppointmentScheduleModel.fromJson(Map json, {bool isForAppointment = false}) { + scheduleName = json['scheduleName']; + if (json['serviceSlotList'] != null) { serviceSlotList = []; json['serviceSlotList'].forEach((v) { @@ -108,6 +116,8 @@ class ServiceAppointmentScheduleModel { amountTotal = json['amountTotal']; amountRem = json['amountRem']; appointmentType = json['appointmentType']; + totalLocationCharges = 0.0; + locationInfoModel = CurrentLocationInfoModel(address: "", latitude: 0.0, longitude: 0.0, distanceToBranch: 0.0, homeChargesInCurrentService: 0.0); appointmentTypeEnum = json['appointmentType'] != null ? (json['appointmentType'] as int).toAppointmentTypeEnum() : AppointmentTypeEnum.workshop; } } diff --git a/lib/models/general_models/widgets_models.dart b/lib/models/general_models/widgets_models.dart index 9972299..d4271c9 100644 --- a/lib/models/general_models/widgets_models.dart +++ b/lib/models/general_models/widgets_models.dart @@ -11,17 +11,19 @@ class SelectionModel { int selectedId; String errorValue; String itemPrice; + String currency; SelectionModel({ this.selectedOption = "", this.errorValue = "", this.selectedId = 0, this.itemPrice = "", + this.currency = "", }); @override String toString() { - return 'SelectionModel{selectedOption: $selectedOption, selectedId: $selectedId, errorValue: $errorValue, itemPrice: $itemPrice}'; + return 'SelectionModel{selectedOption: $selectedOption, selectedId: $selectedId, errorValue: $errorValue, itemPrice: $itemPrice, currency: $currency}'; } } diff --git a/lib/models/provider_branches_models/profile/categroy.dart b/lib/models/provider_branches_models/profile/categroy.dart index cca86fd..1d0a0c1 100644 --- a/lib/models/provider_branches_models/profile/categroy.dart +++ b/lib/models/provider_branches_models/profile/categroy.dart @@ -26,17 +26,17 @@ class Category { String? message; factory Category.fromJson(Map json) => Category( - totalItemsCount: json["totalItemsCount"] == null ? null : json["totalItemsCount"], + totalItemsCount: json["totalItemsCount"], data: json["data"] == null ? null : List.from(json["data"].map((x) => CategoryData.fromJson(x))), - messageStatus: json["messageStatus"] == null ? null : json["messageStatus"], - message: json["message"] == null ? null : json["message"], + messageStatus: json["messageStatus"], + message: json["message"], ); Map toJson() => { - "totalItemsCount": totalItemsCount == null ? null : totalItemsCount, + "totalItemsCount": totalItemsCount, "data": data == null ? null : List.from(data!.map((x) => x.toJson())), - "messageStatus": messageStatus == null ? null : messageStatus, - "message": message == null ? null : message, + "messageStatus": messageStatus, + "message": message, }; } @@ -63,18 +63,18 @@ class CategoryData extends Equatable { List? services; factory CategoryData.fromJson(Map json) => CategoryData( - id: json["id"] == null ? null : json["id"], - categoryName: json["categoryName"] == null ? null : json["categoryName"], - categoryNameN: json["categoryNameN"] == null ? null : json["categoryNameN"], + id: json["id"], + categoryName: json["categoryName"], + categoryNameN: json["categoryNameN"], serviceCategoryIconUrl: json["serviceCategoryIconUrl"], serviceCategoryImageUrl: json["serviceCategoryImageUrl"], services: [], ); Map toJson() => { - "id": id == null ? null : id, - "categoryName": categoryName == null ? null : categoryName, - "categoryNameN": categoryNameN == null ? null : categoryNameN, + "id": id, + "categoryName": categoryName, + "categoryNameN": categoryNameN, "serviceCategoryIconUrl": serviceCategoryIconUrl, "serviceCategoryImageUrl": serviceCategoryImageUrl, }; diff --git a/lib/models/requests_models/provider_offers_model.dart b/lib/models/requests_models/provider_offers_model.dart index c829a8c..a2f6fe2 100644 --- a/lib/models/requests_models/provider_offers_model.dart +++ b/lib/models/requests_models/provider_offers_model.dart @@ -34,7 +34,7 @@ class ProviderOffersModel { this.serviceProviders, }); - ProviderOffersModel.fromJson(Map json) { + ProviderOffersModel.fromJson(Map json, int? reqId) { id = json['id']; customerID = json['customerID']; requestType = json['requestType']; @@ -49,7 +49,7 @@ class ProviderOffersModel { if (json['serviceProviders'] != null) { serviceProviders = []; json['serviceProviders'].forEach((v) { - serviceProviders!.add(ServiceProvidersOffers.fromJson(v)); + serviceProviders!.add(ServiceProvidersOffers.fromJson(v, reqId)); }); } } @@ -65,6 +65,7 @@ class ServiceProvidersOffers { String? createdOn; RequestOfferStatusEnum? requestOfferStatusEnum; int? offerCount; + int? requestId; List? chatMessages; ServiceProvidersOffers({ @@ -74,13 +75,14 @@ class ServiceProvidersOffers { this.email, this.companyName, this.offerCount, + this.requestId, this.chatMessages, this.providerUserId, this.createdOn, this.requestOfferStatusEnum, }); - ServiceProvidersOffers.fromJson(Map json) { + ServiceProvidersOffers.fromJson(Map json, int? reqId) { providerId = json['providerID']; providerUserId = json['providerUserID']; name = json['name']; @@ -88,6 +90,7 @@ class ServiceProvidersOffers { email = json['email']; companyName = json['companyName']; offerCount = json['offerCount']; + requestId = reqId; createdOn = json['createdOn']; requestOfferStatusEnum = ((json['offerStatusLast']) as int).toRequestOfferStatusEnum(); chatMessages = []; diff --git a/lib/models/services_models/service_model.dart b/lib/models/services_models/service_model.dart index d158bee..fcc54f7 100644 --- a/lib/models/services_models/service_model.dart +++ b/lib/models/services_models/service_model.dart @@ -1,4 +1,6 @@ +import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/models/services_models/item_model.dart'; +import 'package:mc_common_app/utils/enums.dart'; class ServiceModel { final int? serviceProviderServiceId; @@ -9,19 +11,21 @@ class ServiceModel { final String? serviceDescription; final String? serviceDescriptionN; final int? serviceStatus; + final ServiceStatusEnum? serviceStatusEnum; final dynamic statusText; final bool? isAllowAppointment; final bool? isAllowAppointmentHome; final int? customerLocationRange; - final String? rangePricePerKm; + String? rangePricePerKm; final int? itemsCount; List? serviceItems; bool isExpandedOrSelected; int providerServiceId; String providerServiceName; bool isHomeSelected; - String homeLocation; double currentTotalServicePrice; + CurrentLocationInfoModel servicelocationInfo; + bool isActive; ServiceModel({ this.serviceProviderServiceId, @@ -32,6 +36,7 @@ class ServiceModel { this.serviceDescription, this.serviceDescriptionN, this.serviceStatus, + this.serviceStatusEnum, this.statusText, this.isAllowAppointment, this.isAllowAppointmentHome, @@ -40,11 +45,12 @@ class ServiceModel { this.itemsCount, this.serviceItems, this.isHomeSelected = false, - this.homeLocation = "", this.currentTotalServicePrice = 0.0, + this.isActive = true, required this.isExpandedOrSelected, required this.providerServiceId, required this.providerServiceName, + required this.servicelocationInfo, }); factory ServiceModel.fromJson(Map json, {bool isForAppointment = false}) => ServiceModel( @@ -56,11 +62,12 @@ class ServiceModel { serviceDescription: json["serviceDescription"] ?? json["serviceName"], serviceDescriptionN: json["serviceDescriptionN"] ?? json["serviceNameN"], serviceStatus: json["serviceStatus"], + serviceStatusEnum: ((json["serviceStatus"] ?? 0) as int).toServiceStatusEnum(), statusText: json["statusText"], isAllowAppointment: json["isAllowAppointment"], isAllowAppointmentHome: json["isAllowAppointmentHome"], customerLocationRange: json["customerLocationRange"], - rangePricePerKm: json["rangePricePerKm"].toString(), + rangePricePerKm: json["rangePricePerKm"]?.toString(), itemsCount: json["itemsCount"], serviceItems: isForAppointment ? json["serviceItemList"] == null @@ -73,7 +80,8 @@ class ServiceModel { providerServiceId: 0, providerServiceName: "", isHomeSelected: false, - homeLocation: "", + isActive: json["isActive"] ?? true, + servicelocationInfo: CurrentLocationInfoModel(address: '', distanceToBranch: 0.0, homeChargesInCurrentService: 0.0, latitude: 0.0, longitude: 0.0), ); Map toJson() => { @@ -99,3 +107,24 @@ class ServiceModel { return 'ServiceModel{serviceProviderServiceId: $serviceProviderServiceId, providerServiceDescription: $providerServiceDescription, categoryId: $categoryId, categoryName: $categoryName, serviceId: $serviceId, serviceDescription: $serviceDescription, serviceDescriptionN: $serviceDescriptionN, serviceStatus: $serviceStatus, statusText: $statusText, isAllowAppointment: $isAllowAppointment, isAllowAppointmentHome: $isAllowAppointmentHome, customerLocationRange: $customerLocationRange, rangePricePerKm: $rangePricePerKm, itemsCount: $itemsCount, serviceItems: $serviceItems, isExpandedOrSelected: $isExpandedOrSelected}'; } } + +class CurrentLocationInfoModel { + String address; + double latitude; + double longitude; + double distanceToBranch; + double homeChargesInCurrentService; + + CurrentLocationInfoModel({ + required this.address, + required this.latitude, + required this.longitude, + required this.distanceToBranch, + required this.homeChargesInCurrentService, + }); + + @override + String toString() { + return 'CurrentLocationInfoModel{address: $address, latitude: $latitude, longitude: $longitude, distanceToBranch: $distanceToBranch, homeChargesInCurrentService: $homeChargesInCurrentService}'; + } +} diff --git a/lib/models/shipping_models/shipping_status_model.dart b/lib/models/shipping_models/shipping_status_model.dart index c68f73c..d8979ef 100644 --- a/lib/models/shipping_models/shipping_status_model.dart +++ b/lib/models/shipping_models/shipping_status_model.dart @@ -1,27 +1,80 @@ import 'package:mc_common_app/extensions/string_extensions.dart'; -import 'package:mc_common_app/models/requests_models/request_model.dart'; import 'package:mc_common_app/utils/enums.dart'; class ShippingRequestModel { int? id; int? requestID; - RequestModel? request; + Request? request; int? shippingStatus; ShippingRequestStatusEnum? shippingStatusEnum; String? deliveredOn; String? comment; String? createdOn; + int? customerID; + String? customerName; - ShippingRequestModel({this.id, this.requestID, this.request, this.shippingStatus, this.deliveredOn, this.comment, this.createdOn}); + ShippingRequestModel({this.id, this.requestID, this.request, this.shippingStatus, this.deliveredOn, this.comment, this.createdOn, this.customerID, this.customerName}); ShippingRequestModel.fromJson(Map json) { id = json['id']; requestID = json['requestID']; - request = json['request']; + request = json['request'] != null ? Request.fromJson(json['request']) : null; shippingStatus = json['shippingStatus']; shippingStatusEnum = json['shippingStatus'] != null ? (json['shippingStatus'] as int).toShippingStatusEnum() : ShippingRequestStatusEnum.initiated; deliveredOn = json['deliveredOn']; comment = json['comment']; createdOn = json['createdOn']; + customerID = json['customerID']; + customerName = json['customerName']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['requestID'] = requestID; + if (request != null) { + data['request'] = request!.toJson(); + } + data['shippingStatus'] = shippingStatus; + data['deliveredOn'] = deliveredOn; + data['comment'] = comment; + data['createdOn'] = createdOn; + data['customerID'] = customerID; + data['customerName'] = customerName; + return data; + } +} + +class Request { + int? requestType; + String? brand; + String? model; + int? year; + bool? isNew; + String? description; + double? price; + + Request({this.requestType, this.brand, this.model, this.year, this.isNew, this.description, this.price}); + + Request.fromJson(Map json) { + requestType = json['requestType']; + brand = json['brand']; + model = json['model']; + year = json['year']; + isNew = json['isNew']; + description = json['description']; + price = json['price']; + } + + Map toJson() { + final Map data = {}; + data['requestType'] = requestType; + data['brand'] = brand; + data['model'] = model; + data['year'] = year; + data['isNew'] = isNew; + data['description'] = description; + data['price'] = price; + return data; } } diff --git a/lib/models/user_models/user.dart b/lib/models/user_models/user.dart index 1efe6db..c8c055b 100644 --- a/lib/models/user_models/user.dart +++ b/lib/models/user_models/user.dart @@ -34,7 +34,7 @@ class User { Map toJson() => { "totalItemsCount": totalItemsCount, - "data": data == null ? null : data!.toJson(), + "data": data?.toJson(), "messageStatus": messageStatus, "message": message, }; @@ -63,8 +63,8 @@ class UserData { Map toJson() => { "accessToken": accessToken, "refreshToken": refreshToken, - "expiryDate": expiryDate == null ? null : expiryDate!.toIso8601String(), - "userInfo": userInfo == null ? null : userInfo!.toJson(), + "expiryDate": expiryDate?.toIso8601String(), + "userInfo": userInfo?.toJson(), }; } @@ -88,6 +88,8 @@ class UserInfo { this.isDealershipUser, this.providerId, this.customerId, + this.countryId, + this.cityId, this.dealershipId, this.userLocalImage}); @@ -109,6 +111,8 @@ class UserInfo { bool? isDealershipUser; dynamic providerId; int? customerId; + int? cityId; + int? countryId; dynamic dealershipId; File? userLocalImage; @@ -140,6 +144,8 @@ class UserInfo { isDealershipUser = json["isDealershipUser"]; providerId = json["providerID"]; customerId = json["customerID"]; + countryId = json["countryId"] ?? 1; + cityId = json["cityId"] ?? 1; dealershipId = json["dealershipID"]; userLocalImage = null; } diff --git a/lib/repositories/ads_repo.dart b/lib/repositories/ads_repo.dart index 5a3fa6f..a76b314 100644 --- a/lib/repositories/ads_repo.dart +++ b/lib/repositories/ads_repo.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + 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'; @@ -14,7 +16,7 @@ import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/utils/enums.dart'; abstract class AdsRepo { - Future> getAdsDuration(); + Future> getAdsDuration({required int? countryId}); Future> getSpecialServices({required int specialServiceType}); @@ -22,7 +24,14 @@ abstract class AdsRepo { Future> getAllAds({required bool isMyAds, AdPostStatus? adPostStatus, CreatedByRoleEnum? createdByRoleEnum}); - Future> getExploreAdsBasedOnFilters({List? cityIdsList, List? vehicleModelYearIdsList, List? vehicleBrandIdsList, List? createdByRolesIdsList}); + Future> getExploreAdsBasedOnFilters({ + List? cityIdsList, + List? vehicleModelYearIdsList, + List? vehicleBrandIdsList, + List? createdByRolesIdsList, + List? vehicleAdConditionIdsList, + List? vehicleAdCreatedDateList, + }); Future> getMyReservedAds(); @@ -52,10 +61,14 @@ class AdsRepoImp implements AdsRepo { AppState appState = injector.get(); @override - Future> getAdsDuration() async { + Future> getAdsDuration({required int? countryId}) async { + var param = { + "CountryID": countryId.toString() ?? "0", + }; GenericRespModel adsGenericModel = await apiClient.getJsonForObject( token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), + queryParameters: param, ApiConsts.vehicleAdsDurationGet, ); List vehicleAdsDuration = List.generate(adsGenericModel.data.length, (index) => AdsDurationModel.fromJson(adsGenericModel.data[index])); @@ -67,7 +80,8 @@ class AdsRepoImp implements AdsRepo { var params = { "SpecialServiceType": specialServiceType.toString(), }; - GenericRespModel adsGenericModel = await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleAdsSpecialServicesGet, queryParameters: params); + GenericRespModel adsGenericModel = + await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleAdsSpecialServicesGet, queryParameters: params); List vehicleAdsDuration = List.generate(adsGenericModel.data.length, (index) => SpecialServiceModel.fromJson(adsGenericModel.data[index])); return vehicleAdsDuration; } @@ -75,6 +89,8 @@ class AdsRepoImp implements AdsRepo { @override Future createOrUpdateAd({required AdsCreationPayloadModel adsCreationPayloadModel, required bool isCreateNew}) async { List vehiclePostingImages = []; + + log("adsCreationPayloadModel.ads!.adsDurationID,: ${adsCreationPayloadModel.ads!.adsDurationID}"); adsCreationPayloadModel.vehiclePosting!.vehiclePostingImages?.forEach((element) { var imageMap = { "id": element.id ?? 0, @@ -89,7 +105,15 @@ class AdsRepoImp implements AdsRepo { List vehiclePostingDamageParts = []; adsCreationPayloadModel.vehiclePosting!.vehiclePostingDamageParts?.forEach((element) { - var imageMap = {"id": element.id ?? 0, "comment": element.comment, "vehicleImageBase64": element.vehicleImageBase64, "vehicleDamagePartID": element.vehicleDamagePartID, "vehiclePostingID": element.vehiclePostingID ?? 0, "isActive": true}; + log("comment: ${element.comment}"); + var imageMap = { + "id": element.id ?? 0, + "comment": element.comment, + "vehicleImageBase64": element.vehicleImageBase64, + "vehicleDamagePartID": element.vehicleDamagePartID, + "vehiclePostingID": element.vehiclePostingID ?? 0, + "isActive": true + }; vehiclePostingDamageParts.add(imageMap); }); var postParams = { @@ -148,13 +172,15 @@ class AdsRepoImp implements AdsRepo { }; if (isMyAds && adPostStatus != null) { onlyMyAdsParams.addAll({ - "AdsStatuses": ["${adPostStatus.getIdFromAdPostStatusEnum()}"] + "AdsStatuses": ["${adPostStatus.getIdFromAdPostStatusEnum()}"], + "PageSize": "30", }); } var allAdsParams = { "AdsStatuses": ["${AdPostStatus.active.getIdFromAdPostStatusEnum()}"], //only Active ADS "isActive": "true", //only Active ADS - "isExplore": "true" + "isExplore": "true", + "PageSize": "30", }; if (!isMyAds && createdByRoleEnum != null) { @@ -178,6 +204,8 @@ class AdsRepoImp implements AdsRepo { List? vehicleModelYearIdsList, List? vehicleBrandIdsList, List? createdByRolesIdsList, + List? vehicleAdConditionIdsList, + List? vehicleAdCreatedDateList, }) async { var parameters = { "CityIDs": cityIdsList ?? [], @@ -185,8 +213,13 @@ 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() : "", + "CreatedOn": (vehicleAdCreatedDateList != null && vehicleAdCreatedDateList.isNotEmpty) ? vehicleAdCreatedDateList.first.toString() : "", "isActive": "true", //only Active ADS - "isExplore": "true" + "isExplore": "true", + "PageSize": "30", }; GenericRespModel adsGenericModel = await apiClient.getJsonForObject( @@ -222,7 +255,11 @@ class AdsRepoImp implements AdsRepo { } Future> getAdsPerSpecificIds({required List ids, required List reservedAds}) async { - var params = {"AdsIDs": ids, "isActive": "true"}; + var params = { + "AdsIDs": ids, + "isActive": "true", + "PageSize": "30", + }; GenericRespModel adsGenericModel = await apiClient.getJsonForObject( token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), @@ -243,6 +280,7 @@ class AdsRepoImp implements AdsRepo { Future> getMyAds() async { var params = { "userID": appState.getUser.data!.userInfo!.userId ?? "", + "PageSize": "30", }; GenericRespModel adsGenericModel = await apiClient.getJsonForObject( token: appState.getUser.data!.accessToken, diff --git a/lib/repositories/appointment_repo.dart b/lib/repositories/appointment_repo.dart index c2755d4..f13fb45 100644 --- a/lib/repositories/appointment_repo.dart +++ b/lib/repositories/appointment_repo.dart @@ -132,7 +132,8 @@ class AppointmentRepoImp implements AppointmentRepo { if (adsGenericModel.data == null) { return []; } - List serviceAppointmentScheduleModel = List.generate(adsGenericModel.data.length, (index) => ServiceAppointmentScheduleModel.fromJson(adsGenericModel.data[index], isForAppointment: true)); + List serviceAppointmentScheduleModel = + List.generate(adsGenericModel.data.length, (index) => ServiceAppointmentScheduleModel.fromJson(adsGenericModel.data[index], isForAppointment: true)); return serviceAppointmentScheduleModel; } @@ -149,11 +150,26 @@ class AppointmentRepoImp implements AppointmentRepo { serviceItemIds.add(item.id!); } } + + int slotId = 0; + + int index = schedule.selectedCustomTimeDateSlotModel!.availableSlots!.indexWhere((element) => element.isSelected); + + if (index == -1) { + index = 0; + } + + slotId = schedule.selectedCustomTimeDateSlotModel!.availableSlots![index].slotId; + mapList.add({ - "serviceSlotID": schedule.selectedCustomTimeDateSlotModel!.date!.slotId, + "serviceSlotID": slotId, "serviceProviderID": serviceProviderID, "customerID": customerId, "serviceItemID": serviceItemIds, + "amountCustomerLocation": schedule.totalLocationCharges.toString(), + "customerLocLat": (schedule.locationInfoModel!.latitude).toString(), + "customerLocLong": (schedule.locationInfoModel!.longitude).toString(), + "customerLocAddress": (schedule.locationInfoModel!.address).toString(), }); } log("maplist: ${mapList.toString()}"); diff --git a/lib/repositories/branch_repo.dart b/lib/repositories/branch_repo.dart index 1809a7a..2cb2f77 100644 --- a/lib/repositories/branch_repo.dart +++ b/lib/repositories/branch_repo.dart @@ -191,7 +191,9 @@ class BranchRepoImp implements BranchRepo { }; String? token = AppState().getUser.data?.accessToken; debugPrint(token); - return await injector.get().getJsonForObject((json) => Document.fromJson(json), ApiConsts.fetProviderDocument, queryParameters: queryParameters, token: AppState().getUser.data!.accessToken ?? ""); + return await injector + .get() + .getJsonForObject((json) => Document.fromJson(json), ApiConsts.fetProviderDocument, queryParameters: queryParameters, token: AppState().getUser.data!.accessToken ?? ""); } @override @@ -438,10 +440,11 @@ class BranchRepoImp implements BranchRepo { }; if (latitude != null && longitude != null) { - postParams.addAll({"Longitude": "$latitude"}); - postParams.addAll({"Latitude": "$longitude"}); + postParams.addAll({"Longitude": "$longitude"}); + postParams.addAll({"Latitude": "$latitude"}); } - GenericRespModel adsGenericModel = await apiClient.getJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.branchesAndServices, token: appState.getUser.data!.accessToken, queryParameters: postParams); + GenericRespModel adsGenericModel = + await apiClient.getJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.branchesAndServices, token: appState.getUser.data!.accessToken, queryParameters: postParams); return ProviderProfileModel.fromJson(adsGenericModel.data); } diff --git a/lib/repositories/chat_repo.dart b/lib/repositories/chat_repo.dart index f099937..7572124 100644 --- a/lib/repositories/chat_repo.dart +++ b/lib/repositories/chat_repo.dart @@ -51,7 +51,7 @@ class ChatRepoImp implements ChatRepo { @override Future getHubConnection() async { final userId = AppState().getUser.data!.userInfo!.userId ?? ""; - final hubUrl = "https://ms.hmg.com/McHub?userID=$userId"; + final hubUrl = "${ApiConsts.signalRUrl}?userID=$userId"; logger.i("Connecting with Hub ($hubUrl)"); HubConnection hub; diff --git a/lib/repositories/common_repo.dart b/lib/repositories/common_repo.dart index df6275b..24d07f2 100644 --- a/lib/repositories/common_repo.dart +++ b/lib/repositories/common_repo.dart @@ -54,6 +54,8 @@ abstract class CommonRepo { Future getVehicleDetails({int? vehicleTypeId, int? vehicleBrandId}); Future> getEnumTypeValues({int? enumTypeID, String? enumTypeName}); + + Future createComplainFromProvider({required int customerId, required String comment, required int complainType}); } class CommonRepoImp implements CommonRepo { @@ -62,7 +64,7 @@ class CommonRepoImp implements CommonRepo { @override Future getAllCountries() async { - return await apiClient.getJsonForObject((json) => Country.fromJson(json), ApiConsts.GetAllCountry); + return await apiClient.getJsonForObject((json) => Country.fromJson(json), ApiConsts.getAllCountry); } @override @@ -70,12 +72,12 @@ class CommonRepoImp implements CommonRepo { var postParams = { "CountryID": countryId, }; - return await apiClient.getJsonForObject((json) => Cities.fromJson(json), ApiConsts.GetAllCities, queryParameters: postParams); + return await apiClient.getJsonForObject((json) => Cities.fromJson(json), ApiConsts.getAllCities, queryParameters: postParams); } @override Future getRoles() async { - return await apiClient.getJsonForObject((json) => Role.fromJson(json), ApiConsts.GetProviderRoles); + return await apiClient.getJsonForObject((json) => Role.fromJson(json), ApiConsts.getProviderRoles); } @override @@ -166,7 +168,8 @@ class CommonRepoImp implements CommonRepo { var postParams = { "VehicleType": vehicleTypeId.toString(), }; - GenericRespModel adsGenericModel = await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleCategoryGet, queryParameters: postParams); + GenericRespModel adsGenericModel = + await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleCategoryGet, queryParameters: postParams); List vehicleCategories = List.generate(adsGenericModel.data.length, (index) => VehicleCategoryModel.fromJson(adsGenericModel.data[index])); return vehicleCategories; } @@ -176,7 +179,8 @@ class CommonRepoImp implements CommonRepo { var postParams = { "CountryID": countryId.toString(), }; - GenericRespModel adsGenericModel = await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleCityGet, queryParameters: countryId != -1 ? postParams : null); + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( + token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleCityGet, queryParameters: countryId != -1 ? postParams : null); List vehicleCities = List.generate(adsGenericModel.data.length, (index) => VehicleCityModel.fromJson(adsGenericModel.data[index])); return vehicleCities; } @@ -186,7 +190,8 @@ class CommonRepoImp implements CommonRepo { var postParams = { "VehicleType": vehicleTypeId.toString(), }; - GenericRespModel adsGenericModel = await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleColorGet, queryParameters: postParams); + GenericRespModel adsGenericModel = + await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleColorGet, queryParameters: postParams); List vehicleColors = List.generate(adsGenericModel.data.length, (index) => VehicleColorModel.fromJson(adsGenericModel.data[index])); return vehicleColors; } @@ -196,7 +201,8 @@ class CommonRepoImp implements CommonRepo { var postParams = { "VehicleType": vehicleTypeId.toString(), }; - GenericRespModel adsGenericModel = await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleConditionGet, queryParameters: postParams); + GenericRespModel adsGenericModel = + await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleConditionGet, queryParameters: postParams); List vehicleConditions = List.generate(adsGenericModel.data.length, (index) => VehicleConditionModel.fromJson(adsGenericModel.data[index])); return vehicleConditions; } @@ -217,7 +223,8 @@ class CommonRepoImp implements CommonRepo { var postParams = { "VehicleType": vehicleTypeId.toString(), }; - GenericRespModel adsGenericModel = await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleMileageGet, queryParameters: postParams); + GenericRespModel adsGenericModel = + await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.vehicleMileageGet, queryParameters: postParams); List vehicleMileages = List.generate(adsGenericModel.data.length, (index) => VehicleMileageModel.fromJson(adsGenericModel.data[index])); return vehicleMileages; } @@ -338,22 +345,21 @@ class CommonRepoImp implements CommonRepo { return vehicleParts; } -// -// @override -// Future> getProviderServiceCategories() async { -// GenericRespModel adsGenericModel = await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.serviceCategoryGet); -// List providerCategories = List.generate(adsGenericModel.data.length, (index) => ProviderCategoryModel.fromJson(adsGenericModel.data[index])); -// return providerCategories; -// } - -// @override -// Future> getProviderServices({required int categoryId}) async { -// var postParams = { -// "ServiceCategoryID": categoryId.toString(), -// }; -// GenericRespModel adsGenericModel = -// await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.serviceCategoryGet, queryParameters: postParams); -// List providerServices = List.generate(adsGenericModel.data.length, (index) => ProviderServiceModel.fromJson(adsGenericModel.data[index])); -// return providerServices; -// } + @override + Future createComplainFromProvider({required int customerId, required String comment, required int complainType}) async { + final providerId = appState.getUser.data!.userInfo!.providerId; + final parameters = { + "serviceProviderID": providerId, + "customerID": customerId, + "detail": comment, + "complaintType": complainType, + }; + GenericRespModel adsGenericModel = await apiClient.postJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.providerComplaintCreate, + parameters, + token: appState.getUser.data!.accessToken, + ); + return adsGenericModel; + } } diff --git a/lib/repositories/request_repo.dart b/lib/repositories/request_repo.dart index c3547c2..514299d 100644 --- a/lib/repositories/request_repo.dart +++ b/lib/repositories/request_repo.dart @@ -38,6 +38,7 @@ abstract class RequestRepo { Future> getRequestsBasedOnFilters({ required int requestTypeId, int requestStatusId = 0, + int reqOfferStatus = 0, int? cityId = 0, int? vehicleYearId = 0, String? vehicleModel = "", @@ -131,6 +132,7 @@ class RequestRepoImp implements RequestRepo { Future> getRequestsBasedOnFilters({ required int requestTypeId, int requestStatusId = 0, + int reqOfferStatus = 0, int? cityId = 0, int? vehicleYearId = 0, String? vehicleModel = "", @@ -146,6 +148,7 @@ class RequestRepoImp implements RequestRepo { "doPagination": true, "requestType": requestTypeId.toString(), "requestStatus": requestStatusId.toString(), + "reqOfferStatus": reqOfferStatus.toString(), "brand": vehicleBrand.toString(), "requestedDate": requestedDate, "model": vehicleModel.toString(), @@ -291,7 +294,7 @@ class RequestRepoImp implements RequestRepo { ); ProviderOffersModel providerOffersModel = ProviderOffersModel(); if (genericRespModel.data != null && genericRespModel.data.length > 0) { - providerOffersModel = ProviderOffersModel.fromJson(genericRespModel.data.first); + providerOffersModel = ProviderOffersModel.fromJson(genericRespModel.data.first, requestId); } return providerOffersModel; } diff --git a/lib/repositories/schedule_repo.dart b/lib/repositories/schedule_repo.dart index 9bd78e9..233de4d 100644 --- a/lib/repositories/schedule_repo.dart +++ b/lib/repositories/schedule_repo.dart @@ -11,6 +11,8 @@ abstract class ScheduleRepo { Future createSchedule(Map map); + Future checkServiceGroupInBranchSchedule(Map map); + Future addServicesInSchedule(Map map); Future updateSchedule(Map map); @@ -36,6 +38,12 @@ class ScheduleRepoImp implements ScheduleRepo { return await injector.get().postJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.createSchedule, map, token: t); } + @override + Future checkServiceGroupInBranchSchedule(Map map) async { + String t = AppState().getUser.data!.accessToken ?? ""; + return await injector.get().postJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.checkGroupServiceInBranchSchedule, map, token: t); + } + @override Future addServicesInSchedule(Map map) async { String t = AppState().getUser.data!.accessToken ?? ""; diff --git a/lib/repositories/setting_options_repo.dart b/lib/repositories/setting_options_repo.dart index c26aa28..6213a94 100644 --- a/lib/repositories/setting_options_repo.dart +++ b/lib/repositories/setting_options_repo.dart @@ -61,11 +61,21 @@ class SettingOptionsRepoImp extends SettingOptionsRepo { @override Future> getAllContactInfos() async { + int channel = 0; + + if (AppState().currentAppType == AppType.provider) { + channel = 2; + } else { + channel = 3; + } + final params = {"Channel": channel.toString()}; + String token = appState.getUser.data!.accessToken ?? ""; GenericRespModel genericRespModel = await injector.get().getJsonForObject( (json) => GenericRespModel.fromJson(json), ApiConsts.getContactInfo, + queryParameters: params, token: token, ); diff --git a/lib/repositories/user_repo.dart b/lib/repositories/user_repo.dart index 8417636..571549c 100644 --- a/lib/repositories/user_repo.dart +++ b/lib/repositories/user_repo.dart @@ -127,7 +127,7 @@ class UserRepoImp implements UserRepo { "mobileorEmail": phoneNo, "password": password, }; - return await injector.get().postJsonForResponse(ApiConsts.Login_V1, postParams); + return await injector.get().postJsonForResponse(ApiConsts.loginV1, postParams); //return await injector.get().postJsonForObject((json) => BasicOtp.fromJson(json), ApiConsts.Login_V1, postParams); } @@ -138,7 +138,7 @@ class UserRepoImp implements UserRepo { "loginType": loginType, }; - Response response = await injector.get().postJsonForResponse(ApiConsts.Login_V2_OTP, postParams); + Response response = await injector.get().postJsonForResponse(ApiConsts.loginV2OTP, postParams); LoginPasswordRespModel user = LoginPasswordRespModel.fromJson(jsonDecode(response.body)); return user; } @@ -151,7 +151,7 @@ class UserRepoImp implements UserRepo { String deviceToken, ) async { var postParams = {"userToken": userToken, "userOTP": otp, "deviceType": deviceType, "deviceToken": deviceToken}; - return await injector.get().postJsonForResponse(ApiConsts.Login_V2_OTPVerify, postParams); + return await injector.get().postJsonForResponse(ApiConsts.loginV2OTPVerify, postParams); } @override @@ -163,7 +163,7 @@ class UserRepoImp implements UserRepo { @override Future getAllCountries() async { - return await injector.get().getJsonForObject((json) => Country.fromJson(json), ApiConsts.GetAllCountry); + return await injector.get().getJsonForObject((json) => Country.fromJson(json), ApiConsts.getAllCountry); } @override @@ -171,12 +171,12 @@ class UserRepoImp implements UserRepo { var postParams = { "CountryID": countryId, }; - return await injector.get().getJsonForObject((json) => Cities.fromJson(json), ApiConsts.GetAllCities, queryParameters: postParams); + return await injector.get().getJsonForObject((json) => Cities.fromJson(json), ApiConsts.getAllCities, queryParameters: postParams); } @override Future getRoles() async { - return await injector.get().getJsonForObject((json) => Role.fromJson(json), ApiConsts.GetProviderRoles); + return await injector.get().getJsonForObject((json) => Role.fromJson(json), ApiConsts.getProviderRoles); } @override @@ -185,14 +185,14 @@ class UserRepoImp implements UserRepo { "userName": userName, "otpType": 1, }; - return await injector.get().postJsonForResponse(ApiConsts.ForgetPasswordOTPRequest, postParams); + return await injector.get().postJsonForResponse(ApiConsts.forgetPasswordOTPRequest, postParams); //return await injector.get().postJsonForObject((json) => PasswordOTPRequest.fromJson(json), ApiConsts.ForgetPasswordOTPRequest, postParams); } @override Future forgetPasswordOTPCompare(String userToken, String userOTP) async { var postParams = {"userToken": userToken, "userOTP": userOTP}; - Response response = await injector.get().postJsonForResponse(ApiConsts.ForgetPasswordOTPCompare, postParams); + Response response = await injector.get().postJsonForResponse(ApiConsts.forgetPasswordOTPCompare, postParams); PasswordOTPCompareResModel otpCompare = PasswordOTPCompareResModel.fromJson(jsonDecode(response.body)); return otpCompare; @@ -205,7 +205,7 @@ class UserRepoImp implements UserRepo { "newPassword": newPassword, }; - Response response = await injector.get().postJsonForResponse(ApiConsts.ForgetPassword, postParams); + Response response = await injector.get().postJsonForResponse(ApiConsts.forgetPassword, postParams); ConfirmPasswordRespModel data = ConfirmPasswordRespModel.fromJson(jsonDecode(response.body)); return data; @@ -222,14 +222,14 @@ class UserRepoImp implements UserRepo { String t = AppState().getUser.data!.accessToken ?? ""; debugPrint("token $t"); - return await injector.get().postJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.ChangePassword, postParams, token: t); + return await injector.get().postJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.changePassword, postParams, token: t); } @override Future changeMobileNoOTPRequest(countryID, String mobileNo, String password) async { var postParams = {"countryID": 1, "mobileNo": mobileNo, "password": password}; String t = AppState().getUser.data!.accessToken ?? ""; - return await injector.get().postJsonForObject((json) => ChangeMobileRespModel.fromJson(json), ApiConsts.ChangeMobileNoOTPRequest, postParams, token: t); + return await injector.get().postJsonForObject((json) => ChangeMobileRespModel.fromJson(json), ApiConsts.changeMobileNoOTPRequest, postParams, token: t); } @override @@ -239,21 +239,21 @@ class UserRepoImp implements UserRepo { "userOTP": userOTP, }; String t = AppState().getUser.data!.accessToken ?? ""; - return await injector.get().postJsonForObject((json) => ConfirmMobileRespModel.fromJson(json), ApiConsts.ChangeMobileNo, postParams, token: t); + return await injector.get().postJsonForObject((json) => ConfirmMobileRespModel.fromJson(json), ApiConsts.changeMobileNo, postParams, token: t); } @override Future changeEmailOTPRequest(String email, String password) async { var postParams = {"email": email, "password": password}; String t = AppState().getUser.data!.accessToken ?? ""; - return await injector.get().postJsonForObject((json) => ChanEmailRespModel.fromJson(json), ApiConsts.ChangeEmailOTPRequest, postParams, token: t); + return await injector.get().postJsonForObject((json) => ChanEmailRespModel.fromJson(json), ApiConsts.changeEmailOTPRequest, postParams, token: t); } @override Future changeEmail(String userToken, String userOTP) async { var postParams = {"userToken": userToken, "userOTP": userOTP}; String t = AppState().getUser.data!.accessToken ?? ""; - return await injector.get().postJsonForObject((json) => ConfirmEmailRespModel.fromJson(json), ApiConsts.ChangeEmail, postParams, token: t); + return await injector.get().postJsonForObject((json) => ConfirmEmailRespModel.fromJson(json), ApiConsts.changeEmail, postParams, token: t); } @override @@ -273,7 +273,7 @@ class UserRepoImp implements UserRepo { }; String t = AppState().getUser.data!.accessToken ?? ""; - return await injector.get().postJsonForObject((json) => VerifyEmailRespModel.fromJson(json), ApiConsts.EmailVerify, postParams, token: t); + return await injector.get().postJsonForObject((json) => VerifyEmailRespModel.fromJson(json), ApiConsts.emailVerify, postParams, token: t); } @override @@ -281,7 +281,7 @@ class UserRepoImp implements UserRepo { var postParams = {"userToken": userToken, "userOTP": userOTP}; String t = AppState().getUser.data!.accessToken ?? ""; - return await injector.get().postJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.EmailVerifyOTPVerify, postParams, token: t); + return await injector.get().postJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.emailVerifyOTPVerify, postParams, token: t); } @override @@ -293,7 +293,7 @@ class UserRepoImp implements UserRepo { debugPrint("token $t"); return await injector.get().postJsonForObject( (json) => ImageResponse.fromJson(json), - ApiConsts.UpdateUserImage, + ApiConsts.updateUserImage, postParams, token: t, ); @@ -306,7 +306,7 @@ class UserRepoImp implements UserRepo { String t = AppState().getUser.data!.accessToken ?? ""; debugPrint("token $t"); - return await injector.get().postJsonForObject((json) => ImageResponse.fromJson(json), ApiConsts.UpdateUserImage, postParams, token: t); + return await injector.get().postJsonForObject((json) => ImageResponse.fromJson(json), ApiConsts.updateUserImage, postParams, token: t); } @override @@ -328,6 +328,6 @@ class UserRepoImp implements UserRepo { Future logoutUser() async { String t = AppState().getUser.data!.accessToken ?? ""; var postParams = {"token": AppState().getUser.data!.accessToken ?? ""}; - return await injector.get().postJsonForResponse(ApiConsts.LogoutUser, postParams, token: t); + return await injector.get().postJsonForResponse(ApiConsts.logoutUser, postParams, token: t); } } diff --git a/lib/services/common_services.dart b/lib/services/common_services.dart index b1788e5..c80988d 100644 --- a/lib/services/common_services.dart +++ b/lib/services/common_services.dart @@ -104,7 +104,7 @@ class CommonServicesImp implements CommonAppServices { destLongitude, ); - return distance ?? 0.0; + return (distance / 1000) ?? 0.0; } catch (e) { logger.e(e.toString()); return 0.0; diff --git a/lib/utils/date_helper.dart b/lib/utils/date_helper.dart index e9cd7ab..9c11007 100644 --- a/lib/utils/date_helper.dart +++ b/lib/utils/date_helper.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class DateHelper { @@ -50,3 +51,44 @@ class DateHelper { return formatter.format(date); } } + +extension TimeOfDayUtils on TimeOfDay { + bool isAfter(TimeOfDay other) { + final thisTime = DateTime(0, 1, 1, hour, minute); + final otherTime = DateTime(0, 1, 1, other.hour, other.minute); + return thisTime.isAfter(otherTime); + } + + bool isBefore(TimeOfDay other) { + final thisTime = DateTime(0, 1, 1, hour, minute); + final otherTime = DateTime(0, 1, 1, other.hour, other.minute); + return thisTime.isBefore(otherTime); + } + + static TimeOfDay convertStringToTimeOfDay(String timeString) { + bool is12HourFormat = timeString.contains(RegExp(r'AM|PM', caseSensitive: false)); + + int hour; + int minute; + + if (is12HourFormat) { + List parts = timeString.split(' '); // Split time and AM/PM part + List timeParts = parts[0].split(':'); // Split hours and minutes + + hour = int.parse(timeParts[0]); + minute = int.parse(timeParts[1]); + + if (parts[1].toUpperCase() == 'PM' && hour != 12) { + hour += 12; + } else if (parts[1].toUpperCase() == 'AM' && hour == 12) { + hour = 0; + } + } else { + List timeParts = timeString.split(':'); + hour = int.parse(timeParts[0]); + minute = int.parse(timeParts[1]); + } + + return TimeOfDay(hour: hour, minute: minute); + } +} diff --git a/lib/utils/enums.dart b/lib/utils/enums.dart index 0657413..cbb19c1 100644 --- a/lib/utils/enums.dart +++ b/lib/utils/enums.dart @@ -11,28 +11,21 @@ class AppEnums { static const int requestFilterEnumId = 16; // Requests Filter Enums + static const int branchServicesEnumId = 6; // Branch Services Filter Enums static const int myAdsFilterEnumId = 18; // My Ads Filter Enums static const int exploreAdsFilterEnumId = 23; // Explore Ads Filter Enums static const int appointmentsFilterEnumId = 13; // Appointments Filter Enums static const int requestStatusesFilterEnumId = 15; // Appointments Filter Enums static const int conditionEnumId = -1; // to get the Condition Filter Enums - static const int shippingStatusEnumId = -2; // to get the Shipping Filter Enums + static const int shippingStatusEnumId = 30; // to get the Shipping Filter Enums } enum DashboardRouteEnum { fromAdsPayment, fromAdsSubmit, - - - - - - none, } - - enum VehicleType { car, motorCycle, @@ -70,6 +63,8 @@ enum AdPostStatus { reserved, buyingService, reserveCancel, + deActive, + pendingForActive, allAds, } @@ -142,6 +137,7 @@ enum ServiceStatusEnum { rejected, //4 blocked, //5 deactivated, //6 + allServices, //0 } enum DocumentStatusEnum { @@ -173,9 +169,11 @@ enum AppointmentStatusEnum { cancelled, rescheduled, allAppointments, - upcoming, workStarted, visitCompleted, + today, + past, + upcoming, } enum AppointmentPaymentStatusEnum { @@ -228,6 +226,7 @@ enum SubscriptionActionTypeEnum { enum ShippingRequestStatusEnum { allRequests, + pending, initiated, inTransit, outForDelivery, diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index e709ddd..bffe45d 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -29,7 +29,8 @@ class Utils { static bool get isLoading => _isLoadingVisible; static void showToast(String message) { - Fluttertoast.showToast(msg: message, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM, timeInSecForIosWeb: 2, backgroundColor: Colors.black54, textColor: Colors.white, fontSize: 16.0); + Fluttertoast.showToast( + msg: message, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM, timeInSecForIosWeb: 2, backgroundColor: Colors.black54, textColor: Colors.white, fontSize: 16.0); } static Future openNumberViaCaller({required String phoneNumber}) async { @@ -62,10 +63,10 @@ class Utils { await launchUrlString(url, mode: LaunchMode.externalApplication); } - static Future pickDateFromDatePicker(BuildContext context, {DateTime? firstDate, DateTime? lastDate}) async { + static Future pickDateFromDatePicker(BuildContext context, {DateTime? initial, DateTime? firstDate, DateTime? lastDate}) async { DateTime? pickedDate = await showDatePicker( context: context, - initialDate: DateTime.now(), //get today's date + initialDate: initial ?? DateTime.now(), //get today's date firstDate: firstDate ?? DateTime.now(), //DateTime.now() - not to allow to choose before today. lastDate: lastDate ?? DateTime(2101)); @@ -78,7 +79,10 @@ class Utils { } static Future pickTime(BuildContext context, {TimeOfDay? initialTime}) async { - TimeOfDay? timeOfDay = await showTimePicker(context: context, initialTime: initialTime ?? TimeOfDay.now()); + TimeOfDay? timeOfDay = await showTimePicker( + context: context, + initialTime: initialTime ?? TimeOfDay.now(), + ); if (timeOfDay == null) { return ""; @@ -220,6 +224,7 @@ class Utils { return MyColors.adCancelledStatusColor; case AdPostStatus.pendingForPost: + case AdPostStatus.pendingForActive: return MyColors.inProgressColor; case AdPostStatus.active: @@ -234,6 +239,9 @@ class Utils { case AdPostStatus.reserved: return MyColors.primaryColor; + case AdPostStatus.deActive: + return MyColors.textColor; + case AdPostStatus.buyingService: case AdPostStatus.reserveCancel: case AdPostStatus.allAds: @@ -313,6 +321,9 @@ class Utils { case ShippingRequestStatusEnum.delivered: return "Delivered"; + + case ShippingRequestStatusEnum.pending: + return "Pending"; } } @@ -332,6 +343,9 @@ class Utils { case ShippingRequestStatusEnum.delivered: return MyColors.greenColor; + + case ShippingRequestStatusEnum.pending: + return MyColors.pendingColor; } } @@ -350,7 +364,7 @@ class Utils { return MyColors.adCancelledStatusColor; case BranchStatusEnum.blocked: - return MyColors.adPendingStatusColor; + return MyColors.expiredColor; case BranchStatusEnum.approvedOrActive: return MyColors.greenColor; @@ -379,7 +393,12 @@ class Utils { } } - static statusContainerChip({required String text, EdgeInsetsGeometry padding = const EdgeInsets.symmetric(vertical: 3, horizontal: 6), Color chipColor = MyColors.greenColor, Color textColor = MyColors.white}) { + static statusContainerChip({ + required String text, + EdgeInsetsGeometry padding = const EdgeInsets.symmetric(vertical: 3, horizontal: 6), + Color chipColor = MyColors.greenColor, + Color textColor = MyColors.white, + }) { return Container( decoration: BoxDecoration( color: chipColor, diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index 152d9be..76b0f09 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -190,11 +190,12 @@ class AdVM extends BaseVM { } myAdsFilterOptions.insert(0, FilterListModel(title: "All Ads", isSelected: true, id: 0)); + log("myAdsFilterOptions: ${myAdsFilterOptions.last.id}"); + for (int i = 0; i < exploreAdsEnums.length; i++) { exploreAdsFilterOptions.add(FilterListModel(title: "${exploreAdsEnums[i].enumValueStr} Ads", isSelected: false, id: exploreAdsEnums[i].enumValue)); } exploreAdsFilterOptions.insert(0, FilterListModel(title: "All Ads", isSelected: true, id: 0)); - notifyListeners(); } @@ -207,8 +208,7 @@ class AdVM extends BaseVM { } exploreAdsFilterOptions[index].isSelected = true; if (createdByRoleFilter == CreatedByRoleEnum.allAds) { - exploreAdsFilteredList = exploreAds; - notifyListeners(); + getExploreAds(); return; } setState(ViewState.busy); @@ -232,12 +232,12 @@ class AdVM extends BaseVM { myAdsFilterOptions[index].isSelected = true; if (adPostStatusEnum.getIdFromAdPostStatusEnum() == 0) { - myAdsFilteredList = myAds; - notifyListeners(); + getMyAds(); return; } // this means if the filter is reserved ads if (adPostStatusEnum == AdPostStatus.reserved) { + log("reserved: ${myReservedAds.length}"); myAdsFilteredList = myReservedAds; for (var ad in myAdsFilteredList) { ad.isReservedByMe = true; @@ -284,11 +284,17 @@ class AdVM extends BaseVM { } Future> getMyReservedAds() async { - setState(ViewState.busy); - myReservedAds = await adsRepo.getMyReservedAds(); - isFetchingLists = false; - setState(ViewState.idle); - return myReservedAds; + try { + setState(ViewState.busy); + myReservedAds = await adsRepo.getMyReservedAds(); + isFetchingLists = false; + setState(ViewState.idle); + return myReservedAds; + } catch (e) { + logger.e(e.toString()); + setState(ViewState.idle); + return []; + } } Future getVehicleTypes() async { @@ -297,7 +303,8 @@ class AdVM extends BaseVM { } Future getVehicleAdsDuration() async { - vehicleAdsDurations = await adsRepo.getAdsDuration(); + final countryId = AppState().getUser.data!.userInfo!.countryId; + vehicleAdsDurations = await adsRepo.getAdsDuration(countryId: countryId); notifyListeners(); } @@ -329,7 +336,7 @@ class AdVM extends BaseVM { return; } Utils.hideLoading(context); - Utils.showToast(respModel.message ?? "Ad has been marked as sold successfully!"); + Utils.showToast(respModel.message ?? LocaleKeys.adMarkedAsSold.tr()); updateIsExploreAds(false); applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.sold); //pending for review navigateReplaceWithName(context, AppRoutes.dashboard); @@ -540,8 +547,17 @@ class AdVM extends BaseVM { SelectionModel vehicleConditionId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); - void updateSelectionVehicleConditionId(SelectionModel id) { - vehicleConditionId = id; + void updateSelectionVehicleConditionId(SelectionModel id, {bool showSelectionInDropdown = true, bool isForSearch = false}) { + if (isForSearch) { + EnumsModel owner = vehicleConditionsEnum.firstWhere((element) => element.enumValue == id.selectedId); + DropValue ownerValue = DropValue(owner.enumValue, owner.enumValueStr, ""); + if (!ifAlreadyExist(list: vehicleAdConditionSearchHistory, value: ownerValue)) { + addToVehicleAdConditionSearchHistory(value: ownerValue); + } + } + if (showSelectionInDropdown) { + vehicleConditionId = id; + } notifyListeners(); } @@ -591,26 +607,16 @@ class AdVM extends BaseVM { OfferRequestCommentModel( index: 0, isSelected: true, - title: LocaleKeys.changedMind.tr(), + title: LocaleKeys.vehicleDealOutsideApp.tr(), ), OfferRequestCommentModel( index: 1, isSelected: false, - title: LocaleKeys.veryHighPrice.tr(), + title: LocaleKeys.changedDesireToSell.tr(), ), OfferRequestCommentModel( index: 2, isSelected: false, - title: LocaleKeys.alreadySold.tr(), - ), - OfferRequestCommentModel( - index: 3, - isSelected: false, - title: LocaleKeys.customerNotResponding.tr(), - ), - OfferRequestCommentModel( - index: 4, - isSelected: false, title: LocaleKeys.otherVar.tr(), ), ]; @@ -626,6 +632,7 @@ class AdVM extends BaseVM { value.isSelected = false; } selectedDeActivateAdCommentModel = deActivateAdModelList[index]; + log("selectedDeActivateAdCommentModel: ${selectedDeActivateAdCommentModel.index}"); deActivateAdModelList[index].isSelected = true; notifyListeners(); } @@ -684,9 +691,11 @@ class AdVM extends BaseVM { SelectionModel vehicleSellerTypeId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); - void updateSelectionVehicleSellerTypeId(SelectionModel id) { + void updateSelectionVehicleSellerTypeId(SelectionModel id, {bool needRefresh = true}) { vehicleSellerTypeId = id; - notifyListeners(); + if (needRefresh) { + notifyListeners(); + } } // SelectionModel vehicleDamagePartId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); @@ -925,12 +934,12 @@ class AdVM extends BaseVM { vehicleTransmissionId.errorValue = ""; } - if (vehicleSellerTypeId.selectedId == -1) { - vehicleSellerTypeId.errorValue = LocaleKeys.vehicle_vehicleSellerType.tr(); - isValidated = false; - } else { - vehicleSellerTypeId.errorValue = ""; - } + // if (vehicleSellerTypeId.selectedId == -1) { + // vehicleSellerTypeId.errorValue = LocaleKeys.vehicle_vehicleSellerType.tr(); + // isValidated = false; + // } else { + // vehicleSellerTypeId.errorValue = ""; + // } if (vehicleCountryId.selectedId == -1) { vehicleCountryId.errorValue = LocaleKeys.vehicle_selectAny.tr(); @@ -1033,7 +1042,7 @@ class AdVM extends BaseVM { bool isAdDurationValidated() { bool isValidated = true; - if (vehicleAdDurationId.selectedId == -1) { + if (AppState().userType != UserType.providerDealer && vehicleAdDurationId.selectedId == -1) { vehicleAdDurationId.errorValue = LocaleKeys.vehicle_vehiclePart.tr(); isValidated = false; } else { @@ -1259,6 +1268,11 @@ class AdVM extends BaseVM { notifyListeners(); } + void updateDamagePartDescription(int index, String value) { + vehicleDamageCards[index].damagePartDescription = value; + notifyListeners(); + } + void removeDamageImageFromCard(int imageIndex, String filePath, int cardIndex) { VehicleDamageCard card = vehicleDamageCards.elementAt(cardIndex); if (card.partImages == null) return; @@ -1427,14 +1441,14 @@ class AdVM extends BaseVM { Utils.hideLoading(context); if (genericRespModel.messageStatus != 1) { - Utils.showToast(LocaleKeys.error.tr() + ": ${genericRespModel.message}"); + Utils.showToast("${LocaleKeys.error.tr()}: ${genericRespModel.message}"); } else { resetSpecialServiceBottomSheet(); navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.ads); } } catch (e) { Utils.hideLoading(context); - Utils.showToast(LocaleKeys.error.tr() + ": ${e.toString()}"); + Utils.showToast("${LocaleKeys.error.tr()}: ${e.toString()}"); } } @@ -1447,7 +1461,7 @@ class AdVM extends BaseVM { } Ads ads = Ads( id: isAdEditEnabled ? previousAdDetails!.id : null, - adsDurationID: vehicleAdDurationId.selectedId, + adsDurationID: vehicleAdDurationId.selectedId == -1 ? 0 : vehicleAdDurationId.selectedId, startDate: selectionDurationStartDate, countryId: vehicleCountryId.selectedId, specialServiceIDs: adsSelectedServices, @@ -1466,6 +1480,7 @@ class AdVM extends BaseVM { VehiclePostingDamageParts stringImage = await convertFileToVehiclePostingDamageParts( imageModel: image, damagePartId: card.partSelectedId!.selectedId, + commentParam: card.damagePartDescription ?? "", ); vehicleDamageImages.add(stringImage); } @@ -1483,7 +1498,7 @@ class AdVM extends BaseVM { vehicleConditionID: vehicleConditionId.selectedId, vehicleMileageID: vehicleMileageId.selectedId, vehicleTransmissionID: vehicleTransmissionId.selectedId, - vehicleSellerTypeID: vehicleSellerTypeId.selectedId, + vehicleSellerTypeID: vehicleSellerTypeId.selectedId == -1 ? 1 : vehicleSellerTypeId.selectedId, cityID: vehicleCityId.selectedId, price: int.parse(vehicleDemandAmount), vehicleVIN: vehicleVin, @@ -1530,7 +1545,7 @@ class AdVM extends BaseVM { return vehiclePostingImages; } - Future convertFileToVehiclePostingDamageParts({required ImageModel imageModel, required int damagePartId}) async { + Future convertFileToVehiclePostingDamageParts({required ImageModel imageModel, required int damagePartId, required String commentParam}) async { VehiclePostingDamageParts vehiclePostingDamageParts; if (imageModel.isFromNetwork ?? false) { @@ -1538,17 +1553,14 @@ class AdVM extends BaseVM { id: imageModel.id, vehicleDamagePartID: damagePartId, imageUrl: imageModel.filePath, + comment: commentParam, vehiclePostingID: isAdEditEnabled ? previousAdDetails!.vehiclePostingID : null, ); } else { File file = File(imageModel.filePath!); List imageBytes = await file.readAsBytes(); String image = base64Encode(imageBytes); - vehiclePostingDamageParts = VehiclePostingDamageParts( - vehicleImageBase64: image, - vehicleDamagePartID: damagePartId, - imageUrl: file.path, - ); + vehiclePostingDamageParts = VehiclePostingDamageParts(vehicleImageBase64: image, vehicleDamagePartID: damagePartId, imageUrl: file.path, comment: commentParam); } return vehiclePostingDamageParts; @@ -1556,8 +1568,15 @@ class AdVM extends BaseVM { // ************ ADS SEARCH VIEW **************** + List vehicleConditionsEnum = []; + Future populateDataForAdFilter() async { setState(ViewState.busy); + + if (vehicleConditionsEnum.isEmpty) { + vehicleConditionsEnum = await commonRepo.getEnumTypeValues(enumTypeID: AppEnums.conditionEnumId); + } + if (vehicleBrands.isEmpty) { vehicleBrands = await commonRepo.getVehicleBrands(vehicleTypeId: -1); } @@ -1693,6 +1712,55 @@ class AdVM extends BaseVM { notifyListeners(); } + // Created Date + + List vehicleAdCreatedDateSearchHistory = []; + + void removeVehicleAdCreatedDateSearchHistory({bool isClear = false, required int index}) { + if (isClear) { + vehicleAdCreatedDateSearchHistory.clear(); + notifyListeners(); + return; + } + vehicleAdCreatedDateSearchHistory.removeAt(index); + if (vehicleAdCreatedDateSearchHistory.isEmpty) { + updateAdsFiltersCounter(adsFiltersCounter - 1); + } + notifyListeners(); + } + + void addToVehicleAdCreatedDateSearchHistory({required DropValue value}) { + if (vehicleAdCreatedDateSearchHistory.isEmpty) { + updateAdsFiltersCounter(adsFiltersCounter + 1); + } + vehicleAdCreatedDateSearchHistory.add(value); + notifyListeners(); + } + + // Ad Condition + List vehicleAdConditionSearchHistory = []; + + void removeVehicleAdConditionSearchHistory({bool isClear = false, required int index}) { + if (isClear) { + vehicleAdConditionSearchHistory.clear(); + notifyListeners(); + return; + } + vehicleAdConditionSearchHistory.removeAt(index); + if (vehicleAdConditionSearchHistory.isEmpty) { + updateAdsFiltersCounter(adsFiltersCounter - 1); + } + notifyListeners(); + } + + void addToVehicleAdConditionSearchHistory({required DropValue value}) { + if (vehicleAdConditionSearchHistory.isEmpty) { + updateAdsFiltersCounter(adsFiltersCounter + 1); + } + vehicleAdConditionSearchHistory.add(value); + notifyListeners(); + } + int adsFiltersCounter = 0; updateAdsFiltersCounter(int value) { @@ -1705,6 +1773,8 @@ class AdVM extends BaseVM { vehicleAdOwnerSearchHistory.clear(); vehicleLocationAdSearchHistory.clear(); vehicleYearAdSearchHistory.clear(); + vehicleAdCreatedDateSearchHistory.clear(); + vehicleAdConditionSearchHistory.clear(); adsFiltersCounter = 0; getExploreAds(); notifyListeners(); @@ -1747,11 +1817,27 @@ class AdVM extends BaseVM { } } + List conditionsIdsList = []; + if (vehicleAdConditionSearchHistory.isNotEmpty) { + for (var element in vehicleAdConditionSearchHistory) { + conditionsIdsList.add(element.id.toString()); + } + } + + List createdDatesList = []; + if (vehicleAdCreatedDateSearchHistory.isNotEmpty) { + for (var element in vehicleAdCreatedDateSearchHistory) { + createdDatesList.add(element.value.toString()); + } + } + exploreAdsFilteredList = await adsRepo.getExploreAdsBasedOnFilters( cityIdsList: cityIdsList, createdByRolesIdsList: adOwnerIdsList, vehicleBrandIdsList: brandsIdsList, vehicleModelYearIdsList: vehicleYearIdsList, + vehicleAdConditionIdsList: conditionsIdsList, + vehicleAdCreatedDateList: createdDatesList, ); setState(ViewState.idle); } @@ -1782,7 +1868,6 @@ class AdVM extends BaseVM { Utils.showToast(e.toString()); return false; } - return false; } void autoFillSelectedVehicleType() async { @@ -1813,7 +1898,8 @@ class AdVM extends BaseVM { SelectionModel( selectedId: vehicleAdsDurations[index].id ?? 0, selectedOption: "${vehicleAdsDurations[index].days} Days", - itemPrice: vehicleAdsDurations[index].price!.toInt().toString(), + itemPrice: vehicleAdsDurations[index].price!.toString(), + currency: vehicleAdsDurations[index].currency!.toString(), ), ); } @@ -1843,7 +1929,8 @@ class AdVM extends BaseVM { updateSelectionVehicleColorId(SelectionModel(selectedId: previousAdDetails!.vehicle!.color!.id!, selectedOption: previousAdDetails!.vehicle!.color!.label ?? "")); updateSelectionVehicleConditionId(SelectionModel(selectedId: previousAdDetails!.vehicle!.condition!.id!, selectedOption: previousAdDetails!.vehicle!.condition!.label ?? "")); updateSelectionVehicleCategoryId(SelectionModel(selectedId: previousAdDetails!.vehicle!.category!.id!, selectedOption: previousAdDetails!.vehicle!.category!.label ?? "")); - updateSelectionVehicleMileageId(SelectionModel(selectedId: previousAdDetails!.vehicle!.mileage!.id!, selectedOption: "${previousAdDetails!.vehicle!.mileage!.mileageStart} - ${previousAdDetails!.vehicle!.mileage!.mileageEnd}")); + updateSelectionVehicleMileageId(SelectionModel( + selectedId: previousAdDetails!.vehicle!.mileage!.id!, selectedOption: "${previousAdDetails!.vehicle!.mileage!.mileageStart} - ${previousAdDetails!.vehicle!.mileage!.mileageEnd}")); updateSelectionVehicleTransmissionId(SelectionModel(selectedId: previousAdDetails!.vehicle!.transmission!.id!, selectedOption: previousAdDetails!.vehicle!.transmission!.label ?? "")); updateSelectionVehicleSellerTypeId(SelectionModel(selectedId: previousAdDetails!.vehicle!.sellertype!.id!, selectedOption: previousAdDetails!.vehicle!.sellertype!.label ?? "")); int indexCountry = vehicleCountries.indexWhere((element) => element.id == previousAdDetails!.vehicle!.countryID); @@ -1882,6 +1969,7 @@ class AdVM extends BaseVM { selectedId: element.vehicleDamagePartID!, selectedOption: element.partName ?? "", ), + damagePartDescription: "", partImages: [imageModel], ); addNewDamagePartCard(damageCard: vehicleDamageCard); @@ -1970,17 +2058,19 @@ class AdVM extends BaseVM { class VehicleDamageCard { List? partImages; SelectionModel? partSelectedId; + String? damagePartDescription; String partImageErrorValue; VehicleDamageCard({ this.partImages, this.partSelectedId, + this.damagePartDescription, this.partImageErrorValue = "", }); @override String toString() { - return 'VehicleDamageCard{partImages: $partImages, partSelectedId: $partSelectedId, partImageErrorValue: $partImageErrorValue}'; + return 'VehicleDamageCard{partImages: $partImages, partSelectedId: $partSelectedId,damagePartDescription: $damagePartDescription, partImageErrorValue: $partImageErrorValue}'; } } diff --git a/lib/view_models/appointments_view_model.dart b/lib/view_models/appointments_view_model.dart index 96db74f..7547d28 100644 --- a/lib/view_models/appointments_view_model.dart +++ b/lib/view_models/appointments_view_model.dart @@ -38,9 +38,11 @@ import 'package:mc_common_app/view_models/dashboard_view_model_customer.dart'; import 'package:mc_common_app/view_models/payment_view_model.dart'; import 'package:mc_common_app/views/appointments/book_appointment_schedules_view.dart'; import 'package:mc_common_app/views/appointments/widgets/appointment_service_pick_bottom_sheet.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'; +import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -81,6 +83,7 @@ class AppointmentsVM extends BaseVM { bool ifItemAlreadySelected(int id) { int indexFound = allSelectedItemsInAppointments.indexWhere((element) => element.id == id); + log("already selected at: $indexFound"); if (indexFound != -1) { return true; } @@ -101,6 +104,7 @@ class AppointmentsVM extends BaseVM { } resetCategorySelectionBottomSheet(); + notifyListeners(); } } @@ -134,7 +138,6 @@ class AppointmentsVM extends BaseVM { } else { isSuccess = false; Utils.showToast(element['message']); - return; } }); @@ -226,7 +229,7 @@ class AppointmentsVM extends BaseVM { Utils.showToast(genericRespModel.message.toString()); return; } - if (genericRespModel.messageStatus == 1) { + if (genericRespModel.messageStatus == 1 && AppState().currentAppType == AppType.customer) { context.read().onNavbarTapped(1); applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.cancelled); Utils.showToast(genericRespModel.message.toString()); @@ -236,6 +239,7 @@ class AppointmentsVM extends BaseVM { navigateReplaceWithNameUntilRoute(context, AppRoutes.dashboard); } } catch (e) { + logger.e(e.toString()); Utils.showToast(e.toString()); } } @@ -248,16 +252,16 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - String pickedHomeLocation = ""; - - void updatePickedHomeLocation(String value) { - pickedHomeLocation = value; - pickHomeLocationError = ""; - if (currentServiceSelection != null) { - currentServiceSelection!.homeLocation = value; - } - notifyListeners(); - } + // String pickedHomeLocation = ""; + // + // void updatePickedHomeLocation(String value) { + // pickedHomeLocation = value; + // pickHomeLocationError = ""; + // if (currentServiceSelection != null) { + // currentServiceSelection!.homeLocation = value; + // } + // notifyListeners(); + // } SelectionModel branchSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); @@ -272,12 +276,64 @@ class AppointmentsVM extends BaseVM { List servicesInCurrentAppointment = []; ServiceModel? currentServiceSelection; + CurrentLocationInfoModel? currentLocationInfoModel; + + void updateCurrentLocationInfoModel(CurrentLocationInfoModel? value) { + currentLocationInfoModel = value; + pickHomeLocationError = ""; + if (currentServiceSelection != null && value != null) { + currentServiceSelection!.servicelocationInfo = value; + } + + if (value != null) { + for (var service in servicesInCurrentAppointment) { + service.servicelocationInfo.address = value.address; + service.servicelocationInfo.latitude = value.latitude; + service.servicelocationInfo.longitude = value.longitude; + service.servicelocationInfo.distanceToBranch = value.distanceToBranch; + service.servicelocationInfo.homeChargesInCurrentService = double.parse(service.rangePricePerKm ?? "0.0"); + } + } + + notifyListeners(); + } + void updateBranchServiceId(SelectionModel id) async { branchSelectedServiceId = id; currentServiceSelection = branchServices.firstWhere((element) => element.serviceProviderServiceId == id.selectedId); + if (currentServiceSelection == null) { + return; + } + + servicesInCurrentAppointment.forEach((i) { + log("hlo1: ${i.serviceDescription} ${i.servicelocationInfo.homeChargesInCurrentService}"); + }); + if (!(currentServiceSelection!.isAllowAppointmentHome ?? false)) { + updateIsHomeTapped(false); + return; + } + if (currentLocationInfoModel != null) { + currentLocationInfoModel!.homeChargesInCurrentService = double.parse(currentServiceSelection!.rangePricePerKm ?? "0.0"); + currentServiceSelection!.servicelocationInfo = CurrentLocationInfoModel( + address: currentLocationInfoModel!.address, + latitude: currentLocationInfoModel!.latitude, + longitude: currentLocationInfoModel!.longitude, + 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}"); + }); - log("currentServiceSelection isAllowAppointmentHome: ${currentServiceSelection!.isAllowAppointmentHome}"); - log("currentServiceSelection serviceDescription: ${currentServiceSelection!.serviceDescription}"); notifyListeners(); } @@ -298,9 +354,18 @@ class AppointmentsVM extends BaseVM { } resetAfterBookingAppointment() { - // allSelectedItemsInAppointments.clear(); + allSelectedItemsInAppointments.clear(); + currentServiceSelection = null; + for (var element in servicesInCurrentAppointment) { + if (element.serviceItems != null) { + element.serviceItems!.clear(); + element.serviceItems = []; + } + } servicesInCurrentAppointment.clear(); - // serviceAppointmentScheduleList.clear(); + servicesInCurrentAppointment = []; + currentLocationInfoModel = null; + log("here servicesInCurrentAppointment: ${servicesInCurrentAppointment.length}"); } List myAppointmentsEnum = []; @@ -315,13 +380,11 @@ class AppointmentsVM extends BaseVM { } appointmentsFilterOptions.insert(0, FilterListModel(title: "All Appointments", isSelected: true, id: 0)); - // TODO: THIS SHOULD REMOVED AND ADDED IN THE ENUMS API - appointmentsFilterOptions.add(FilterListModel(title: "Work In Progress", isSelected: false, id: 7)); - appointmentsFilterOptions.add(FilterListModel(title: "Visit Completed", isSelected: false, id: 8)); notifyListeners(); } applyFilterOnAppointmentsVM({required AppointmentStatusEnum appointmentStatusEnum, bool isNeedCustomerFilter = false}) { + log("appointmentStatusEnum: ${appointmentStatusEnum}"); // isNeedCustomerFilter IS ONLY FOR THE PROVIDER APP if (appointmentsFilterOptions.isEmpty) return; for (var value in appointmentsFilterOptions) { @@ -405,7 +468,9 @@ class AppointmentsVM extends BaseVM { myAppointments = await appointmentRepo.getMyAppointmentsForCustomersByFilters(); // myFilteredAppointments = myAppointments; myUpComingAppointments = myAppointments - .where((element) => (element.appointmentStatusEnum == AppointmentStatusEnum.booked || element.appointmentStatusEnum == AppointmentStatusEnum.confirmed) && (DateHelper.parseStringToDate(element.appointmentDate!).isAfter(DateTime.now()))) + .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); @@ -433,39 +498,47 @@ class AppointmentsVM extends BaseVM { myAppointments = await appointmentRepo.getMyAppointmentsForProvider(map); myFilteredAppointments = myAppointments; myUpComingAppointments = myAppointments - .where((element) => (element.appointmentStatusEnum == AppointmentStatusEnum.confirmed || element.appointmentStatusEnum == AppointmentStatusEnum.booked) && (DateHelper.parseStringToDate(element.appointmentDate!).isAfter(DateTime.now()))) + .where((element) => + (element.appointmentStatusEnum == AppointmentStatusEnum.confirmed || element.appointmentStatusEnum == AppointmentStatusEnum.booked) && + (DateHelper.parseStringToDate(element.appointmentDate!).isAfter(DateTime.now()))) .toList(); applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments, isNeedCustomerFilter: true); setState(ViewState.idle); } - updateAppointmentStatus({required int appointmentId, required AppointmentStatusEnum appointmentStatusEnum, bool isNeedToRebuild = false}) async { + Future updateAppointmentStatus({required int appointmentId, required AppointmentStatusEnum appointmentStatusEnum, bool isNeedToRebuild = false}) async { if (isNeedToRebuild) setState(ViewState.busy); try { GenericRespModel genericRespModel = await appointmentRepo.updateAppointmentStatus(appointmentId: appointmentId, appointmentStatusEnum: appointmentStatusEnum); if (genericRespModel.messageStatus == 1) { Utils.showToast(LocaleKeys.appointmentStatusUpdated.tr()); + return true; } else { Utils.showToast(genericRespModel.message.toString()); + return false; } } catch (e) { Utils.showToast(e.toString()); + return false; } } - updateAppointmentPaymentStatus(Map map, {bool isNeedToRebuild = false}) async { + Future updateAppointmentPaymentStatus(Map map, {bool isNeedToRebuild = false}) async { if (isNeedToRebuild) setState(ViewState.busy); try { GenericRespModel genericRespModel = await appointmentRepo.updateAppointmentPaymentStatus(map); if (genericRespModel.messageStatus == 1) { Utils.showToast(LocaleKeys.paymentStatusUpdated.tr()); + return true; } else { Utils.showToast(genericRespModel.message.toString()); + return false; } } catch (e) { Utils.showToast(e.toString()); + return false; } } @@ -484,7 +557,8 @@ class AppointmentsVM extends BaseVM { bool inNeedToEnableMergeButton = false; void updateCheckBoxInMergeRequest(int currentIndex) { - myFilteredAppointments2[selectedAppointmentIndex].customerAppointmentList![currentIndex].isSelected = !(myFilteredAppointments2[selectedAppointmentIndex].customerAppointmentList?[currentIndex].isSelected ?? false); + myFilteredAppointments2[selectedAppointmentIndex].customerAppointmentList![currentIndex].isSelected = + !(myFilteredAppointments2[selectedAppointmentIndex].customerAppointmentList?[currentIndex].isSelected ?? false); int count = countSelected(myFilteredAppointments2[selectedAppointmentIndex].customerAppointmentList ?? []); if (count > 1) { @@ -540,38 +614,35 @@ class AppointmentsVM extends BaseVM { void onItemUpdateOrSelected(int index, bool selected, int itemId) { int serviceIndex = servicesInCurrentAppointment.indexWhere((element) => element.serviceId == currentServiceSelection!.serviceId!); - log("index: $index"); - log("selected: $selected"); - log("itemId: $itemId"); - log("servicesInCurrentAppointment: ${servicesInCurrentAppointment.length}"); - serviceItemsFromApi[index].isUpdateOrSelected = selected; serviceItemsFromApi[index].isHomeSelected = isHomeTapped; - log(" serviceItemsFromApi[index].isHomeSelected: ${serviceItemsFromApi[index].isHomeSelected}"); + log(" serviceIndex: $serviceIndex"); if (selected) { selectedSubServicesCounter = selectedSubServicesCounter + 1; selectSubServicesError = ""; - currentServiceSelection!.serviceItems!.add(serviceItemsFromApi[index]); - allSelectedItemsInAppointments.add(serviceItemsFromApi[index]); - for (var element in allSelectedItemsInAppointments) { - if (!ifItemAlreadySelected(element.id!)) { - if (serviceIndex != -1) { - servicesInCurrentAppointment[serviceIndex].serviceItems!.add(serviceItemsFromApi[index]); - servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice = servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice + double.parse((serviceItemsFromApi[index].price) ?? "0.0"); - } + + if (!ifItemAlreadySelected(serviceItemsFromApi[index].id!)) { + currentServiceSelection!.serviceItems!.add(serviceItemsFromApi[index]); + allSelectedItemsInAppointments.add(serviceItemsFromApi[index]); + log("Here with: ${serviceItemsFromApi[index].description}"); + if (serviceIndex != -1) { + // servicesInCurrentAppointment[serviceIndex].serviceItems!.add(serviceItemsFromApi[index]); + servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice = + servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice + double.parse((serviceItemsFromApi[index].price) ?? "0.0"); } } } else { - log("currentServiceSelection!.serviceItems: ${currentServiceSelection!.serviceItems!.length}"); selectedSubServicesCounter = selectedSubServicesCounter - 1; currentServiceSelection!.serviceItems!.removeWhere((element) => element.id == itemId); - log("currentServiceSelection!.serviceItems: ${currentServiceSelection!.serviceItems!.length}"); - allSelectedItemsInAppointments.removeWhere((element) => element.id == itemId); if (serviceIndex != -1) { - servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice = servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice - double.parse((serviceItemsFromApi[index].price) ?? "0.0"); - servicesInCurrentAppointment[serviceIndex].serviceItems!.removeWhere((element) => element.id == itemId); + servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice = + servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice - double.parse((serviceItemsFromApi[index].price) ?? "0.0"); + if (servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice < 0.0) { + servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice = servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice * -1; + } + // servicesInCurrentAppointment[serviceIndex].serviceItems!.removeWhere((element) => element.id == itemId); } } @@ -593,9 +664,11 @@ class AppointmentsVM extends BaseVM { setState(ViewState.busy); serviceItemsFromApi.clear(); serviceItemsFromApi = await branchRepo.getServiceItems(serviceId); + selectedSubServicesCounter = 0; for (var item in serviceItemsFromApi) { if (ifItemAlreadySelected(item.id!)) { item.isUpdateOrSelected = true; + selectedSubServicesCounter++; } } setState(ViewState.idle); @@ -618,7 +691,7 @@ class AppointmentsVM extends BaseVM { } if (isHomeTapped) { - if (pickedHomeLocation == "") { + if (currentLocationInfoModel == null || currentLocationInfoModel!.address == "") { updatePickHomeLocationError(GlobalConsts.homeLocationEmptyError); return false; } @@ -662,8 +735,8 @@ class AppointmentsVM extends BaseVM { isScrollControlled: true, enableDrag: true, builder: (BuildContext context) { - // TODO: It should be calculated from API - double totalKms = 15.3; + double totalKms = double.parse(selectedService.servicelocationInfo.distanceToBranch.toStringAsFixed(2)); + double pricePerKm = double.parse(selectedService.servicelocationInfo.homeChargesInCurrentService.toStringAsFixed(2)); return InfoBottomSheet( title: LocaleKeys.chargesBreakdown.tr().toText(fontSize: 24, isBold: true), description: Column( @@ -695,14 +768,14 @@ class AppointmentsVM extends BaseVM { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ "${totalKms}km ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), - "${selectedService.rangePricePerKm} x $totalKms".toText(fontSize: 12, isBold: true), + "$pricePerKm x $totalKms".toText(fontSize: 12, isBold: true), ], ), 8.height, Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - "${(double.parse(selectedService.rangePricePerKm!)) * totalKms} ${LocaleKeys.sar.tr()}".toText(fontSize: 16, isBold: true), + "${((pricePerKm) * totalKms).toStringAsFixed(2)} ${LocaleKeys.sar.tr()}".toText(fontSize: 16, isBold: true), ], ), ], @@ -714,7 +787,7 @@ class AppointmentsVM extends BaseVM { Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - (selectedService.isHomeSelected ? "${(selectedService.currentTotalServicePrice) + ((double.parse((selectedService.rangePricePerKm ?? "0.0")) * totalKms))}" : "${selectedService.currentTotalServicePrice}") + (selectedService.isHomeSelected ? ((selectedService.currentTotalServicePrice) + (pricePerKm * totalKms)).toStringAsFixed(2) : "${selectedService.currentTotalServicePrice}") .toText(fontSize: 29, isBold: true), 2.width, LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5), @@ -762,7 +835,7 @@ class AppointmentsVM extends BaseVM { void onServicesNextPressed(BuildContext context) async { if (allSelectedItemsInAppointments.isEmpty) { - Utils.showToast("Please select at least one service."); + Utils.showToast(LocaleKeys.pleaseSelectService.tr()); return; } Utils.showLoading(context); @@ -770,8 +843,6 @@ class AppointmentsVM extends BaseVM { List serviceItemIdsForHome = []; List serviceItemIdsForWorkshop = []; for (var serviceItem in allSelectedItemsInAppointments) { - log("homeselcted for : ${serviceItem.isHomeSelected}"); - if (serviceItem.isHomeSelected!) { serviceItemIdsForHome.add(serviceItem.id!.toString()); } else { @@ -794,6 +865,19 @@ class AppointmentsVM extends BaseVM { for (var schedule in serviceAppointmentScheduleList) { amountToPayForAppointment = amountToPayForAppointment + (schedule.amountToPay ?? 0.0); totalAmount = totalAmount + (schedule.amountTotal ?? 0.0); + for (var service in schedule.servicesListInAppointment!) { + int index = servicesInCurrentAppointment.indexWhere((element) => element.serviceProviderServiceId == service.serviceProviderServiceId); + if (index != -1) { + if (servicesInCurrentAppointment[index].isHomeSelected) { + service.servicelocationInfo = servicesInCurrentAppointment[index].servicelocationInfo; + schedule.locationInfoModel = servicesInCurrentAppointment[index].servicelocationInfo; + totalAmount = + totalAmount + ((servicesInCurrentAppointment[index].servicelocationInfo.distanceToBranch) * servicesInCurrentAppointment[index].servicelocationInfo.homeChargesInCurrentService); + schedule.totalLocationCharges = schedule.totalLocationCharges! + + (servicesInCurrentAppointment[index].servicelocationInfo.distanceToBranch * servicesInCurrentAppointment[index].servicelocationInfo.homeChargesInCurrentService); + } + } + } } Utils.hideLoading(context); @@ -867,7 +951,90 @@ class AppointmentsVM extends BaseVM { } } -// BRANCHES RELATED + // REPORT A COMPLAINT + + String complaintDescription = ""; + String complaintDescriptionError = ""; + + void updateComplaintDescription(String value) { + complaintDescription = value; + if (value.isNotEmpty) { + complaintDescriptionError = ""; + } + } + + Future buildComplaintBottomSheet({required int customerID, required int complainType, required String customerName, required BuildContext context}) { + return showModalBottomSheet( + context: context, + isScrollControlled: true, + enableDrag: true, + builder: (BuildContext context) { + return InfoBottomSheet( + title: LocaleKeys.reportComplaint.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), + description: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (customerName.isNotEmpty) ...[ + ("${LocaleKeys.customerName.tr()}: $customerName").toText(fontSize: 15, color: MyColors.lightTextColor), + ], + 10.height, + TxtField( + maxLines: 5, + value: complaintDescription, + errorValue: complaintDescriptionError, + keyboardType: TextInputType.text, + hint: LocaleKeys.description.tr(), + onChanged: (v) => updateComplaintDescription(v), + ), + 25.height, + ShowFillButton( + title: LocaleKeys.submit.tr(), + onPressed: () async { + if (complaintDescription.isEmpty) { + Utils.showToast(LocaleKeys.descriptionCannotBeEmpty.tr()); + return; + } + final status = await reportComplaintAgainstCustomer( + customerID: customerID, + comment: complaintDescription, + complaintType: complainType, // 1 for Appointment and 2 for Requests + context: context, + ); + + if (status) { + updateComplaintDescription(""); + pop(context); + } + }, + maxWidth: double.infinity, + ), + 19.height, + ], + ), + ), + ); + }, + ); + } + + Future reportComplaintAgainstCustomer({required int customerID, required String comment, required int complaintType, required BuildContext context}) async { + Utils.showLoading(context); + try { + GenericRespModel genericRespModel = await commonRepo.createComplainFromProvider(customerId: customerID, comment: comment, complainType: complaintType); + Utils.showToast(genericRespModel.message.toString()); + Utils.hideLoading(context); + return genericRespModel.messageStatus == 1; + } catch (e, s) { + logger.e(s); + Utils.hideLoading(context); + Utils.showToast(e.toString()); + return false; + } + } + + // BRANCHES RELATED List nearbyBranches = []; List myRecentBranches = []; @@ -937,7 +1104,6 @@ class AppointmentsVM extends BaseVM { Future getBranchServices({required int categoryId}) async { branchSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); isHomeTapped = false; - pickedHomeLocation = ""; pickHomeLocationError = ""; if (categoryId != -1) { isFetchingServices = true; diff --git a/lib/view_models/base_view_model.dart b/lib/view_models/base_view_model.dart index 8386b7a..b91e680 100644 --- a/lib/view_models/base_view_model.dart +++ b/lib/view_models/base_view_model.dart @@ -1,6 +1,5 @@ import 'package:flutter/cupertino.dart'; -import 'package:mc_common_app/classes/app_state.dart'; -import 'package:mc_common_app/main.dart'; +import 'package:flutter/material.dart'; import 'package:mc_common_app/utils/enums.dart'; class BaseVM extends ChangeNotifier { diff --git a/lib/view_models/chat_view_model.dart b/lib/view_models/chat_view_model.dart index 822e88c..f558cf5 100644 --- a/lib/view_models/chat_view_model.dart +++ b/lib/view_models/chat_view_model.dart @@ -19,12 +19,13 @@ import 'package:mc_common_app/services/common_services.dart'; import 'package:mc_common_app/utils/enums.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/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 ChangeNotifier { +class ChatVM extends BaseVM { final ChatRepo chatRepo; final RequestRepo requestRepo; final CommonAppServices commonServices; @@ -75,34 +76,48 @@ class ChatVM extends ChangeNotifier { } } + List indexesForCancelSpecialCarOffer = [0, 1, 6, 7]; + List indexesForCancelSparePartOffer = [2, 7]; + List indexesForRejectOffer = [3, 4, 5, 7]; + List offerRejectModelList = [ OfferRequestCommentModel( index: 0, isSelected: true, - title: LocaleKeys.itemNoLongerAvailable.tr(), + title: LocaleKeys.dealOutsideApp.tr(), ), OfferRequestCommentModel( index: 1, + isSelected: false, + title: LocaleKeys.noAgreementCustomer.tr(), + ), + OfferRequestCommentModel( + index: 2, + isSelected: false, + title: LocaleKeys.itemNoLongerAvailable.tr(), + ), + OfferRequestCommentModel( + index: 3, isSelected: true, title: LocaleKeys.changedMind.tr(), ), OfferRequestCommentModel( - index: 2, + index: 4, isSelected: false, title: LocaleKeys.veryHighPrice.tr(), ), OfferRequestCommentModel( - index: 3, + index: 5, isSelected: false, title: LocaleKeys.alreadySold.tr(), ), OfferRequestCommentModel( - index: 4, + index: 6, isSelected: false, title: LocaleKeys.customerNotResponding.tr(), ), OfferRequestCommentModel( - index: 5, + index: 7, isSelected: false, title: LocaleKeys.otherVar.tr(), ), @@ -154,7 +169,8 @@ class ChatVM extends ChangeNotifier { Future onNewMessageReceivedForRequestOffer({required List messages, bool isMyOwnOffer = false, required RequestsVM requestsVM}) async { if (AppState().currentAppType == AppType.customer) { for (var currentMessage in messages) { - log("currentMessage: ${currentMessage.reqOffer!.reqOfferImages!.first.imageUrl}"); + logger.i(currentMessage); + int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == currentMessage.senderUserID); if (providerIndex != -1) { if (currentMessage.chatMessageTypeEnum == ChatMessageTypeEnum.offer) { @@ -197,17 +213,17 @@ class ChatVM extends ChangeNotifier { notifyListeners(); } - void subscribeToReceiveRequestOfferMessages(BuildContext context) { + void _subscribeToReceiveRequestOfferMessages(BuildContext context) { hubConnection!.on(SignalrConsts.receiveMessageRequestOffer, (List? arguments) { + logger.i(arguments); + if (arguments == null || arguments.isEmpty) return; List chat = []; for (var message in arguments) { final chatMessage = ChatMessageModel.fromJson(message as Map, isForReqOfferImagesURLs: true); - log("chatMessagechatMessage: ${chatMessage.reqOffer!.reqOfferImages!.first.imageUrl}"); chat.add(chatMessage); } onNewMessageReceivedForRequestOffer(messages: chat, requestsVM: context.read()); - logger.i(arguments); // Utils.showToast(arguments.toString()); }); } @@ -227,22 +243,67 @@ class ChatVM extends ChangeNotifier { return status; } + int _retryCount = 0; + final int _maxRetries = 5; // Maximum number of retries + bool _isReconnecting = false; + + Future _reconnectWithBackoff(BuildContext context) async { + if (_retryCount >= _maxRetries) { + logger.e("Maximum retry attempts reached. Stopping reconnection."); + return; + } + + final delay = Duration(seconds: 2 * (_retryCount + 1)); // Exponential backoff + logger.i("Retrying connection in ${delay.inSeconds} seconds..."); + await Future.delayed(delay); + + _retryCount++; + await buildHubConnection(context); // Try reconnecting + } + Future buildHubConnection(BuildContext context) async { if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { + if (_isReconnecting) { + logger.i("Reconnection already in progress"); + return; + } + + if (_isReconnecting) { + logger.i("Reconnection already in progress"); + return; + } + try { + _isReconnecting = true; + hubConnection = await chatRepo.getHubConnection(); await hubConnection!.start(); - subscribeToReceiveRequestOfferMessages(context); - subscribeToReceiveAdMessages(context); - subscribeToReceiveGeneralMessages(context); - hubConnection!.onclose((exception) { + + // Reset retry count on successful connection + _retryCount = 0; + _isReconnecting = false; + + // Subscribe to messages + _subscribeToReceiveRequestOfferMessages(context); + _subscribeToReceiveAdMessages(context); + _subscribeToReceiveGeneralMessages(context); + + // Configure handlers + hubConnection!.onclose((exception) async { logger.i("onClose: ${exception.toString()}"); - buildHubConnection(context); + await _reconnectWithBackoff(context); + }); + + hubConnection!.onreconnecting((exception) { + logger.i("onReconnecting: ${exception?.toString()}"); + }); + + hubConnection!.onreconnected((connectionId) { + logger.i("onReconnected: ${connectionId.toString()}"); }); - hubConnection!.onreconnecting((exception) => () => logger.i("onReconnecting: ${exception.toString()}")); - hubConnection!.onreconnected((connectionId) => () => logger.i("onReconnected: ${connectionId.toString()}")); } catch (e) { - logger.i("Error: ${e.toString()}"); + logger.e("Error during hub connection setup: ${e.toString()}"); + _isReconnecting = false; // Reset on failure } notifyListeners(); @@ -251,6 +312,12 @@ class ChatVM extends ChangeNotifier { } } + Future closeHubConnection() async { + if (hubConnection != null && hubConnection!.state == HubConnectionState.connected) { + hubConnection!.stop(); + } + } + Future onOfferSendForRequest({ required String receiverId, required ChatMessageTypeEnum chatMessageType, @@ -424,18 +491,24 @@ class ChatVM extends ChangeNotifier { List serviceProviderOffersList = []; - Future getOffersFromProvidersByRequest({required int requestId, required BuildContext context}) async { + Future getOffersFromProvidersByRequest({required int requestId, bool isNeedLoading = true}) async { try { - Utils.showLoading(context); + if (isNeedLoading) { + setState(ViewState.busy); + } ProviderOffersModel providerOffersModel = await requestRepo.getOffersFromProvidersByRequest(requestId: requestId); - Utils.hideLoading(context); + if (isNeedLoading) { + setState(ViewState.idle); + } serviceProviderOffersList.clear(); serviceProviderOffersList = providerOffersModel.serviceProviders ?? []; notifyListeners(); } catch (e) { logger.i(e.toString()); Utils.showToast(e.toString()); - Utils.hideLoading(context); + if (isNeedLoading) { + setState(ViewState.idle); + } } } @@ -611,7 +684,7 @@ class ChatVM extends ChangeNotifier { } } - void subscribeToReceiveAdMessages(BuildContext context) { + void _subscribeToReceiveAdMessages(BuildContext context) { hubConnection!.on(SignalrConsts.receiveMessageAds, (List? arguments) { if (arguments == null || arguments.isEmpty) return; List chat = []; @@ -727,7 +800,7 @@ class ChatVM extends ChangeNotifier { notifyListeners(); } - void subscribeToReceiveGeneralMessages(BuildContext context) { + void _subscribeToReceiveGeneralMessages(BuildContext context) { hubConnection!.on(SignalrConsts.receiveMessageGeneral, (List? arguments) { if (arguments == null || arguments.isEmpty) return; List chat = []; diff --git a/lib/view_models/dashboard_view_model_provider.dart b/lib/view_models/dashboard_view_model_provider.dart index 8d4e0ad..afc0755 100644 --- a/lib/view_models/dashboard_view_model_provider.dart +++ b/lib/view_models/dashboard_view_model_provider.dart @@ -82,6 +82,7 @@ class DashboardVMProvider extends BaseVM { appointmentVM.populateAppointmentsFilterList(); shippingManagementVM.populateShippingRequestFilterList(); await serviceVM.getBranchAndServices(); + await serviceVM.populateBranchServiceFilters(); await appointmentVM.getMyAppointmentsForProvider({"ServiceProviderID": injector.get().getUser.data?.userInfo?.providerId.toString() ?? "0"}); adVM.populateAdsFilterList(); await subscriptionsVM.getSubscriptionBySP(AppState().getUser.data?.userInfo?.providerId.toString() ?? "", true); diff --git a/lib/view_models/payment_view_model.dart b/lib/view_models/payment_view_model.dart index 9201598..427fcce 100644 --- a/lib/view_models/payment_view_model.dart +++ b/lib/view_models/payment_view_model.dart @@ -73,7 +73,8 @@ class PaymentVM extends ChangeNotifier { return; } - Future verifyPayments({required BuildContext context, required int id, List? appointmentIds, required int paymentTypeId, required Function() onVerified, bool isForAppointment = false}) async { + Future verifyPayments( + {required BuildContext context, required int id, List? appointmentIds, required int paymentTypeId, required Function() onVerified, bool isForAppointment = false}) async { try { Utils.showLoading(context); bool isPaid = await paymentRepo.verifyPayment( @@ -85,12 +86,13 @@ class PaymentVM extends ChangeNotifier { Utils.hideLoading(context); // TODO: VERIFY THIS WITH ZAHOOR - // if (!isPaid) { - // Utils.showToast(LocaleKeys.paymentFailed.tr()); - // pop(context); - // return; - // } - Utils.showToast(LocaleKeys.paymentSuccessful.tr()); + if (!isPaid) { + // Utils.showToast(LocaleKeys.paymentFailed.tr()); + // pop(context); + return; + } else { + Utils.showToast(LocaleKeys.paymentSuccessful.tr()); + } onVerified(); } catch (e) { Utils.showToast(e.toString()); diff --git a/lib/view_models/requests_view_model.dart b/lib/view_models/requests_view_model.dart index 9b18b93..079cd9a 100644 --- a/lib/view_models/requests_view_model.dart +++ b/lib/view_models/requests_view_model.dart @@ -46,6 +46,7 @@ class RequestsVM extends BaseVM { List myRequestsTypeEnum = []; List vehicleConditionsEnum = []; + List requestStatusesEnum = []; RequestModel? currentSelectedRequest; @@ -86,6 +87,7 @@ class RequestsVM extends BaseVM { Future getRequests({bool isNeedToRebuild = false}) async { if (isNeedToRebuild) setState(ViewState.busy); applyFilterOnRequestsVM(requestsTypeEnum: RequestsTypeEnum.specialCarRequest); + isOfferSent = false; setState(ViewState.idle); } @@ -111,25 +113,63 @@ class RequestsVM extends BaseVM { requestStatusIdApi = requestStatusId.selectedId; } - // try { - myFilteredRequests = await requestRepo.getRequestsBasedOnFilters( - requestTypeId: requestTypeIdApi, - cityId: requestCityIdApi, - conditionId: requestConditionIdApi, - vehicleBrand: brandForSearch, - vehicleModel: modelForSearch, - vehicleYearId: requestVehicleYearIdApi, - requestStatusId: requestStatusIdApi, - requestedDate: requestedDateFilter, - ); - setState(ViewState.idle); - return true; - // } catch (e, s) { - // logger.i("e: ${e.toString()}"); - // Utils.showToast("Need to ask Zahoor Bhai to add params for this API: /api/RequestManagement/Request_ServiceProvider"); - // setState(ViewState.idle); - // return false; - // } + try { + myFilteredRequests = await requestRepo.getRequestsBasedOnFilters( + requestTypeId: requestTypeIdApi, + cityId: requestCityIdApi, + conditionId: requestConditionIdApi, + vehicleBrand: brandForSearch, + vehicleModel: modelForSearch, + vehicleYearId: requestVehicleYearIdApi, + requestStatusId: requestStatusIdApi, + requestedDate: requestedDateFilter, + ); + setState(ViewState.idle); + return true; + } catch (e, s) { + logger.i("e: ${e.toString()}"); + setState(ViewState.idle); + return false; + } + } + + List providersAcceptedRequestsList = []; + List acceptedRequestsTypeFilterOptions = []; + + Future> getProvidersAcceptedRequests() async { + setState(ViewState.busy); + + if (acceptedRequestsTypeFilterOptions.isEmpty) { + for (int i = 0; i < myRequestsTypeEnum.length; i++) { + acceptedRequestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, isSelected: false, id: myRequestsTypeEnum[i].enumValue)); + } + acceptedRequestsTypeFilterOptions[0].isSelected = true; + } + providersAcceptedRequestsList.clear(); + try { + int requestTypeIdApi = acceptedRequestsTypeFilterOptions.firstWhere((element) => element.isSelected).id; + + providersAcceptedRequestsList = await requestRepo.getRequestsBasedOnFilters( + requestTypeId: requestTypeIdApi, + reqOfferStatus: RequestOfferStatusEnum.accepted.getIdFromRequestOfferStatusEnum(), + ); + setState(ViewState.idle); + return providersAcceptedRequestsList; + } catch (e) { + logger.e(e.toString()); + setState(ViewState.idle); + return []; + } + } + + applyFilterOnProvidersAcceptedRequests({required RequestsTypeEnum requestsTypeEnum}) async { + if (acceptedRequestsTypeFilterOptions.isEmpty) return; + for (var value in acceptedRequestsTypeFilterOptions) { + value.isSelected = false; + } + acceptedRequestsTypeFilterOptions[requestsTypeEnum.getIdFromRequestTypeEnum() - 1].isSelected = true; // -1 to match with the index + await getProvidersAcceptedRequests(); + notifyListeners(); } Future> getServiceRequestsForProviders() async { @@ -166,8 +206,9 @@ class RequestsVM extends BaseVM { for (var value in requestsTypeFilterOptions) { value.isSelected = false; } - requestsTypeFilterOptions[requestsTypeEnum.getIdFromRequestTypeStatusEnum() - 1].isSelected = true; // -1 to match with the index + requestsTypeFilterOptions[requestsTypeEnum.getIdFromRequestTypeEnum() - 1].isSelected = true; // -1 to match with the index await getRequestsBasedOnFilters(); + isOfferSent = false; notifyListeners(); } @@ -220,9 +261,10 @@ class RequestsVM extends BaseVM { requestStatusesEnum = await commonRepo.getEnumTypeValues(enumTypeID: AppEnums.requestStatusesFilterEnumId); } - if (requestsTypeFilterOptions.isEmpty) { + if (requestsTypeFilterOptions.isEmpty || acceptedRequestsTypeFilterOptions.isEmpty) { for (int i = 0; i < myRequestsTypeEnum.length; i++) { requestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, isSelected: false, id: myRequestsTypeEnum[i].enumValue)); + acceptedRequestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, isSelected: false, id: myRequestsTypeEnum[i].enumValue)); } } @@ -721,7 +763,8 @@ class RequestsVM extends BaseVM { } } catch (e, s) { Utils.hideLoading(context); - log(s.toString()); + log(e.toString()); + Utils.showToast(e.toString()); } } } @@ -904,6 +947,13 @@ class RequestsVM extends BaseVM { return requestImages; } + bool isOfferSent = false; + + updateIsOfferSent(var value) { + isOfferSent = value; + notifyListeners(); + } + Future onSendOfferPressed({ required BuildContext context, required String receiverId, @@ -946,6 +996,8 @@ class RequestsVM extends BaseVM { return; } + updateIsOfferSent(true); + final senderName = AppState().getUser.data!.userInfo!.firstName; final senderId = AppState().getUser.data!.userInfo!.userId; resetSendOfferBottomSheet(); diff --git a/lib/view_models/service_view_model.dart b/lib/view_models/service_view_model.dart index 0ef3af5..1068494 100644 --- a/lib/view_models/service_view_model.dart +++ b/lib/view_models/service_view_model.dart @@ -6,8 +6,11 @@ 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/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.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'; import 'package:mc_common_app/models/provider_branches_models/branch_detail_model.dart'; import 'package:mc_common_app/models/provider_branches_models/profile/categroy.dart'; import 'package:mc_common_app/models/provider_branches_models/profile/document.dart'; @@ -67,6 +70,8 @@ class ServiceVM extends BaseVM { List homePageBranches = []; List categories = []; + bool isNeedRefreshServicesList = false; + void updateSelectionOpenTime(String date) { openTime = date; notifyListeners(); @@ -174,6 +179,31 @@ class ServiceVM extends BaseVM { notifyListeners(); } + List branchServiceEnums = []; + + populateBranchServiceFilters() async { + if (branchServiceEnums.isEmpty) { + branchServiceEnums = await commonRepo.getEnumTypeValues(enumTypeID: AppEnums.branchServicesEnumId); + } + + branchServicesFilterOptions.clear(); + for (int i = 0; i < branchServiceEnums.length; i++) { + branchServicesFilterOptions.add(FilterListModel(title: branchServiceEnums[i].enumValueStr, isSelected: false, id: branchServiceEnums[i].enumValue)); + } + } + + List branchServicesFilterOptions = []; + + applyFilterOnBranchServices({required ServiceStatusEnum serviceStatusEnum}) async { + if (branchServicesFilterOptions.isEmpty) return; + for (var value in branchServicesFilterOptions) { + value.isSelected = false; + } + + branchServicesFilterOptions[serviceStatusEnum.getIdFromServiceStatusEnum() - 1].isSelected = true; // -1 to match with the index + + } + // Future selectFile(BuildContext context, int index) async { // final status = await AppPermissions.checkStoragePermissions(context); // if (status) { @@ -234,7 +264,11 @@ class ServiceVM extends BaseVM { context, allowMultiple: false, ); - if (files != null && files.any((element) => element.path.split('.').last.toLowerCase() != 'pdf')) { + if (files != null && files.any((element) => + element.path + .split('.') + .last + .toLowerCase() != 'pdf')) { Utils.showToast("Only PDF Files are allowed"); return; } @@ -248,8 +282,8 @@ class ServiceVM extends BaseVM { documentID == 1 ? commerceCertificates.addAll(imageModels) : documentID == 2 - ? commercialCertificates.addAll(imageModels) - : vatCertificates.addAll(imageModels); + ? commercialCertificates.addAll(imageModels) + : vatCertificates.addAll(imageModels); document!.data![index].document = Utils.convertFileToBase64(files.first); document!.data![index].fileExt = Utils.checkFileExt(files.first.path); document!.data![index].documentUrl = files.first.path; @@ -304,6 +338,8 @@ class ServiceVM extends BaseVM { branches = await branchRepo.getBranchAndServices(); if (branches!.data == null) { homePageBranches = []; + } else if (branches!.data!.serviceProviderBranch == null) { + homePageBranches = []; } else { homePageBranches = branches!.data!.serviceProviderBranch!.where((element) => element.branchStatus == BranchStatusEnum.approvedOrActive).toList(); } @@ -423,10 +459,10 @@ class ServiceVM extends BaseVM { DropValue( element.id ?? 0, ((element.categoryName!.isEmpty - ? "N/A" - : countryCode == "SA" - ? element.categoryNameN - : element.categoryName) ?? + ? "N/A" + : countryCode == "SA" + ? element.categoryNameN + : element.categoryName) ?? "N/A"), "", ), @@ -621,7 +657,9 @@ class ServiceVM extends BaseVM { File file = File(imageModel.filePath!); List imageBytes = await file.readAsBytes(); String image = base64Encode(imageBytes); - String fileName = file.path.split('/').last; + String fileName = file.path + .split('/') + .last; branchPostingImages = BranchPostingImages( imageName: fileName, imageStr: image, diff --git a/lib/view_models/shipping_management_view_model.dart b/lib/view_models/shipping_management_view_model.dart index ca5a672..91f541f 100644 --- a/lib/view_models/shipping_management_view_model.dart +++ b/lib/view_models/shipping_management_view_model.dart @@ -42,7 +42,7 @@ class ShippingManagementVM extends BaseVM { } Future populateShippingRequestFilterList() async { - if (shippingRequestFilterOptions.isNotEmpty && shippingRequestStatusesList.isNotEmpty) return; + // if (shippingRequestFilterOptions.isNotEmpty && shippingRequestStatusesList.isNotEmpty) return; shippingStatusEnums = await commonRepo.getEnumTypeValues(enumTypeID: AppEnums.shippingStatusEnumId); shippingRequestFilterOptions.clear(); @@ -51,8 +51,8 @@ class ShippingManagementVM extends BaseVM { shippingRequestFilterOptions.add(FilterListModel(title: shippingStatusEnums[i].enumValueStr, isSelected: false, id: shippingStatusEnums[i].enumValue)); shippingRequestStatusesList.add(FilterListModel(title: shippingStatusEnums[i].enumValueStr, isSelected: false, id: shippingStatusEnums[i].enumValue)); } - - shippingRequestFilterOptions.insert(0, FilterListModel(title: Utils.getNameByShippingRequestStatusEnum(0.toShippingStatusEnum()), isSelected: true, id: 0)); + shippingRequestFilterOptions.insert(0, FilterListModel(title: Utils.getNameByShippingRequestStatusEnum(0.toShippingStatusEnum()), isSelected: false, id: 0)); + shippingRequestFilterOptions.insert(0, FilterListModel(title: Utils.getNameByShippingRequestStatusEnum(ShippingRequestStatusEnum.allRequests), isSelected: true, id: -1)); notifyListeners(); } @@ -66,7 +66,9 @@ class ShippingManagementVM extends BaseVM { notifyListeners(); return; } - shippingRequestFilterOptions[shippingRequestStatusEnum.getIdFromShippingStatusEnum()].isSelected = true; // -1 to match with the index + log("shippingRequestStatusEnum.getIdFromShippingStatusEnum(): ${shippingRequestStatusEnum.getIdFromShippingStatusEnum()}"); + log("shippingRequestFilterOptions: ${shippingRequestFilterOptions[shippingRequestStatusEnum.getIdFromShippingStatusEnum() + 1].title}"); + shippingRequestFilterOptions[shippingRequestStatusEnum.getIdFromShippingStatusEnum() + 1].isSelected = true; // +1 to match with the index 0 index has all requests await getShippingRequestsListByFilters(shippingStatusEnum: shippingRequestStatusEnum); notifyListeners(); } @@ -93,12 +95,12 @@ class ShippingManagementVM extends BaseVM { } Future onUpdateShippingStatusTapped({required BuildContext context, required ShippingRequestStatusEnum shippingStatusEnum, required int shippingRequestId}) async { - Utils.showLoading(context); try { - GenericRespModel? genericRespModel = await shippingRepo.updateShippingRequestStatus(shippingStatusEnum: shippingStatusEnum, shippingRequestId: shippingRequestId); + GenericRespModel? genericRespModel = await shippingRepo.updateShippingRequestStatus(shippingStatusEnum: shippingStatusEnum, shippingRequestId: shippingRequestId, comment: requestStatusComments); Utils.showToast(genericRespModel.message.toString()); Utils.hideLoading(context); + requestStatusComments = ""; return genericRespModel.messageStatus == 1; } catch (e) { logger.i(e.toString()); diff --git a/lib/view_models/user_view_model.dart b/lib/view_models/user_view_model.dart index 4117789..7c32ace 100644 --- a/lib/view_models/user_view_model.dart +++ b/lib/view_models/user_view_model.dart @@ -37,6 +37,7 @@ import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/utils/shared_prefrence.dart'; 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/view_models/service_view_model.dart'; import 'package:mc_common_app/view_models/subscriptions_view_model.dart'; import 'package:mc_common_app/views/location_views/map_selection_widget.dart'; @@ -693,8 +694,10 @@ class UserVM extends BaseVM { print(value.body); print("Logout"); } + SharedPrefManager.setPhoneOrEmail(""); SharedPrefManager.setUserPassword(""); + context.read().closeHubConnection(); if (AppState().getUser.data!.userInfo!.userLocalImage != null) { AppState().getUser.data!.userInfo!.userLocalImage = null; AppState().getUser.data!.userInfo!.userImageUrl = null; diff --git a/lib/views/advertisement/ad_creation_steps/ad_duration_container.dart b/lib/views/advertisement/ad_creation_steps/ad_duration_container.dart index ede81cf..20a17fe 100644 --- a/lib/views/advertisement/ad_creation_steps/ad_duration_container.dart +++ b/lib/views/advertisement/ad_creation_steps/ad_duration_container.dart @@ -1,11 +1,13 @@ import 'package:country_code_picker/country_code_picker.dart'; 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/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.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/views/advertisement/bottom_sheets/ad_special_service_sheet.dart'; @@ -17,7 +19,7 @@ import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; class AdDuration extends StatelessWidget { - const AdDuration({Key? key}) : super(key: key); + const AdDuration({super.key}); void onAddSpecialServiceButtonTapped(BuildContext context, AdVM adVM) { showModalBottomSheet( @@ -60,19 +62,22 @@ class AdDuration extends StatelessWidget { children: [ LocaleKeys.adDuration.tr().toText(fontSize: 18, isBold: true), 8.height, - TxtField( - isBackgroundEnabled: true, - isButtonEnable: false, - isNeedBorder: false, - hint: LocaleKeys.adDuration.tr(), - value: adVM.vehicleAdDurationId.selectedOption, - isNeedClickAll: true, - postFixDataColor: MyColors.darkTextColor, - onTap: () async { - final formattedDate = await Utils.pickDateFromDatePicker(context); - adVM.updateSelectionDurationStartDate(formattedDate); - }, - ), + + if (AppState().userType != UserType.providerDealer) ...[ + TxtField( + isBackgroundEnabled: true, + isButtonEnable: false, + isNeedBorder: false, + hint: LocaleKeys.adDuration.tr(), + value: adVM.vehicleAdDurationId.selectedOption, + isNeedClickAll: true, + postFixDataColor: MyColors.darkTextColor, + onTap: () async { + final formattedDate = await Utils.pickDateFromDatePicker(context); + adVM.updateSelectionDurationStartDate(formattedDate); + }, + ), + ], // Builder(builder: (context) { // List vehicleAdsDurationsDrop = []; // for (var element in adVM.vehicleAdsDurations) { diff --git a/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart b/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart index 01a5af0..af32a90 100644 --- a/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart +++ b/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.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/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/views/advertisement/components/picked_images_container_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; @@ -10,7 +12,7 @@ import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; class ReviewAd extends StatelessWidget { - const ReviewAd({Key? key}) : super(key: key); + const ReviewAd({super.key}); @override Widget build(BuildContext context) { @@ -30,7 +32,7 @@ class SingleDetailWidget extends StatelessWidget { final String text; final String type; - const SingleDetailWidget({Key? key, required this.text, required this.type}) : super(key: key); + const SingleDetailWidget({super.key, required this.text, required this.type}); @override Widget build(BuildContext context) { @@ -45,7 +47,7 @@ class SingleDetailWidget extends StatelessWidget { } class VehicleDetailsReview extends StatelessWidget { - const VehicleDetailsReview({Key? key}) : super(key: key); + const VehicleDetailsReview({super.key}); @override Widget build(BuildContext context) { @@ -127,7 +129,7 @@ class VehicleDetailsReview extends StatelessWidget { } class DamagePartsReview extends StatelessWidget { - const DamagePartsReview({Key? key}) : super(key: key); + const DamagePartsReview({super.key}); Widget buildDamagePartList(AdVM adVM) { List list = List.generate( @@ -138,11 +140,26 @@ class DamagePartsReview extends StatelessWidget { 8.height, Row( children: [ - SingleDetailWidget(type: LocaleKeys.vehiclePart.tr(), text: adVM.vehicleDamageCards[index].partSelectedId!.selectedOption), + ("${LocaleKeys.vehiclePart.tr()}:").toText(fontSize: 12, color: MyColors.lightTextColor), + 4.width, + (adVM.vehicleDamageCards[index].partSelectedId!.selectedOption ?? "").toText(fontSize: 14, color: MyColors.darkTextColor), ], ), + if (adVM.vehicleDamageCards[index].damagePartDescription != null && adVM.vehicleDamageCards[index].damagePartDescription!.isNotEmpty) ...[ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ("${LocaleKeys.comment.tr()}:").toText(fontSize: 12, color: MyColors.lightTextColor), + 4.width, + Flexible( + child: (adVM.vehicleDamageCards[index].damagePartDescription ?? "").toText(fontSize: 12, color: MyColors.darkTextColor), + ), + ], + ), + 5.height, + ], if (adVM.vehicleDamageCards[index].partImages != null && adVM.vehicleDamageCards[index].partImages!.isNotEmpty) ...[ - ("${LocaleKeys.damagePartPictures.tr()}:").toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), + ("${LocaleKeys.damagePartPictures.tr()}:").toText(fontSize: 12, color: MyColors.lightTextColor), PickedFilesContainer( pickedFiles: adVM.vehicleDamageCards[index].partImages!, onCrossPressedPrimary: adVM.removeImageFromList, @@ -176,7 +193,7 @@ class DamagePartsReview extends StatelessWidget { } class AdDurationReview extends StatelessWidget { - const AdDurationReview({Key? key}) : super(key: key); + const AdDurationReview({super.key}); @override Widget build(BuildContext context) { @@ -189,10 +206,12 @@ class AdDurationReview extends StatelessWidget { 8.height, Row( children: [ - Expanded( - flex: 7, - child: SingleDetailWidget(type: LocaleKeys.duration.tr(), text: adVM.vehicleAdDurationId.selectedOption), - ), + if (AppState().userType != UserType.providerDealer) ...[ + Expanded( + flex: 7, + child: SingleDetailWidget(type: LocaleKeys.duration.tr(), text: adVM.vehicleAdDurationId.selectedOption), + ), + ], Expanded( flex: 5, child: SingleDetailWidget(type: LocaleKeys.startDate.tr(), text: adVM.selectionDurationStartDate), diff --git a/lib/views/advertisement/ad_creation_steps/damage_parts_container.dart b/lib/views/advertisement/ad_creation_steps/damage_parts_container.dart index be1b994..4e24abe 100644 --- a/lib/views/advertisement/ad_creation_steps/damage_parts_container.dart +++ b/lib/views/advertisement/ad_creation_steps/damage_parts_container.dart @@ -9,11 +9,12 @@ import 'package:mc_common_app/views/advertisement/bottom_sheets/ad_damage_parts_ import 'package:mc_common_app/views/advertisement/components/picked_images_container_widget.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:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; class DamageParts extends StatelessWidget { - const DamageParts({Key? key}) : super(key: key); + const DamageParts({super.key}); bool getEnabledStatus(bool? isSelected) { if (isSelected == null) { @@ -79,7 +80,14 @@ class DamageParts extends StatelessWidget { ).onPress(() => adVM.removeDamagePartCard(index)) ], ), - 10.height, + 3.height, + TxtField( + isNeedBorder: false, + value: vehicleDamageCard.damagePartDescription, + hint: LocaleKeys.addCommentsHere.tr(), + onChanged: (v) => adVM.updateDamagePartDescription(index, v), + ), + 3.height, // Builder(builder: (context) { // List vehicleDamagePartsDrop = []; diff --git a/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart b/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart index 51f1b7e..bfbf338 100644 --- a/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart +++ b/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart @@ -1,11 +1,16 @@ +import 'dart:developer'; + 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/advertisment_models/vehicle_details_models.dart'; import 'package:mc_common_app/models/general_models/widgets_models.dart'; import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/views/advertisement/ad_creation_steps/ad_creation_steps_containers.dart'; import 'package:mc_common_app/views/advertisement/components/picked_images_container_widget.dart'; @@ -26,19 +31,21 @@ class VehicleDetails extends StatelessWidget { children: [ LocaleKeys.vehicleDetail.tr().toText(fontSize: 18, isBold: true), 8.height, - Builder(builder: (context) { - List vehicleBrandsDrop = []; - for (var element in adVM.vehicleBrands) { - vehicleBrandsDrop.add(DropValue(element.id?.toInt() ?? 0, element.vehicleBrandDescription ?? "", "")); - } - return DropdownField( - (DropValue value) => adVM.updateSelectionVehicleBrandId(SelectionModel(selectedId: value.id, selectedOption: value.value)), - list: vehicleBrandsDrop, - dropdownValue: adVM.vehicleBrandId.selectedId != -1 ? DropValue(adVM.vehicleBrandId.selectedId, adVM.vehicleBrandId.selectedOption, "") : null, - hint: LocaleKeys.vehicleBrand.tr(), - errorValue: adVM.vehicleBrandId.errorValue, - ); - }), + Builder( + builder: (context) { + List vehicleBrandsDrop = []; + for (var element in adVM.vehicleBrands) { + vehicleBrandsDrop.add(DropValue(element.id?.toInt() ?? 0, element.vehicleBrandDescription ?? "", "")); + } + return DropdownField( + (DropValue value) => adVM.updateSelectionVehicleBrandId(SelectionModel(selectedId: value.id, selectedOption: value.value)), + list: vehicleBrandsDrop, + dropdownValue: adVM.vehicleBrandId.selectedId != -1 ? DropValue(adVM.vehicleBrandId.selectedId, adVM.vehicleBrandId.selectedOption, "") : null, + hint: LocaleKeys.vehicleBrand.tr(), + errorValue: adVM.vehicleBrandId.errorValue, + ); + }, + ), if (adVM.vehicleBrandId.selectedId != -1) ...[ if (adVM.isFetchingLists) ...[ const Row( @@ -49,6 +56,32 @@ class VehicleDetails extends StatelessWidget { ).paddingAll(10), ] else ...[ 8.height, + if (AppState().currentAppType == AppType.provider) ...[ + Builder(builder: (context) { + List vehicleSellerTypesDrop = []; + for (var element in adVM.vehicleSellerTypes) { + vehicleSellerTypesDrop.add(DropValue(element.id?.toInt() ?? 0, element.sellerType ?? "", "")); + } + + DropValue? model; + if (AppState().userType == UserType.providerDealer) { + model = vehicleSellerTypesDrop.firstWhere((element) => element.id == 2); + } else { + model = vehicleSellerTypesDrop.firstWhere((element) => element.id == 1); + } + adVM.updateSelectionVehicleSellerTypeId(SelectionModel(selectedId: model.id, selectedOption: model.value), needRefresh: false); + + return DropdownField( + (DropValue value) => adVM.updateSelectionVehicleSellerTypeId(SelectionModel(selectedId: value.id, selectedOption: value.value)), + isSelectAble: false, + list: vehicleSellerTypesDrop, + dropdownValue: adVM.vehicleSellerTypeId.selectedId != -1 ? DropValue(adVM.vehicleSellerTypeId.selectedId, adVM.vehicleSellerTypeId.selectedOption, "") : null, + hint: LocaleKeys.vehicleSellerType.tr(), + errorValue: adVM.vehicleSellerTypeId.errorValue, + ); + }), + 8.height, + ], Builder(builder: (context) { List vehicleModelsDrop = []; for (var element in adVM.vehicleModels) { @@ -148,20 +181,7 @@ class VehicleDetails extends StatelessWidget { ); }), 8.height, - Builder(builder: (context) { - List vehicleSellerTypesDrop = []; - for (var element in adVM.vehicleSellerTypes) { - vehicleSellerTypesDrop.add(DropValue(element.id?.toInt() ?? 0, element.sellerType ?? "", "")); - } - return DropdownField( - (DropValue value) => adVM.updateSelectionVehicleSellerTypeId(SelectionModel(selectedId: value.id, selectedOption: value.value)), - list: vehicleSellerTypesDrop, - dropdownValue: adVM.vehicleSellerTypeId.selectedId != -1 ? DropValue(adVM.vehicleSellerTypeId.selectedId, adVM.vehicleSellerTypeId.selectedOption, "") : null, - hint: LocaleKeys.vehicleSellerType.tr(), - errorValue: adVM.vehicleSellerTypeId.errorValue, - ); - }), - 8.height, + Builder(builder: (context) { List vehicleCountriesDrop = []; for (var element in adVM.vehicleCountries) { @@ -203,6 +223,7 @@ class VehicleDetails extends StatelessWidget { value: adVM.vehicleDemandAmount, errorValue: adVM.demandAmountError, keyboardType: TextInputType.number, + numbersOnly: true, hint: LocaleKeys.demandAmount.tr(), onChanged: (v) => adVM.updateVehicleDemandAmount(v), ), diff --git a/lib/views/advertisement/ads_detail_view/ads_detail_view.dart b/lib/views/advertisement/ads_detail_view/ads_detail_view.dart index a3ae474..99afa00 100644 --- a/lib/views/advertisement/ads_detail_view/ads_detail_view.dart +++ b/lib/views/advertisement/ads_detail_view/ads_detail_view.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:developer'; import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; @@ -28,7 +29,7 @@ import 'package:easy_localization/easy_localization.dart'; class AdsDetailView extends StatefulWidget { final AdDetailsModel adDetails; - const AdsDetailView({Key? key, required this.adDetails}) : super(key: key); + const AdsDetailView({super.key, required this.adDetails}); @override State createState() => _AdsDetailViewState(); @@ -130,6 +131,12 @@ class _AdsDetailViewState extends State { "${widget.adDetails.vehicle!.transmission!.label}".toText(fontSize: 12), ], ), + Row( + children: [ + ("${LocaleKeys.financeAvailable.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), + ((widget.adDetails.vehicle!.isFinanceAvailable ?? false) ? LocaleKeys.yes.tr() : LocaleKeys.no.tr()).toText(fontSize: 12), + ], + ), 8.height, ("${LocaleKeys.description.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), "${widget.adDetails.vehicle!.vehicleDescription}".toText(fontSize: 14), @@ -140,7 +147,7 @@ class _AdsDetailViewState extends State { crossAxisAlignment: CrossAxisAlignment.end, children: [ widget.adDetails.vehicle!.demandAmount!.toInt().toString().toText(fontSize: 30, height: 1.2, isBold: true), - LocaleKeys.sar.tr().toText(fontSize: 15, color: MyColors.lightTextColor).paddingOnly(bottom: 5), + (widget.adDetails.vehicle!.currency ?? LocaleKeys.sar.tr()).toText(fontSize: 15, color: MyColors.lightTextColor).paddingOnly(bottom: 5), ], ), if (widget.adDetails.adPostStatus == AdPostStatus.expired) ...[ @@ -158,6 +165,43 @@ class _AdsDetailViewState extends State { ).toWhiteContainer(width: double.infinity, allPading: 12); } + 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)), + ], + ); + } + + Widget buildPersonalInformationCard({required BuildContext context}) { + if (widget.adDetails.adOwnerDetails == null) { + return const SizedBox(); + } + if ((widget.adDetails.adOwnerDetails!.name == null || widget.adDetails.adOwnerDetails!.name!.isEmpty) && + (widget.adDetails.adOwnerDetails!.mobileNo == null || widget.adDetails.adOwnerDetails!.mobileNo!.isEmpty) && + (widget.adDetails.adOwnerDetails!.email == null || widget.adDetails.adOwnerDetails!.email!.isEmpty)) { + return const SizedBox(); + } + + String name = widget.adDetails.adOwnerDetails!.name ?? ""; + String phone = widget.adDetails.adOwnerDetails!.mobileNo ?? ""; + String email = widget.adDetails.adOwnerDetails!.email ?? ""; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + (LocaleKeys.ownerInformation.tr()).toText(fontSize: 16), + name.isNotEmpty ? showItem(LocaleKeys.name.tr(), "$name ") : const SizedBox(), + phone.isNotEmpty ? showItem(LocaleKeys.phoneNumber.tr(), "$phone ") : const SizedBox(), + email.isNotEmpty ? showItem(LocaleKeys.emailAddress.tr(), "$email ") : const SizedBox(), + ], + ).toWhiteContainer(width: double.infinity, allPading: 12); + } + Widget buildBankDetailsIfAvailable() { return Consumer(builder: (BuildContext context, AdVM adVM, Widget? child) { if (adVM.adsBankDetailsModel != null) { @@ -294,36 +338,35 @@ class _AdsDetailViewState extends State { } Widget buildAdStartEndDates() { + String startDate = ""; + String endDate = ""; + + if (widget.adDetails.startdate != null) { + startDate = "${DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.startdate!))} at ${DateHelper.formatAsTime(DateTime.parse(widget.adDetails.startdate!))}"; + } + if (widget.adDetails.enddate != null) { + endDate = "${DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.enddate!))} at ${DateHelper.formatAsTime(DateTime.parse(widget.adDetails.enddate!))}"; + } return Column( children: [ + Row( + children: [ + ("${LocaleKeys.startDate.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), + startDate.isNotEmpty ? startDate.toText(fontSize: 10) : const SizedBox(), + ], + ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - ("${LocaleKeys.startDate.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), - widget.adDetails.startdate != null - ? DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.startdate!)).toText( - fontSize: 10, - ) - : const SizedBox(), - ], - ), Row( children: [ ("${LocaleKeys.endDate.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), - widget.adDetails.enddate != null ? DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.enddate!)).toText(fontSize: 10) : const SizedBox(), + endDate.isNotEmpty ? endDate.toText(fontSize: 10) : const SizedBox(), ], ), - ], - ), - 3.height, - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ (LocaleKeys.extendAd.tr()).toText(fontSize: 12, color: MyColors.darkPrimaryColor, isUnderLine: true).onPress(() async => await onExtendAdPressed()), ], - ) + ), ], ).paddingOnly(top: 5, bottom: 2).toWhiteContainer(width: double.infinity, allPading: 12); } @@ -331,7 +374,10 @@ class _AdsDetailViewState extends State { @override Widget build(BuildContext context) { Widget actionWidget = const SizedBox(); - if ((widget.adDetails.isMyAd ?? false) && (widget.adDetails.adPostStatus != AdPostStatus.reserved) && (widget.adDetails.adPostStatus != AdPostStatus.active) && (widget.adDetails.adPostStatus != AdPostStatus.pendingForPost)) { + if ((widget.adDetails.isMyAd ?? false) && + (widget.adDetails.adPostStatus != AdPostStatus.reserved) && + (widget.adDetails.adPostStatus != AdPostStatus.active) && + (widget.adDetails.adPostStatus != AdPostStatus.pendingForPost)) { actionWidget = IconButton( icon: const Icon(Icons.delete_outline, color: MyColors.redColor), onPressed: () { @@ -351,7 +397,6 @@ class _AdsDetailViewState extends State { actionWidget.toContainer( margin: const EdgeInsets.fromLTRB(0, 8, 21, 8), paddingAll: 0, - padding: const EdgeInsets.only(right: 21), borderRadius: 100, borderColor: MyColors.lightGreyEFColor, isEnabledBorder: true, @@ -375,6 +420,10 @@ class _AdsDetailViewState extends State { if (widget.adDetails.vehicle!.damagereport != null && widget.adDetails.vehicle!.damagereport!.isNotEmpty) ...[ buildDamagePartDetails(), ], + if (!(widget.adDetails.isMyAd ?? false)) ...[ + 12.height, + buildPersonalInformationCard(context: context), + ], if (widget.adDetails.isMyAd ?? false) ...[ 12.height, buildAdStartEndDates(), @@ -397,7 +446,7 @@ class _AdsDetailViewState extends State { crossAxisAlignment: CrossAxisAlignment.end, children: [ widget.adDetails.vehicle!.demandAmount!.toInt().toString().toText(fontSize: 30, isBold: true), - LocaleKeys.sar.tr().toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor).paddingOnly(bottom: 5), + (widget.adDetails.vehicle!.currency ?? LocaleKeys.sar.tr()).toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor).paddingOnly(bottom: 5), ], ), 14.height, diff --git a/lib/views/advertisement/ads_detail_view/components.dart b/lib/views/advertisement/ads_detail_view/components.dart index 0d580b9..a2eddec 100644 --- a/lib/views/advertisement/ads_detail_view/components.dart +++ b/lib/views/advertisement/ads_detail_view/components.dart @@ -1,5 +1,6 @@ import 'dart:async'; 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/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; @@ -32,7 +33,7 @@ import 'package:easy_localization/easy_localization.dart'; class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { final AdDetailsModel adDetailsModel; - const BuildAdDetailsActionButtonForExploreAds({Key? key, required this.adDetailsModel}) : super(key: key); + const BuildAdDetailsActionButtonForExploreAds({super.key, required this.adDetailsModel}); void reserveAdPriceBreakDownClicked(BuildContext context, AdDetailsModel adDetailsModel) { showModalBottomSheet( @@ -238,7 +239,13 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { ), if (adDetailsModel.whatsAppNo != null && adDetailsModel.whatsAppNo!.isNotEmpty) ...[ 8.width, - Container(height: 55, width: 55, alignment: Alignment.center, decoration: BoxDecoration(border: Border.all(color: MyColors.black, width: 2)), child: MyAssets.whatsAppIcon.buildSvg(height: 33, width: 35)).onPress(() { + Container( + height: 55, + width: 55, + alignment: Alignment.center, + decoration: BoxDecoration(border: Border.all(color: MyColors.black, width: 2)), + child: MyAssets.whatsAppIcon.buildSvg(height: 33, width: 35)) + .onPress(() { Utils.openNumberViaWhatsApp(phoneNumber: adDetailsModel.whatsAppNo ?? ""); }), ], @@ -264,12 +271,12 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { final AdDetailsModel adDetailsModel; - const BuildAdDetailsActionButtonForMyAds({Key? key, required this.adDetailsModel}) : super(key: key); + const BuildAdDetailsActionButtonForMyAds({super.key, required this.adDetailsModel}); void onBookPhotographyServiceClicked(BuildContext context, {required AdDetailsModel adDetailsModel}) async { AdVM adVM = context.read(); if (adVM.photoOfficeSelectedId.selectedId == -1) { - adVM.getPhotographyServiceScheduleListByOffices(latitude: 46.703430, longitude: 24.625720, isNeedToRebuild: true); // TODO: These Lat Long need to be dynamic + adVM.getPhotographyServiceScheduleListByOffices(latitude: AppState().currentLocation.latitude, longitude: AppState().currentLocation.longitude, isNeedToRebuild: true); } return showModalBottomSheet( @@ -582,7 +589,7 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { return actionConfirmationBottomSheet( context: context, title: LocaleKeys.markAsSold.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), - subtitle: LocaleKeys.markAsSold.tr(), + subtitle: LocaleKeys.markAdAsSoldDesc.tr(), actionButtonYes: Expanded( child: ShowFillButton( maxHeight: 55, @@ -655,15 +662,9 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { shrinkWrap: true, itemCount: adVM.deActivateAdModelList.length, separatorBuilder: (BuildContext context, int index) { - if (adVM.deActivateAdModelList[index].index == 3) { - return const SizedBox(); - } return const Divider(thickness: 0.5); }, itemBuilder: (BuildContext context, int index) { - if (adVM.deActivateAdModelList[index].index == 3) { - return const SizedBox(); - } OfferRequestCommentModel offerRequestCommentModel = adVM.deActivateAdModelList[index]; return CircleCheckBoxWithTitle( isChecked: offerRequestCommentModel.isSelected ?? false, @@ -948,7 +949,7 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { child: ShowFillButton( fontSize: 16, maxHeight: 55, - title: LocaleKeys.extendAd.tr(), + title: LocaleKeys.reactivateAd.tr(), onPressed: () { final AdVM adVM = context.read(); return actionConfirmationBottomSheet( @@ -1046,6 +1047,7 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { case AdPostStatus.cancelled: return pendingForReviewAction(pendingText: LocaleKeys.cancelledByOwner.tr()); case AdPostStatus.pendingForPost: + case AdPostStatus.pendingForActive: return pendingForReviewAction(pendingText: LocaleKeys.waitingAdminPost.tr()); case AdPostStatus.pendingForReview: @@ -1054,6 +1056,9 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { case AdPostStatus.sold: return pendingForReviewAction(pendingText: LocaleKeys.sold.tr()); + case AdPostStatus.deActive: + return pendingForReviewAction(pendingText: LocaleKeys.deactivateAd.tr()); + case AdPostStatus.expired: return expiredAdAction(context); case AdPostStatus.allAds: diff --git a/lib/views/advertisement/ads_filter_view.dart b/lib/views/advertisement/ads_filter_view.dart index 1c3ae8a..705ccc0 100644 --- a/lib/views/advertisement/ads_filter_view.dart +++ b/lib/views/advertisement/ads_filter_view.dart @@ -9,12 +9,14 @@ import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/models/general_models/widgets_models.dart'; import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/utils/enums.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/widgets/button/show_fill_button.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/common_widgets/search_entity_widget.dart'; import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -134,22 +136,45 @@ class _AdsFilterViewState extends State { ), const Divider(thickness: 1.2).paddingOnly(top: 7, bottom: 7), SearchEntityWidget( - title: LocaleKeys.searchByAdOwner.tr(), - actionWidget: Builder(builder: (context) { - List vehicleOwnerDrop = []; - for (var element in adVM.exploreAdsEnums) { - vehicleOwnerDrop.add(DropValue(element.enumValue.toInt(), "${element.enumValueStr} Ads", "")); - } - return DropdownField( - (DropValue value) => adVM.updateSelectionVehicleAdOwnerId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true), - list: vehicleOwnerDrop, - dropdownValue: adVM.vehicleOwnerId.selectedId != -1 ? DropValue(adVM.vehicleOwnerId.selectedId, adVM.vehicleOwnerId.selectedOption, "") : null, - hint: LocaleKeys.selectOwner.tr(), - errorValue: adVM.vehicleOwnerId.errorValue, - ); - }), - historyContent: adVM.vehicleAdOwnerSearchHistory, - onHistoryItemDeleted: (index) => adVM.removeVehicleAdOwnerSearchHistory(index: index), + title: LocaleKeys.searchByCondition.tr(), + actionWidget: Builder( + builder: (context) { + List vehicleOwnerDrop = []; + for (var element in adVM.vehicleConditionsEnum) { + vehicleOwnerDrop.add(DropValue(element.id.toInt(), element.enumValueStr, "")); + } + return DropdownField( + (DropValue value) => + adVM.updateSelectionVehicleConditionId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true, showSelectionInDropdown: false), + list: vehicleOwnerDrop, + dropdownValue: adVM.vehicleConditionId.selectedId != -1 ? DropValue(adVM.vehicleConditionId.selectedId, adVM.vehicleConditionId.selectedOption, "") : null, + hint: LocaleKeys.selectCondition.tr(), + errorValue: adVM.vehicleConditionId.errorValue, + ); + }, + ), + historyContent: adVM.vehicleAdConditionSearchHistory, + onHistoryItemDeleted: (index) => adVM.removeVehicleAdConditionSearchHistory(index: index), + onHistoryItemTapped: (DropValue value) => null, + ), + const Divider(thickness: 1.2).paddingOnly(top: 7, bottom: 7), + SearchEntityWidget( + title: LocaleKeys.searchByCreatedDate.tr(), + actionWidget: TxtField( + isNeedBorder: true, + hint: LocaleKeys.createdOn.tr(), + value: adVM.vehicleAdDurationId.selectedOption, + isNeedClickAll: true, + postFixDataColor: MyColors.darkTextColor, + onTap: () async { + final formattedDate = await Utils.pickDateFromDatePicker(context); + adVM.updateSelectionDurationStartDate(formattedDate); + DropValue value = DropValue(0, formattedDate, ""); + adVM.addToVehicleAdCreatedDateSearchHistory(value: value); + }, + ), + historyContent: adVM.vehicleAdCreatedDateSearchHistory, + onHistoryItemDeleted: (index) => adVM.removeVehicleAdCreatedDateSearchHistory(index: index), onHistoryItemTapped: (DropValue value) => null, ) ], diff --git a/lib/views/advertisement/bottom_sheets/ad_damage_parts_selection_sheet.dart b/lib/views/advertisement/bottom_sheets/ad_damage_parts_selection_sheet.dart index 6703a47..f1ee3b1 100644 --- a/lib/views/advertisement/bottom_sheets/ad_damage_parts_selection_sheet.dart +++ b/lib/views/advertisement/bottom_sheets/ad_damage_parts_selection_sheet.dart @@ -125,6 +125,7 @@ class _AdDamagePartsSelectionSheetState extends State createState() => _BottomSheetAdDamagePartsContentState(); @@ -129,6 +129,7 @@ class _BottomSheetAdDamagePartsContentState extends State { selectedId: element.vehicleDamagePartID!, selectedOption: element.partName ?? "", ), + damagePartDescription: element.comment ?? "", partImages: [imageModel], ); vehicleDamageCards.add(vehicleDamageCard); @@ -62,6 +65,9 @@ class _AdDamagePartPicturesSheetState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ vehicleDamageCard.partSelectedId!.selectedOption.toText(fontSize: 18, color: MyColors.darkTextColor), + if (vehicleDamageCard.damagePartDescription!.isNotEmpty) ...[ + vehicleDamageCard.damagePartDescription!.toText(fontSize: 14, color: MyColors.lightTextColor), + ], PickedFilesContainer( pickedFiles: vehicleDamageCard.partImages ?? [], isReview: true, @@ -78,7 +84,6 @@ class _AdDamagePartPicturesSheetState extends State { id: image.id, isFromNetwork: true, imageUrl: image.filePath, - )); } navigateWithName(context, AppRoutes.mediaViewerScreen, arguments: images); diff --git a/lib/views/advertisement/components/ads_images_corousel_widget.dart b/lib/views/advertisement/components/ads_images_corousel_widget.dart index 52ad8a7..7b4de92 100644 --- a/lib/views/advertisement/components/ads_images_corousel_widget.dart +++ b/lib/views/advertisement/components/ads_images_corousel_widget.dart @@ -1,8 +1,13 @@ import 'package:carousel_slider/carousel_slider.dart'; +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/string_extensions.dart'; +import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/models/advertisment_models/ad_details_model.dart'; +import 'package:mc_common_app/models/chat_models/chat_message_model.dart'; import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:sizer/sizer.dart'; @@ -17,7 +22,7 @@ class ImagesCorouselWidget extends StatefulWidget { class _CarouselWithIndicatorState extends State { int _current = 0; - CarouselSliderController _controller = CarouselSliderController(); + final CarouselSliderController _controller = CarouselSliderController(); @override Widget build(BuildContext context) { @@ -28,11 +33,11 @@ class _CarouselWithIndicatorState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - "No Images to Show".toText( - fontSize: 12, - color: MyColors.lightTextColor, - isBold: true, - ), + LocaleKeys.noImagesToShow.tr().toText( + fontSize: 12, + color: MyColors.lightTextColor, + isBold: true, + ), ], ), ), @@ -40,20 +45,33 @@ class _CarouselWithIndicatorState extends State { } return SizedBox( height: 26.h, - child: ListView(shrinkWrap: true, children: [ - CarouselSlider( - items: widget.imagesList - .map((item) => - Container( - margin: const EdgeInsets.all(5.0), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(5.0)), - child: item.imageUrl.buildNetworkImage(), - ), - )) - .toList(), - carouselController: _controller, - options: CarouselOptions( + child: ListView( + shrinkWrap: true, + children: [ + CarouselSlider( + items: widget.imagesList + .map( + (item) => Container( + margin: const EdgeInsets.all(5.0), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(5.0)), + child: item.imageUrl.buildNetworkImage(), + ), + ).onPress(() { + List images = []; + for (var image in widget.imagesList) { + images.add(MessageImageModel( + id: image.id, + isFromNetwork: true, + imageUrl: image.imageUrl, + )); + } + navigateWithName(context, AppRoutes.mediaViewerScreen, arguments: images); + }), + ) + .toList(), + carouselController: _controller, + options: CarouselOptions( autoPlay: false, enlargeCenterPage: false, aspectRatio: 1.8, @@ -61,29 +79,28 @@ class _CarouselWithIndicatorState extends State { setState(() { _current = index; }); - }), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: widget.imagesList - .asMap() - .entries - .map((entry) { - return GestureDetector( - onTap: () => _controller.animateToPage(entry.key), - child: Container( - width: 12.0, - height: 12.0, - margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _current == entry.key ? MyColors.darkPrimaryColor : MyColors.lightTextColor.withOpacity(0.5), + }, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: widget.imagesList.asMap().entries.map((entry) { + return GestureDetector( + onTap: () => _controller.animateToPage(entry.key), + child: Container( + width: 12.0, + height: 12.0, + margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _current == entry.key ? MyColors.darkPrimaryColor : MyColors.lightTextColor.withOpacity(0.5), + ), ), - ), - ); - }).toList(), - ), - ]), + ); + }).toList(), + ), + ], + ), ); } } diff --git a/lib/views/advertisement/create_ad_view.dart b/lib/views/advertisement/create_ad_view.dart index 26e7da6..ef23f5e 100644 --- a/lib/views/advertisement/create_ad_view.dart +++ b/lib/views/advertisement/create_ad_view.dart @@ -17,7 +17,7 @@ import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; class CreateAdView extends StatefulWidget { - const CreateAdView({Key? key}) : super(key: key); + const CreateAdView({super.key}); @override State createState() => _CreateAdViewState(); @@ -68,7 +68,7 @@ class _CreateAdViewState extends State { } class BuildFooterButton extends StatelessWidget { - const BuildFooterButton({Key? key}) : super(key: key); + const BuildFooterButton({super.key}); @override Widget build(BuildContext context) { @@ -187,13 +187,13 @@ class BuildAdStepContainer extends StatelessWidget { final AdCreationSteps adCreationStepsEnum; const BuildAdStepContainer({ - Key? key, + super.key, required this.onVehicleDetails, required this.onDamageParts, required this.onAdDuration, required this.onReviewAd, required this.adCreationStepsEnum, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/views/advertisement/select_ad_type_view.dart b/lib/views/advertisement/select_ad_type_view.dart index ee4d0a2..07076c9 100644 --- a/lib/views/advertisement/select_ad_type_view.dart +++ b/lib/views/advertisement/select_ad_type_view.dart @@ -14,6 +14,7 @@ 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/view_models/ad_view_model.dart'; +import 'package:mc_common_app/views/advertisement/ads_buyer_chats_view.dart'; import 'package:mc_common_app/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart'; import 'package:mc_common_app/widgets/bottom_sheet.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; @@ -24,7 +25,7 @@ import 'package:easy_localization/easy_localization.dart'; class SelectAdTypeView extends StatelessWidget { final List arguments; // [0] isProviderOrCustomer [1] isFromExtendAd - const SelectAdTypeView({Key? key, required this.arguments}) : super(key: key); + const SelectAdTypeView({super.key, required this.arguments}); Widget getVehicleAdTypeIcon(VehicleType vehicleTypeEnum) { switch (vehicleTypeEnum) { @@ -74,12 +75,24 @@ class SelectAdTypeView extends StatelessWidget { itemBuilder: (BuildContext context, int index) { VehicleTypeModel vehicleTypeModel = adVM.vehicleTypes[index]; return InkWell( - onTap: () { + onTap: () async { adVM.updateSelectionVehicleTypeId(SelectionModel(selectedId: vehicleTypeModel.id!, selectedOption: vehicleTypeModel.vehicleTypeName ?? "", errorValue: "")); if (adVM.isAdEditEnabled) { adVM.autoFillSelectedVehicleAdsDetails(); } - showMyBottomSheet(context, child: AdDurationSelectionSheet(isUpdateAdSelected: adVM.isAdEditEnabled, isFromExtendAd: isFromExtendAd, adsID: adsId)); + if (AppState().currentAppType == AppType.provider && AppState().userType == UserType.providerDealer) { + if (isFromExtendAd && !adVM.isAdEditEnabled) { + bool status = await adVM.createAdExtensionOrder(context, adId: adsId, adsDurationId: adVM.vehicleAdDurationId.selectedId); + log("hereStatus: $status"); + if (status) { + navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.extendAds); + } + } else { + navigateReplaceWithName(context, AppRoutes.createAdView); + } + } else { + showMyBottomSheet(context, child: AdDurationSelectionSheet(isUpdateAdSelected: adVM.isAdEditEnabled, isFromExtendAd: isFromExtendAd, adsID: adsId)); + } }, child: SizedBox( width: double.infinity, @@ -95,21 +108,23 @@ class SelectAdTypeView extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - vehicleTypeModel.vehicleTypeName.toString().toText(fontSize: 18, isBold: true), + vehicleTypeModel.vehicleTypeName.toString().toText(fontSize: 18), getVehicleAdTypeIcon(vehicleTypeModel.vehicleTypeEnum!), ], ), if (isProvider) ...[ Row( children: [ - LocaleKeys.duration.tr().toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), - LocaleKeys.validUntilSubscriptionExpiration.tr().toText(fontSize: 13, isBold: true), + "${LocaleKeys.duration.tr()}: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), + 2.width, + LocaleKeys.validUntilSubscriptionExpiration.tr().toText(fontSize: 13), ], ).paddingOnly(top: 5, bottom: 5), ] else ...[ Row( children: [ LocaleKeys.duration.tr().toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), + 4.width, adVM.vehicleAdDurationId.selectedOption.toText(fontSize: 13, isBold: true), const Icon(Icons.keyboard_arrow_down_sharp, color: MyColors.darkPrimaryColor, size: 20), ], @@ -123,7 +138,7 @@ class SelectAdTypeView extends StatelessWidget { 2.width, Padding( padding: const EdgeInsets.only(bottom: 4), - child: LocaleKeys.sar.tr().toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), + child: adVM.vehicleAdDurationId.currency.toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), ), ], ), @@ -150,7 +165,7 @@ class SelectAdTypeView extends StatelessWidget { Row( children: [ "$remainingAds ".toText(fontSize: 29, isBold: true, letterSpacing: 0, height: 1), - LocaleKeys.adsRemaining.tr().toText(fontSize: 17, color: MyColors.lightTextColor, isBold: true), + LocaleKeys.adsRemaining.tr().toText(fontSize: 17, color: MyColors.lightTextColor), ], ), Text.rich( diff --git a/lib/views/appointments/appointment_detail_view.dart b/lib/views/appointments/appointment_detail_view.dart index 78d2e43..397d54a 100644 --- a/lib/views/appointments/appointment_detail_view.dart +++ b/lib/views/appointments/appointment_detail_view.dart @@ -26,7 +26,7 @@ import 'package:sizer/sizer.dart'; class AppointmentDetailView extends StatelessWidget { final AppointmentListModel appointmentListModel; - const AppointmentDetailView({Key? key, required this.appointmentListModel}) : super(key: key); + const AppointmentDetailView({super.key, required this.appointmentListModel}); Widget getBaseActionButtonWidget({required Color color, required String text, Color textColor = MyColors.white, required Function() onPressed}) { return Expanded( diff --git a/lib/views/appointments/book_appointment_schedules_view.dart b/lib/views/appointments/book_appointment_schedules_view.dart index b46c46a..05318d2 100644 --- a/lib/views/appointments/book_appointment_schedules_view.dart +++ b/lib/views/appointments/book_appointment_schedules_view.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; @@ -56,7 +58,7 @@ class BookAppointmentSchedulesView extends StatelessWidget { Row( children: [ Expanded( - child: ("${LocaleKeys.schedule.tr()} ${scheduleIndex + 1}").toText(fontSize: 20, isBold: true), + child: (scheduleData.scheduleName ?? "").toText(fontSize: 20, isBold: true), ), ], ), @@ -65,7 +67,9 @@ class BookAppointmentSchedulesView extends StatelessWidget { children: [ (LocaleKeys.serviceLocation.tr()).toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), 2.width, - (scheduleData.appointmentTypeEnum == AppointmentTypeEnum.home ? LocaleKeys.customerLocation.tr() : LocaleKeys.companyLocation.tr()).toText(fontSize: 12, isBold: true).expand(), + (scheduleData.appointmentTypeEnum == AppointmentTypeEnum.home ? LocaleKeys.customerLocation.tr() : LocaleKeys.companyLocation.tr()) + .toText(fontSize: 12, isBold: true) + .expand(), ], ), Column( @@ -112,7 +116,9 @@ class BookAppointmentSchedulesView extends StatelessWidget { SizedBox( width: double.infinity, child: BuildTimeSlots( - timeSlots: appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex!].availableSlots ?? [], + timeSlots: appointmentsVM.serviceAppointmentScheduleList[scheduleIndex] + .customTimeDateSlotList![appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex!].availableSlots ?? + [], onPressed: (slotIndex) { appointmentsVM.updateSelectedAppointmentSlotByDate(scheduleIndex: scheduleIndex, slotIndex: slotIndex); }, diff --git a/lib/views/appointments/book_appointment_services_view.dart b/lib/views/appointments/book_appointment_services_view.dart index 9092246..eb9c4bc 100644 --- a/lib/views/appointments/book_appointment_services_view.dart +++ b/lib/views/appointments/book_appointment_services_view.dart @@ -1,10 +1,16 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/general_models/widgets_models.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/theme/colors.dart'; +import 'package:mc_common_app/utils/navigator.dart'; 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'; @@ -14,7 +20,7 @@ import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; class BookAppointmentServicesView extends StatelessWidget { - const BookAppointmentServicesView({Key? key}) : super(key: key); + const BookAppointmentServicesView({super.key}); @override Widget build(BuildContext context) { @@ -57,7 +63,23 @@ class BookAppointmentServicesView extends StatelessWidget { Row( children: [ Expanded(child: (serviceData.serviceDescription ?? "").toText(fontSize: 15, isBold: true)), - IconButton(onPressed: () => appointmentsVM.removeServiceInCurrentAppointment(serviceIndex), icon: const Icon(Icons.delete_outline, color: MyColors.redColor)) + Row( + children: [ + MyAssets.icEdit.buildSvg(height: 17, width: 17).onPress(() { + appointmentsVM.updateBranchServiceId( + SelectionModel( + selectedId: serviceData.serviceProviderServiceId!, + selectedOption: serviceData.serviceDescription ?? "", + itemPrice: "", + ), + ); + appointmentsVM.getServiceItems(serviceData.serviceProviderServiceId!); + navigateWithName(context, AppRoutes.bookAppointmentsItemView); + }), + 10.width, + MyAssets.closeWithOrangeBg.buildSvg(height: 30, width: 30).onPress(() => appointmentsVM.removeServiceInCurrentAppointment(serviceIndex)), + ], + ), ], ), if (true) ...[ @@ -79,14 +101,19 @@ class BookAppointmentServicesView extends StatelessWidget { children: [ (LocaleKeys.serviceLocation.tr()).toText(fontSize: 12, color: MyColors.lightTextColor), 4.width, - (serviceData.isHomeSelected ? serviceData.homeLocation : LocaleKeys.workshop.tr()).toText(fontSize: 12, isBold: true).expand(), + (serviceData.isHomeSelected ? serviceData.servicelocationInfo.address : LocaleKeys.workshop.tr()).toText(fontSize: 12, isBold: true).expand(), ], ), 5.height, Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - ((serviceData.currentTotalServicePrice).toString()).toText(fontSize: 32, isBold: true), + if (serviceData.isHomeSelected) ...[ + ((serviceData.currentTotalServicePrice + (serviceData.servicelocationInfo.distanceToBranch * double.parse(serviceData.rangePricePerKm!))).toStringAsFixed(2)) + .toText(fontSize: 32, isBold: true), + ] else ...[ + ((serviceData.currentTotalServicePrice).toString()).toText(fontSize: 32, isBold: true), + ], 2.width, LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5), const Icon(Icons.arrow_drop_down, size: 30) @@ -105,8 +132,7 @@ class BookAppointmentServicesView extends StatelessWidget { maxHeight: 55, title: LocaleKeys.cancel.tr(), onPressed: () { - appointmentsVM.servicesInCurrentAppointment.clear(); - appointmentsVM.allSelectedItemsInAppointments.clear(); + appointmentsVM.resetAfterBookingAppointment(); Navigator.pop(context); }, backgroundColor: MyColors.greyButtonColor, diff --git a/lib/views/appointments/book_appointments_item_view.dart b/lib/views/appointments/book_appointments_item_view.dart index 65ca7ee..823d8f8 100644 --- a/lib/views/appointments/book_appointments_item_view.dart +++ b/lib/views/appointments/book_appointments_item_view.dart @@ -18,7 +18,7 @@ import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; class BookAppointmentsItemView extends StatelessWidget { - const BookAppointmentsItemView({Key? key}) : super(key: key); + const BookAppointmentsItemView({super.key}); @override Widget build(BuildContext context) { @@ -65,9 +65,9 @@ class BookAppointmentsItemView extends StatelessWidget { ], ).horPaddingMain(), appointmentsVM.state == ViewState.busy - ? const Center(child: CircularProgressIndicator()) + ? const Expanded(child: Center(child: CircularProgressIndicator())) : appointmentsVM.serviceItemsFromApi.isEmpty - ? Expanded(child: Center(child: LocaleKeys.noAvailableItems.toText(fontSize: 16, color: MyColors.lightTextColor))) + ? Expanded(child: Center(child: LocaleKeys.noAvailableItems.tr().toText(fontSize: 16, color: MyColors.lightTextColor))) : ListView.separated( separatorBuilder: (BuildContext context, int index) => const Divider(), itemCount: appointmentsVM.serviceItemsFromApi.length, diff --git a/lib/views/appointments/review_appointment_view.dart b/lib/views/appointments/review_appointment_view.dart index 67021ff..3badab3 100644 --- a/lib/views/appointments/review_appointment_view.dart +++ b/lib/views/appointments/review_appointment_view.dart @@ -206,6 +206,7 @@ class ReviewAppointment extends StatelessWidget { AppointmentsVM appointmentsVM = context.read(); return ListView.builder( shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), itemCount: appointmentsVM.serviceAppointmentScheduleList.length, itemBuilder: (BuildContext context, int scheduleIndex) { ServiceAppointmentScheduleModel scheduleData = appointmentsVM.serviceAppointmentScheduleList[scheduleIndex]; @@ -223,7 +224,7 @@ class ReviewAppointment extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ ListView.separated( - // physics: const NeverScrollableScrollPhysics(), + physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: scheduleData.servicesListInAppointment!.length, itemBuilder: (BuildContext context, int serviceIndex) { @@ -238,11 +239,18 @@ class ReviewAppointment extends StatelessWidget { children: List.generate(scheduleData.servicesListInAppointment!.length, (serviceIndex) { ServiceModel serviceData = scheduleData.servicesListInAppointment![serviceIndex]; return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ "${serviceData.providerServiceDescription}".toText(fontSize: 16, isBold: true), + if (scheduleData.appointmentTypeEnum == AppointmentTypeEnum.home) ...[ + "${LocaleKeys.chargesPerKM.tr()}: ${serviceData.servicelocationInfo.homeChargesInCurrentService.toStringAsFixed(2)} ${LocaleKeys.sar.tr()}/km".toText( + fontSize: 12, + color: MyColors.lightTextColor, + ), + ], ], ), Column( @@ -250,6 +258,7 @@ class ReviewAppointment extends StatelessWidget { ItemData itemData = serviceData.serviceItems![itemIndex]; return Column( children: [ + // Item Name and Price Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -257,13 +266,15 @@ class ReviewAppointment extends StatelessWidget { ("${itemData.name}: ${itemData.price} ${LocaleKeys.sar.tr()}").toText(fontSize: 13, color: MyColors.lightTextColor), ], ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - ("${itemData.description}").toText(fontSize: 11, color: MyColors.lightTextColor), - ], - ), + + //Description + // Row( + // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisAlignment: MainAxisAlignment.start, + // children: [ + // ("${itemData.description}").toText(fontSize: 11, color: MyColors.lightTextColor), + // ], + // ), ], ); }), @@ -285,22 +296,32 @@ class ReviewAppointment extends StatelessWidget { "${LocaleKeys.dateAndTime.tr()}: ".toText(fontSize: 12, color: MyColors.lightTextColor), "${scheduleData.selectedCustomTimeDateSlotModel!.date!.date} at $selectedTimeSlot".toText(fontSize: 12), ]), - Row(children: [ - ("${LocaleKeys.location.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), - (scheduleData.appointmentTypeEnum == AppointmentTypeEnum.home ? LocaleKeys.home.tr() : LocaleKeys.workshop.tr()).toText(fontSize: 12), - ]), + Row( + children: [ + ("${LocaleKeys.location.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), + (scheduleData.appointmentTypeEnum == AppointmentTypeEnum.home ? LocaleKeys.homeLocation.tr() : LocaleKeys.companyLocation.tr()).toText(fontSize: 12), + if (appointmentsVM.currentLocationInfoModel != null && scheduleData.appointmentTypeEnum == AppointmentTypeEnum.home) ...[ + " , ${appointmentsVM.currentLocationInfoModel!.distanceToBranch.toStringAsFixed(2)} KM".toText(fontSize: 12), + ], + ], + ), + if (appointmentsVM.currentLocationInfoModel != null && scheduleData.appointmentTypeEnum == AppointmentTypeEnum.home) ...[ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ("${LocaleKeys.address.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), + Flexible(child: appointmentsVM.currentLocationInfoModel!.address.toText(fontSize: 12)), + ], + ), + ], ], ), ), 10.height, const Divider(thickness: 0.7), Builder(builder: (BuildContext context) { - //TODO: THIS SHOULD BE FINALIZED FROM API - double totalServicePrice = 0.0; - double totalKms = 15.3; - double rangePricePerKm = 5; - totalServicePrice = rangePricePerKm * totalKms; - + double totalServicePrice = scheduleData.totalLocationCharges ?? 0.0; + double totalKms = scheduleData.locationInfoModel!.distanceToBranch; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -316,8 +337,8 @@ class ReviewAppointment extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ("${LocaleKeys.locationCharges.tr()} ($rangePricePerKm x $totalKms )").toText(fontSize: 14, color: MyColors.lightTextColor), - ("${totalServicePrice.toString()} ${LocaleKeys.sar.tr()}").toText(fontSize: 16, isBold: true), + Flexible(child: ("${LocaleKeys.locationCharges.tr()} (${totalKms.toStringAsFixed(2)}km away)").toText(fontSize: 14, color: MyColors.lightTextColor)), + ("${totalServicePrice.toStringAsFixed(2)} ${LocaleKeys.sar.tr()}").toText(fontSize: 16, isBold: true), ], ), ], @@ -345,13 +366,13 @@ class ReviewAppointment extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Divider(thickness: 0.7, height: 3), + const Divider(thickness: 0.8, height: 2), 8.height, if (appointmentsVM.amountToPayForAppointment > 0) ...[ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.payableNow.tr().toText(fontSize: 14, isBold: true), + LocaleKeys.payableNow.tr().toText(fontSize: 14), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -365,11 +386,11 @@ class ReviewAppointment extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.remainingAmount.tr().toText(fontSize: 14, isBold: true), + LocaleKeys.remainingAmount.tr().toText(fontSize: 14), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - (appointmentsVM.totalAmount - appointmentsVM.amountToPayForAppointment).toString().toText(fontSize: 16, isBold: true), + (appointmentsVM.totalAmount - appointmentsVM.amountToPayForAppointment).toStringAsFixed(2).toText(fontSize: 16, isBold: true), 2.width, LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, fontSize: 12, isBold: true).paddingOnly(bottom: 2), ], @@ -384,7 +405,7 @@ class ReviewAppointment extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - appointmentsVM.totalAmount.toString().toText(fontSize: 29, isBold: true), + appointmentsVM.totalAmount.toStringAsFixed(2).toText(fontSize: 29, isBold: true), 2.width, LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5), ], @@ -436,6 +457,7 @@ class ReviewAppointment extends StatelessWidget { buildServicesInfoCard(context: context), 10.height, buildPersonalInformationCard(context: context), + 20.height, ], ).expand(), buildNextButtonFooter(context: context), 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 add3b01..29ed87d 100644 --- a/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart +++ b/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart @@ -1,10 +1,15 @@ +import 'dart:developer'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:mc_common_app/config/dependency_injection.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/models/general_models/widgets_models.dart'; +import 'package:mc_common_app/models/services_models/service_model.dart'; +import 'package:mc_common_app/services/common_services.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'; @@ -18,7 +23,7 @@ import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; class AppointmentServicePickBottomSheet extends StatelessWidget { - const AppointmentServicePickBottomSheet({Key? key}) : super(key: key); + const AppointmentServicePickBottomSheet({super.key}); @override Widget build(BuildContext context) { @@ -50,7 +55,9 @@ class AppointmentServicePickBottomSheet extends StatelessWidget { (DropValue value) => appointmentsVM.updateProviderCategoryId(SelectionModel(selectedId: value.id, selectedOption: value.value, itemPrice: value.subValue)), list: appointmentsVM.branchCategories, hint: LocaleKeys.selectCategory.tr(), - dropdownValue: appointmentsVM.branchSelectedCategoryId.selectedId != -1 ? DropValue(appointmentsVM.branchSelectedCategoryId.selectedId, appointmentsVM.branchSelectedCategoryId.selectedOption, "") : null, + dropdownValue: appointmentsVM.branchSelectedCategoryId.selectedId != -1 + ? DropValue(appointmentsVM.branchSelectedCategoryId.selectedId, appointmentsVM.branchSelectedCategoryId.selectedOption, "") + : null, ); }, ), @@ -78,7 +85,9 @@ class AppointmentServicePickBottomSheet extends StatelessWidget { (DropValue value) => appointmentsVM.updateBranchServiceId(SelectionModel(selectedId: value.id, selectedOption: value.value, itemPrice: value.subValue)), list: serviceCategories, hint: LocaleKeys.selectService.tr(), - dropdownValue: appointmentsVM.branchSelectedServiceId.selectedId != -1 ? DropValue(appointmentsVM.branchSelectedServiceId.selectedId, appointmentsVM.branchSelectedServiceId.selectedOption, "") : null, + dropdownValue: appointmentsVM.branchSelectedServiceId.selectedId != -1 + ? DropValue(appointmentsVM.branchSelectedServiceId.selectedId, appointmentsVM.branchSelectedServiceId.selectedOption, "") + : null, ); }, ), @@ -91,6 +100,10 @@ class AppointmentServicePickBottomSheet extends StatelessWidget { children: [ Expanded( child: ShowFillButton( + borderColor: !(appointmentsVM.currentServiceSelection != null && (appointmentsVM.currentServiceSelection!.isAllowAppointmentHome ?? false)) + ? Colors.transparent + : MyColors.darkPrimaryColor, + isDisabled: !(appointmentsVM.currentServiceSelection != null && (appointmentsVM.currentServiceSelection!.isAllowAppointmentHome ?? false)), isFilled: appointmentsVM.isHomeTapped, maxHeight: 48, title: LocaleKeys.home.tr(), @@ -117,11 +130,11 @@ class AppointmentServicePickBottomSheet extends StatelessWidget { ], ), if (appointmentsVM.isHomeTapped) ...[ - 8.height, + 12.height, TxtField( hint: LocaleKeys.pickLocation.tr(), errorValue: appointmentsVM.pickHomeLocationError, - value: appointmentsVM.pickedHomeLocation, + value: appointmentsVM.currentLocationInfoModel == null ? "" : appointmentsVM.currentLocationInfoModel!.address, isNeedClickAll: true, postfixData: Icons.add_location_outlined, postFixDataColor: MyColors.darkPrimaryColor, @@ -129,9 +142,41 @@ class AppointmentServicePickBottomSheet extends StatelessWidget { navigateTo( context, PickLocationPage( - onPickAddress: (double latitude, double longitude, String address) { - appointmentsVM.updatePickedHomeLocation(address); + onPickAddress: (double latitude, double longitude, String address) async { appointmentsVM.setState(ViewState.idle); + + double distance = injector.get().getDistanceBetween( + curLatitude: double.parse(appointmentsVM.selectedBranchModel!.latitude!), + curLongitude: double.parse(appointmentsVM.selectedBranchModel!.longitude!), + destLatitude: latitude, + destLongitude: longitude, + ); + log("distance: $distance"); + log("customerLocationRange : ${appointmentsVM.currentServiceSelection!.customerLocationRange}"); + + if (distance > (appointmentsVM.currentServiceSelection!.customerLocationRange ?? 0)) { + Utils.showToast(LocaleKeys.serviceNotProvided.tr()); + appointmentsVM.currentServiceSelection!.servicelocationInfo = CurrentLocationInfoModel( + address: '', + latitude: 0.0, + longitude: 0.0, + distanceToBranch: 0.0, + homeChargesInCurrentService: 0.0, + ); + appointmentsVM.updateCurrentLocationInfoModel(null); + return; + } + + CurrentLocationInfoModel temp = CurrentLocationInfoModel( + address: address, + latitude: latitude, + longitude: longitude, + distanceToBranch: distance, + homeChargesInCurrentService: double.parse(appointmentsVM.currentServiceSelection!.rangePricePerKm ?? "0.0"), + ); + + log("appointmentsVM.currentServiceSelection!.rangePricePerKm : ${temp.toString()}"); + appointmentsVM.updateCurrentLocationInfoModel(temp); }, ), ); @@ -139,11 +184,11 @@ class AppointmentServicePickBottomSheet extends StatelessWidget { ), 14.height, Row( - crossAxisAlignment: CrossAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, children: [ const Icon(Icons.warning, color: MyColors.adPendingStatusColor, size: 19).paddingOnly(bottom: 2), 3.width, - LocaleKeys.servicesNotAvailableHomeLocation.tr().toText(color: MyColors.adPendingStatusColor, fontSize: 12, isItalic: true), + Flexible(child: LocaleKeys.pickedLocationNotice.tr().toText(color: MyColors.adPendingStatusColor, fontSize: 12, isItalic: true)), ], ), ] @@ -155,14 +200,30 @@ class AppointmentServicePickBottomSheet extends StatelessWidget { width: double.infinity, child: Column( children: [ - if (appointmentsVM.isHomeTapped && !appointmentsVM.isFetchingServices && appointmentsVM.currentServiceSelection != null && (appointmentsVM.currentServiceSelection!.isAllowAppointmentHome ?? false)) ...[ + if (appointmentsVM.isHomeTapped && + !appointmentsVM.isFetchingServices && + appointmentsVM.currentServiceSelection != null && + (appointmentsVM.currentServiceSelection!.isAllowAppointmentHome ?? false)) ...[ const Divider(thickness: 1, height: 1), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - // TODO: This Price will be decided according to the service selected, We will calculate the KMs and multiple it with price per KMs - appointmentsVM.currentServiceSelection!.rangePricePerKm.toString().toText(fontSize: 30, isBold: true), - "${LocaleKeys.sar.tr()}/km".toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor).paddingOnly(bottom: 5), + if (appointmentsVM.currentLocationInfoModel == null) ...[ + appointmentsVM.currentServiceSelection!.rangePricePerKm.toString().toText(fontSize: 30, isBold: true), + "${LocaleKeys.sar.tr()}/km".toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor).paddingOnly(bottom: 5), + ] else ...[ + (appointmentsVM.currentServiceSelection!.servicelocationInfo.homeChargesInCurrentService * appointmentsVM.currentServiceSelection!.servicelocationInfo.distanceToBranch) + .toStringAsFixed(2) + .toText( + fontSize: 30, + isBold: true, + ), + LocaleKeys.sar.tr().toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor).paddingOnly(bottom: 5), + 10.width, + ("(${(appointmentsVM.currentServiceSelection!.servicelocationInfo.homeChargesInCurrentService).toStringAsFixed(2)} ${LocaleKeys.sar.tr()} x ${appointmentsVM.currentServiceSelection!.servicelocationInfo.distanceToBranch.toStringAsFixed(2)} KMs )") + .toText(fontSize: 13, color: MyColors.lightTextColor) + .paddingOnly(bottom: 5), + ], ], ), LocaleKeys.additionalChargesNotice.tr().toText(fontSize: 12, color: MyColors.lightTextColor), diff --git a/lib/views/chat/chat_view.dart b/lib/views/chat/chat_view.dart index 397f692..448eb52 100644 --- a/lib/views/chat/chat_view.dart +++ b/lib/views/chat/chat_view.dart @@ -167,6 +167,7 @@ class _ChatViewState extends State { chatMessageModel: chatMessageModel, requestStatusEnum: requestVM.currentSelectedRequest?.requestStatus, chatTypeEnum: chatTypeEnum, + requestsTypeEnum: chatTypeEnum == ChatTypeEnum.requestOffer ? chatViewArgumentsForRequest!.requestModel!.requestType.toRequestTypeEnum() : RequestsTypeEnum.specialCarRequest, ); }, ).horPaddingMain(), @@ -176,7 +177,7 @@ class _ChatViewState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ if (chatTypeEnum == ChatTypeEnum.requestOffer && - requestVM.currentSelectedRequest!.requestType.toRequestTypeStatusEnum() == RequestsTypeEnum.serviceRequest && + requestVM.currentSelectedRequest!.requestType.toRequestTypeEnum() == RequestsTypeEnum.serviceRequest && requestVM.currentSelectedRequest!.requestStatus == RequestStatusEnum.inProgress && AppState().currentAppType == AppType.customer) ...[ Expanded( @@ -200,7 +201,7 @@ class _ChatViewState extends State { // ), // ).paddingAll(15) ] else if (chatTypeEnum == ChatTypeEnum.requestOffer && - requestVM.currentSelectedRequest!.requestType.toRequestTypeStatusEnum() == RequestsTypeEnum.specialCarRequest && + requestVM.currentSelectedRequest!.requestType.toRequestTypeEnum() == RequestsTypeEnum.specialCarRequest && requestVM.currentSelectedRequest!.requestStatus == RequestStatusEnum.inProgress && requestVM.currentSelectedOffer!.requestOfferStatusEnum == RequestOfferStatusEnum.accepted && AppState().currentAppType == AppType.customer) ...[ diff --git a/lib/views/chat/widgets/chat_message_widget.dart b/lib/views/chat/widgets/chat_message_widget.dart index 13106b7..d4d2e3e 100644 --- a/lib/views/chat/widgets/chat_message_widget.dart +++ b/lib/views/chat/widgets/chat_message_widget.dart @@ -28,8 +28,9 @@ class ChatMessageCustomWidget extends StatefulWidget { final ChatMessageModel chatMessageModel; final RequestStatusEnum? requestStatusEnum; final ChatTypeEnum chatTypeEnum; + final RequestsTypeEnum requestsTypeEnum; - const ChatMessageCustomWidget({super.key, required this.chatMessageModel, required this.requestStatusEnum, required this.chatTypeEnum}); + const ChatMessageCustomWidget({super.key, required this.chatMessageModel, required this.requestStatusEnum, required this.chatTypeEnum, this.requestsTypeEnum = RequestsTypeEnum.specialCarRequest}); @override State createState() => _ChatMessageCustomWidgetState(); @@ -43,8 +44,14 @@ class _ChatMessageCustomWidgetState extends State { enableDrag: true, builder: (BuildContext context) { return Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) { + String title = ""; + if (requestOfferStatusEnum == RequestOfferStatusEnum.cancel) { + title = LocaleKeys.dealNotCompleted.tr(); + } else { + title = LocaleKeys.pleaseSpecify.tr(); + } return InfoBottomSheet( - title: LocaleKeys.pleaseSpecify.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44), + title: title.toText(fontSize: 28, isBold: true, letterSpacing: -1.44), description: Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: Column( @@ -58,26 +65,30 @@ class _ChatMessageCustomWidgetState extends State { shrinkWrap: true, itemCount: chatVM.offerRejectModelList.length, separatorBuilder: (BuildContext context, int index) { - if ((chatVM.offerRejectModelList[index].index == 1 || chatVM.offerRejectModelList[index].index == 2 || chatVM.offerRejectModelList[index].index == 3) && requestOfferStatusEnum == RequestOfferStatusEnum.cancel) { + bool indexContainsInCancel = widget.requestsTypeEnum == RequestsTypeEnum.specialCarRequest + ? (chatVM.indexesForCancelSpecialCarOffer.indexWhere((i) => i == index) != -1) + : (chatVM.indexesForCancelSparePartOffer.indexWhere((i) => i == index) != -1); + bool indexContainsInReject = chatVM.indexesForRejectOffer.indexWhere((i) => i == index) != -1; + if ((!indexContainsInCancel) && requestOfferStatusEnum == RequestOfferStatusEnum.cancel) { return const SizedBox(); } - // 0 -> itemNoLongerAvailable - // 1 -> customerNotResponding - if ((chatVM.offerRejectModelList[index].index == 4 || chatVM.offerRejectModelList[index].index == 0) && requestOfferStatusEnum == RequestOfferStatusEnum.rejected) { + if ((!indexContainsInReject) && requestOfferStatusEnum == RequestOfferStatusEnum.rejected) { return const SizedBox(); } return const Divider(thickness: 0.5); }, itemBuilder: (BuildContext context, int index) { - if ((chatVM.offerRejectModelList[index].index == 1 || chatVM.offerRejectModelList[index].index == 2 || chatVM.offerRejectModelList[index].index == 3) && requestOfferStatusEnum == RequestOfferStatusEnum.cancel) { + bool indexContainsInCancel = widget.requestsTypeEnum == RequestsTypeEnum.specialCarRequest + ? (chatVM.indexesForCancelSpecialCarOffer.indexWhere((i) => i == index) != -1) + : (chatVM.indexesForCancelSparePartOffer.indexWhere((i) => i == index) != -1); + bool indexContainsInReject = chatVM.indexesForRejectOffer.indexWhere((i) => i == index) != -1; + + if ((!indexContainsInCancel) && requestOfferStatusEnum == RequestOfferStatusEnum.cancel) { return const SizedBox(); } - // 0 -> itemNoLongerAvailable - // 1 -> customerNotResponding - // 2 -> veryHighPrice - // 3 -> alreadySold - if ((chatVM.offerRejectModelList[index].index == 4 || chatVM.offerRejectModelList[index].index == 0) && requestOfferStatusEnum == RequestOfferStatusEnum.rejected) { + + if ((!indexContainsInReject) && requestOfferStatusEnum == RequestOfferStatusEnum.rejected) { return const SizedBox(); } OfferRequestCommentModel offerRequestCommentModel = chatVM.offerRejectModelList[index]; @@ -136,7 +147,13 @@ 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)); + + if (index != -1) { + chatVM.serviceProviderOffersList[index].requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum; + } setState(() {}); // Navigator.pop(context); chatVM.updateRejectOfferDescription(''); @@ -184,11 +201,17 @@ class _ChatMessageCustomWidgetState extends State { if (status) { final requestVM = context.read(); + ChatVM chatVM = context.read(); + 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(() {}); // Navigator.pop(context); Utils.showToast("Offer Accepted"); @@ -447,7 +470,7 @@ class _ChatMessageCustomWidgetState extends State { Widget buildFreeTextDetailsInMessage({required ChatMessageTypeEnum chatMessageTypeEnum}) { return Row( - crossAxisAlignment: CrossAxisAlignment.start, + // crossAxisAlignment: CrossAxisAlignment.start, children: [ if (chatMessageTypeEnum == ChatMessageTypeEnum.offer && (widget.chatMessageModel.isMyMessage == true) && widget.chatMessageModel.isRead == true) ...[ Row( @@ -460,7 +483,7 @@ class _ChatMessageCustomWidgetState extends State { ], Expanded( child: Directionality( - textDirection: TextDirection.ltr, + textDirection: (widget.chatMessageModel.isMyMessage ?? false) ? TextDirection.ltr : TextDirection.rtl, child: (widget.chatMessageModel.chatText ?? "").toText( color: (widget.chatMessageModel.isMyMessage ?? false) ? MyColors.white : MyColors.lightTextColor, fontSize: 12, @@ -588,11 +611,24 @@ class _ChatMessageCustomWidgetState extends State { messageTypeWidget = Column( 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, + ), + ], + ), + ], if (widget.chatMessageModel.reqOffer!.reqOfferImages != null && widget.chatMessageModel.reqOffer!.reqOfferImages!.isNotEmpty) ...[ 5.height, buildImagesInOffer(widget.chatMessageModel.reqOffer!.reqOfferImages!, widget.chatMessageModel.isMyMessage ?? false), ], - 10.height, + 4.height, buildOfferDetailsInChatMessage(requestStatusEnum: widget.requestStatusEnum!, chatMessageModel: widget.chatMessageModel, context: context), ], ); diff --git a/lib/views/common_fragments/ads_fragment.dart b/lib/views/common_fragments/ads_fragment.dart index 9b08eb8..0a61fa2 100644 --- a/lib/views/common_fragments/ads_fragment.dart +++ b/lib/views/common_fragments/ads_fragment.dart @@ -7,6 +7,7 @@ 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/advertisment_models/ad_details_model.dart'; +import 'package:mc_common_app/models/general_models/widgets_models.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'; @@ -77,7 +78,6 @@ class AdsFragment extends StatelessWidget { txtColor: adVM.isExploreAdsTapped ? MyColors.white : MyColors.darkTextColor, onPressed: () { print("accessToken: ${AppState().getUser.data!.accessToken}"); - adVM.populateAdsFilterList(); if (adVM.myAds.isEmpty) { adVM.getMyAds(); } @@ -134,9 +134,7 @@ class AdsFragment extends StatelessWidget { } }, child: adVM.state == ViewState.busy - ? const Center( - child: CircularProgressIndicator(), - ) + ? const Center(child: CircularProgressIndicator()) : AdsListWidget(isAdsFragment: true, shouldShowAdStatus: !adVM.isExploreAdsTapped, adsList: getAdsList(adVM)), ), ) @@ -145,6 +143,18 @@ class AdsFragment extends StatelessWidget { ), floatingActionButton: FloatingActionButton( onPressed: () async { + if (AppState().userType != UserType.providerDealer) { + if (adVM.vehicleAdDurationId.selectedId == -1 && adVM.vehicleAdsDurations.isNotEmpty) { + SelectionModel selection = SelectionModel( + selectedId: adVM.vehicleAdsDurations.first.id ?? 0, + selectedOption: ("${adVM.vehicleAdsDurations.first.days} ${LocaleKeys.daysVar.tr()}"), + itemPrice: adVM.vehicleAdsDurations.first.price!.toString(), + currency: adVM.vehicleAdsDurations.first.currency!.toString(), + ); + adVM.updateVehicleAdDurationId(selection); + } + } + navigateWithName(context, AppRoutes.selectAdTypeView, arguments: [injector.get().currentAppType == AppType.provider, false, -1]); }, backgroundColor: MyColors.darkPrimaryColor, diff --git a/lib/views/common_fragments/requests_fragment.dart b/lib/views/common_fragments/requests_fragment.dart index 4c93f06..7ff9a4b 100644 --- a/lib/views/common_fragments/requests_fragment.dart +++ b/lib/views/common_fragments/requests_fragment.dart @@ -104,7 +104,7 @@ class MyRequestsFragment extends StatelessWidget { FiltersList( filterList: requestsVM.requestsTypeFilterOptions, onFilterTapped: (index, selectedFilterId) { - requestsVM.applyFilterOnRequestsVM(requestsTypeEnum: selectedFilterId.toRequestTypeStatusEnum()); + requestsVM.applyFilterOnRequestsVM(requestsTypeEnum: selectedFilterId.toRequestTypeEnum()); }, ), 8.height, @@ -155,7 +155,12 @@ class MyRequestsFragment extends StatelessWidget { cancelRequestBottomSheet(context, requestsVM.myFilteredRequests[index].id); return; }, - child: RequestItem(request: requestsVM.myFilteredRequests[index], appType: AppState().currentAppType, requestIndex: index), + child: RequestItem( + request: requestsVM.myFilteredRequests[index], + appType: AppState().currentAppType, + requestIndex: index, + shouldShowStatuses: AppState().currentAppType == AppType.customer, + ), ); }, separatorBuilder: (context, index) { diff --git a/lib/views/requests/create_request_page.dart b/lib/views/requests/create_request_page.dart index 25f2436..555defb 100644 --- a/lib/views/requests/create_request_page.dart +++ b/lib/views/requests/create_request_page.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; @@ -32,243 +34,252 @@ class CreateRequestPage extends StatelessWidget { pop(context); }, ), - body: Consumer(builder: (context, requestsVM, widget) { - return Column( - children: [ - Expanded( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.requestType.tr().toText(fontSize: 18, isBold: true), - 8.height, - if (requestsVM.isFetchingRequestType) ...[ - const Center( - child: CircularProgressIndicator(), - ), - ] else ...[ - if (AppState().getUser.data!.userInfo != null) ...[ - TxtField( - value: "${AppState().getUser.data!.userInfo!.firstName ?? ""} ${AppState().getUser.data!.userInfo!.lastName ?? ""}", - isBackgroundEnabled: true, - isNeedClickAll: true, - hint: '', - onTap: () {}, - ), - ], + body: PopScope( + onPopInvokedWithResult: (bool result, dynamic) { + if (result) { + context.read().resetRequestCreationForm(); + } + }, + child: Consumer(builder: (context, requestsVM, widget) { + return Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.requestType.tr().toText(fontSize: 18, isBold: true), 8.height, - Builder(builder: (context) { - List requestTypeDrop = []; - for (var element in requestsVM.myRequestsTypeEnum) { - requestTypeDrop.add(DropValue(element.enumValue.toInt() ?? 0, element.enumValueStr ?? "", "")); - } - return DropdownField( - (DropValue value) => requestsVM.updateSelectionRequestTypeId(SelectionModel(selectedId: value.id, selectedOption: value.value)), - list: requestTypeDrop, - dropdownValue: requestsVM.requestTypeId.selectedId != -1 ? DropValue(requestsVM.requestTypeId.selectedId, requestsVM.requestTypeId.selectedOption, "") : null, - hint: LocaleKeys.requestType.tr(), - errorValue: requestsVM.requestTypeId.errorValue, - ); - }), - ], - 8.height, - if (requestsVM.requestTypeId.selectedId != -1) - if (requestsVM.isFetchingVehicleType) ...[ + if (requestsVM.isFetchingRequestType) ...[ const Center( child: CircularProgressIndicator(), ), ] else ...[ + if (AppState().getUser.data!.userInfo != null) ...[ + TxtField( + value: "${AppState().getUser.data!.userInfo!.firstName ?? ""} ${AppState().getUser.data!.userInfo!.lastName ?? ""}", + isBackgroundEnabled: true, + isNeedClickAll: true, + hint: '', + onTap: () {}, + ), + ], + 8.height, Builder(builder: (context) { - List vehicleTypeDrop = []; - for (var element in requestsVM.vehicleTypes) { - vehicleTypeDrop.add(DropValue(element.id?.toInt() ?? 0, element.vehicleTypeName ?? "", "")); + List requestTypeDrop = []; + for (var element in requestsVM.myRequestsTypeEnum) { + requestTypeDrop.add(DropValue(element.enumValue.toInt() ?? 0, element.enumValueStr ?? "", "")); } return DropdownField( - (DropValue value) => requestsVM.updateSelectionVehicleTypeId(SelectionModel(selectedId: value.id, selectedOption: value.value)), - list: vehicleTypeDrop, - dropdownValue: requestsVM.vehicleTypeId.selectedId != -1 ? DropValue(requestsVM.vehicleTypeId.selectedId, requestsVM.vehicleTypeId.selectedOption, "") : null, - hint: LocaleKeys.vehicleType.tr(), - errorValue: requestsVM.vehicleTypeId.errorValue, + (DropValue value) => requestsVM.updateSelectionRequestTypeId(SelectionModel(selectedId: value.id, selectedOption: value.value)), + list: requestTypeDrop, + dropdownValue: requestsVM.requestTypeId.selectedId != -1 ? DropValue(requestsVM.requestTypeId.selectedId, requestsVM.requestTypeId.selectedOption, "") : null, + hint: LocaleKeys.requestType.tr(), + errorValue: requestsVM.requestTypeId.errorValue, ); }), ], - 8.height, - if (requestsVM.vehicleTypeId.selectedId != -1) - if (requestsVM.isFetchingVehicleDetail) ...[ - const Center( - child: CircularProgressIndicator(), - ), - ] else ...[ - Column( - children: [ - TxtField( - hint: LocaleKeys.brand.tr(), - value: requestsVM.brand, - onChanged: (e) => requestsVM.updateBrand(e), - ), - 8.height, - TxtField( - hint: LocaleKeys.model.tr(), - value: requestsVM.model, - onChanged: (e) => requestsVM.updateModel(e), - ), - 8.height, - Builder(builder: (context) { - List vehicleYearModelsDrop = []; - for (var element in requestsVM.vehicleYears) { - vehicleYearModelsDrop.add(DropValue(element.id?.toInt() ?? 0, element.modelYear ?? "", "")); - } - - return DropdownField( - (DropValue value) => requestsVM.updateSelectionVehicleYearId(SelectionModel(selectedId: value.id, selectedOption: value.value)), - list: vehicleYearModelsDrop, - dropdownValue: requestsVM.vehicleYearId.selectedId != -1 ? DropValue(requestsVM.vehicleYearId.selectedId, requestsVM.vehicleYearId.selectedOption, "") : null, - hint: LocaleKeys.year.tr(), - errorValue: requestsVM.vehicleYearId.errorValue, - ); - }), - 8.height, - Builder( - builder: (context) { - List vehicleOwnerDrop = []; - for (var element in requestsVM.vehicleConditionsEnum) { - vehicleOwnerDrop.add(DropValue(element.id.toInt(), element.enumValueStr, "")); + 8.height, + if (requestsVM.requestTypeId.selectedId != -1) + if (requestsVM.isFetchingVehicleType) ...[ + const Center( + child: CircularProgressIndicator(), + ), + ] else ...[ + Builder(builder: (context) { + List vehicleTypeDrop = []; + for (var element in requestsVM.vehicleTypes) { + vehicleTypeDrop.add(DropValue(element.id?.toInt() ?? 0, element.vehicleTypeName ?? "", "")); + } + return DropdownField( + (DropValue value) => requestsVM.updateSelectionVehicleTypeId(SelectionModel(selectedId: value.id, selectedOption: value.value)), + list: vehicleTypeDrop, + dropdownValue: requestsVM.vehicleTypeId.selectedId != -1 ? DropValue(requestsVM.vehicleTypeId.selectedId, requestsVM.vehicleTypeId.selectedOption, "") : null, + hint: LocaleKeys.vehicleType.tr(), + errorValue: requestsVM.vehicleTypeId.errorValue, + ); + }), + ], + 8.height, + if (requestsVM.vehicleTypeId.selectedId != -1) + if (requestsVM.isFetchingVehicleDetail) ...[ + const Center( + child: CircularProgressIndicator(), + ), + ] else ...[ + Column( + children: [ + TxtField( + hint: LocaleKeys.brand.tr(), + value: requestsVM.brand, + onChanged: (e) => requestsVM.updateBrand(e), + ), + 8.height, + TxtField( + hint: LocaleKeys.model.tr(), + value: requestsVM.model, + onChanged: (e) => requestsVM.updateModel(e), + ), + 8.height, + Builder(builder: (context) { + List vehicleYearModelsDrop = []; + for (var element in requestsVM.vehicleYears) { + vehicleYearModelsDrop.add(DropValue(element.id?.toInt() ?? 0, element.modelYear ?? "", "")); } + return DropdownField( - (DropValue value) => requestsVM.updateSelectionVehicleConditionId(SelectionModel(selectedId: value.id, selectedOption: value.value)), - list: vehicleOwnerDrop, - dropdownValue: requestsVM.vehicleConditionId.selectedId != -1 ? DropValue(requestsVM.vehicleConditionId.selectedId, requestsVM.vehicleConditionId.selectedOption, "") : null, - hint: LocaleKeys.condition.tr(), - errorValue: requestsVM.vehicleConditionId.errorValue, + (DropValue value) => requestsVM.updateSelectionVehicleYearId(SelectionModel(selectedId: value.id, selectedOption: value.value)), + list: vehicleYearModelsDrop, + dropdownValue: requestsVM.vehicleYearId.selectedId != -1 ? DropValue(requestsVM.vehicleYearId.selectedId, requestsVM.vehicleYearId.selectedOption, "") : null, + hint: LocaleKeys.year.tr(), + errorValue: requestsVM.vehicleYearId.errorValue, ); - }, - ), - 8.height, - Builder(builder: (context) { - List vehicleCountriesDrop = []; - for (var element in requestsVM.vehicleCountries) { - vehicleCountriesDrop.add(DropValue(element.id?.toInt() ?? 0, element.countryName ?? "", "")); - } - return DropdownField( - (DropValue value) => requestsVM.updateSelectionVehicleCountryId(SelectionModel(selectedOption: value.value, selectedId: value.id)), - list: vehicleCountriesDrop, - dropdownValue: requestsVM.vehicleCountryId.selectedId != -1 ? DropValue(requestsVM.vehicleCountryId.selectedId, requestsVM.vehicleCountryId.selectedOption, "") : null, - hint: LocaleKeys.country.tr(), - errorValue: requestsVM.vehicleCountryId.errorValue, - ); - }), - if (requestsVM.vehicleCountryId.selectedId != -1) ...[ - if (requestsVM.isCountryFetching) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [const CircularProgressIndicator().paddingAll(10)], - ), - ] else ...[ - 8.height, - Builder(builder: (context) { - List vehicleCitiesDrop = []; - for (var element in requestsVM.vehicleCities) { - vehicleCitiesDrop.add(DropValue(element.id?.toInt() ?? 0, element.cityName ?? "", "")); + }), + 8.height, + Builder( + builder: (context) { + List vehicleOwnerDrop = []; + for (var element in requestsVM.vehicleConditionsEnum) { + vehicleOwnerDrop.add(DropValue(element.id.toInt(), element.enumValueStr, "")); } return DropdownField( - (DropValue value) => requestsVM.updateSelectionVehicleCityId(SelectionModel(selectedId: value.id, selectedOption: value.value)), - list: vehicleCitiesDrop, - dropdownValue: requestsVM.vehicleCityId.selectedId != -1 ? DropValue(requestsVM.vehicleCityId.selectedId, requestsVM.vehicleCityId.selectedOption, "") : null, - hint: LocaleKeys.city.tr(), - errorValue: requestsVM.vehicleCityId.errorValue, + (DropValue value) => requestsVM.updateSelectionVehicleConditionId(SelectionModel(selectedId: value.id, selectedOption: value.value)), + list: vehicleOwnerDrop, + dropdownValue: + requestsVM.vehicleConditionId.selectedId != -1 ? DropValue(requestsVM.vehicleConditionId.selectedId, requestsVM.vehicleConditionId.selectedOption, "") : null, + hint: LocaleKeys.condition.tr(), + errorValue: requestsVM.vehicleConditionId.errorValue, ); - }), - ], - ], - // 8.height, - // TxtField( - // hint: LocaleKeys.price.tr(), - // value: requestsVM.price, - // keyboardType: TextInputType.number, - // onChanged: (e) => requestsVM.updatePrice(e), - // ), - 8.height, - TxtField( - hint: LocaleKeys.description.tr(), - maxLines: 3, - value: requestsVM.description, - onChanged: (e) => requestsVM.updateDescription(e), - ), - 8.height, - - // 1 -> SpecialCarRequest - // 2 -> ServiceRequest - - if (requestsVM.requestTypeId.selectedId == 2) ...[ - TxtField( - hint: LocaleKeys.address.tr(), - isNeedClickAll: false, - value: requestsVM.address, - postfixWidget: IconButton( - icon: const Icon( - size: 28, - Icons.add_location_outlined, - ), - onPressed: () { - navigateTo( - context, - PickLocationPage( - onPickAddress: (double latitude, double longitude, String address) { - requestsVM.address = address; - requestsVM.setState(ViewState.idle); - }, - ), - ); - }), - onChanged: (e) => requestsVM.updateAddress(e), + }, ), 8.height, - ], - if (requestsVM.pickedVehicleImages.isEmpty) ...[ - DottedRectContainer( - onTap: () => context.read().pickMultipleImages(), - text: LocaleKeys.attachImage.tr(), - icon: MyAssets.attachmentIcon.buildSvg(), - ), - ], - if (requestsVM.vehicleImageError != "") ...[ - 10.height, - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - requestsVM.vehicleImageError.toText(fontSize: 14, color: Colors.red), + Builder(builder: (context) { + List vehicleCountriesDrop = []; + for (var element in requestsVM.vehicleCountries) { + vehicleCountriesDrop.add(DropValue(element.id?.toInt() ?? 0, element.countryName ?? "", "")); + } + return DropdownField( + (DropValue value) => requestsVM.updateSelectionVehicleCountryId(SelectionModel(selectedOption: value.value, selectedId: value.id)), + list: vehicleCountriesDrop, + dropdownValue: + requestsVM.vehicleCountryId.selectedId != -1 ? DropValue(requestsVM.vehicleCountryId.selectedId, requestsVM.vehicleCountryId.selectedOption, "") : null, + hint: LocaleKeys.country.tr(), + errorValue: requestsVM.vehicleCountryId.errorValue, + ); + }), + if (requestsVM.vehicleCountryId.selectedId != -1) ...[ + if (requestsVM.isCountryFetching) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [const CircularProgressIndicator().paddingAll(10)], + ), + ] else ...[ + 8.height, + Builder(builder: (context) { + List vehicleCitiesDrop = []; + for (var element in requestsVM.vehicleCities) { + vehicleCitiesDrop.add(DropValue(element.id?.toInt() ?? 0, element.cityName ?? "", "")); + } + return DropdownField( + (DropValue value) => requestsVM.updateSelectionVehicleCityId(SelectionModel(selectedId: value.id, selectedOption: value.value)), + list: vehicleCitiesDrop, + dropdownValue: requestsVM.vehicleCityId.selectedId != -1 ? DropValue(requestsVM.vehicleCityId.selectedId, requestsVM.vehicleCityId.selectedOption, "") : null, + hint: LocaleKeys.city.tr(), + errorValue: requestsVM.vehicleCityId.errorValue, + ); + }), ], - ).paddingOnly(right: 10) - ], - if (requestsVM.pickedVehicleImages.isNotEmpty) ...[ - 16.height, - PickedFilesContainer( - pickedFiles: requestsVM.pickedVehicleImages, - onCrossPressedPrimary: requestsVM.removeImageFromList, - onAddFilePressed: () { - context.read().pickMultipleImages(); - }, + ], + // 8.height, + // TxtField( + // hint: LocaleKeys.price.tr(), + // value: requestsVM.price, + // keyboardType: TextInputType.number, + // onChanged: (e) => requestsVM.updatePrice(e), + // ), + 8.height, + TxtField( + hint: LocaleKeys.description.tr(), + maxLines: 3, + value: requestsVM.description, + onChanged: (e) => requestsVM.updateDescription(e), ), + 8.height, + + // 1 -> SpecialCarRequest + // 2 -> ServiceRequest + + if (requestsVM.requestTypeId.selectedId == 2) ...[ + TxtField( + hint: LocaleKeys.address.tr(), + isNeedClickAll: false, + value: requestsVM.address, + postfixWidget: IconButton( + icon: const Icon( + size: 28, + Icons.add_location_outlined, + ), + onPressed: () { + navigateTo( + context, + PickLocationPage( + onPickAddress: (double latitude, double longitude, String address) { + requestsVM.address = address; + requestsVM.setState(ViewState.idle); + }, + ), + ); + }), + onChanged: (e) => requestsVM.updateAddress(e), + ), + 8.height, + ], + if (requestsVM.pickedVehicleImages.isEmpty) ...[ + DottedRectContainer( + onTap: () => context.read().pickMultipleImages(), + text: LocaleKeys.attachImage.tr(), + icon: MyAssets.attachmentIcon.buildSvg(), + ), + ], + if (requestsVM.vehicleImageError != "") ...[ + 10.height, + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + requestsVM.vehicleImageError.toText(fontSize: 14, color: Colors.red), + ], + ).paddingOnly(right: 10) + ], + if (requestsVM.pickedVehicleImages.isNotEmpty) ...[ + 16.height, + PickedFilesContainer( + pickedFiles: requestsVM.pickedVehicleImages, + onCrossPressedPrimary: requestsVM.removeImageFromList, + onAddFilePressed: () { + context.read().pickMultipleImages(); + }, + ), + ], ], - ], - ), - ] - ], - ).toContainer(isShadowEnabled: true, marginAll: 16, paddingAll: 12), + ), + ] + ], + ).toContainer(isShadowEnabled: true, marginAll: 16, paddingAll: 12), + ), ), - ), - ShowFillButton( - title: LocaleKeys.createRequest.tr(), - maxWidth: double.infinity, - maxHeight: 55, - margin: const EdgeInsets.all(16), - onPressed: () async { - await context.read().onCreateRequestTapped(context); - }, - ) - ], - ); - }), + ShowFillButton( + title: LocaleKeys.createRequest.tr(), + maxWidth: double.infinity, + maxHeight: 55, + margin: const EdgeInsets.all(16), + onPressed: () async { + await context.read().onCreateRequestTapped(context); + }, + ) + ], + ); + }), + ), ); } } diff --git a/lib/views/requests/offer_list_page.dart b/lib/views/requests/offer_list_page.dart index 8b10f42..785d3f3 100644 --- a/lib/views/requests/offer_list_page.dart +++ b/lib/views/requests/offer_list_page.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:developer'; import 'package:flutter/material.dart'; @@ -18,100 +19,131 @@ import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; -class OfferListPage extends StatelessWidget { - final OfferListPageArguments offerListPageArguments; +class OfferListPage extends StatefulWidget { + final int requestId; - const OfferListPage({super.key, required this.offerListPageArguments}); + const OfferListPage({super.key, required this.requestId}); + + @override + State createState() => _OfferListPageState(); +} + +class _OfferListPageState extends State { + @override + void initState() { + _onRefresh(); + super.initState(); + } + + _onRefresh() async { + scheduleMicrotask(() async { + ChatVM chatVM = context.read(); + await chatVM.getOffersFromProvidersByRequest(requestId: widget.requestId); + }); + } @override Widget build(BuildContext context) { - final List serviceProviderOffers = offerListPageArguments.serviceProviderOffers; - return Scaffold( - appBar: CustomAppBar(title: LocaleKeys.offers.tr()), - body: serviceProviderOffers.isEmpty - ? Center(child: LocaleKeys.noOffersShow.tr().toText(fontSize: 16, color: MyColors.lightTextColor)) - : ListView.separated( - itemCount: serviceProviderOffers.length, - padding: const EdgeInsets.all(16), - itemBuilder: (context, index) { - ServiceProvidersOffers offersModel = serviceProviderOffers[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Utils.statusContainerChip( - text: Utils.getNameByRequestOfferStatusEnum(offersModel.requestOfferStatusEnum!), - chipColor: Utils.getChipColorByRequestOfferStatusEnum(offersModel.requestOfferStatusEnum!), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - (offersModel.name ?? "").toText(fontSize: 16, isBold: true), - Center( - child: "${offersModel.offerCount}".toText( - color: Colors.white, - isBold: true, - fontSize: 10, + return Consumer(builder: (context, ChatVM chatVM, Widget? child) { + return Scaffold( + appBar: CustomAppBar(title: LocaleKeys.offers.tr()), + body: RefreshIndicator( + onRefresh: () async { + _onRefresh(); + }, + child: chatVM.state == ViewState.busy + ? const Center(child: CircularProgressIndicator()) + : chatVM.serviceProviderOffersList.isEmpty + ? Center( + child: LocaleKeys.noOffersShow.tr().toText( + fontSize: 16, + color: MyColors.lightTextColor, ), - ).toContainer( - backgroundColor: MyColors.cancelledColor, - borderRadius: 100, - paddingAll: 1, - width: 22, - height: 22, - ), - ], - ), - 8.height, - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( + ) + : ListView.separated( + itemCount: chatVM.serviceProviderOffersList.length, + padding: const EdgeInsets.all(16), + itemBuilder: (context, index) { + ServiceProvidersOffers offersModel = chatVM.serviceProviderOffersList[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - "${offersModel.companyName}".toText(color: MyColors.lightTextColor, fontSize: 14), - if (offersModel.createdOn != null && offersModel.createdOn!.isNotEmpty) ...[ - " | ${DateTime.parse(offersModel.createdOn!).getTimeAgo()}".toText(color: MyColors.lightTextColor, fontSize: 14), - ], - // " | 1 hour ago".toText( - // color: MyColors.lightTextColor, - // fontSize: 12, - // ), + Utils.statusContainerChip( + text: Utils.getNameByRequestOfferStatusEnum(offersModel.requestOfferStatusEnum!), + chipColor: Utils.getChipColorByRequestOfferStatusEnum(offersModel.requestOfferStatusEnum!), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (offersModel.name ?? "").toText( + fontSize: 16, + isBold: true, + ), + Center( + child: "${offersModel.offerCount}".toText( + color: Colors.white, + isBold: true, + fontSize: 10, + ), + ).toContainer( + backgroundColor: MyColors.cancelledColor, + borderRadius: 100, + paddingAll: 1, + width: 22, + height: 22, + ), + ], + ), + 8.height, + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + "${offersModel.companyName}".toText(color: MyColors.lightTextColor, fontSize: 14), + if (offersModel.createdOn != null && offersModel.createdOn!.isNotEmpty) ...[ + " | ${DateTime.parse(offersModel.createdOn!).getTimeAgo()}".toText(color: MyColors.lightTextColor, fontSize: 14), + ], + ], + ), + const Icon(Icons.arrow_forward, color: MyColors.darkIconColor, size: 18), + ], + ), ], - ), - const Icon(Icons.arrow_forward, color: MyColors.darkIconColor, size: 18), - ], - ), - ], - ).onPress(() async { - context.read().currentSelectedOffer = offersModel; + ).onPress(() async { + context.read().currentSelectedOffer = offersModel; - ChatViewArgumentsForRequest chatViewArgumentsForRequest = ChatViewArgumentsForRequest( - chatTypeEnum: ChatTypeEnum.requestOffer, - receiverId: "${offersModel.providerUserId}", - senderId: AppState().getUser.data!.userInfo!.userId.toString(), - requestId: offerListPageArguments.requestId, - providerIndex: index, - requestIndex: -1, // This will be only send in case of provider - ); + ChatViewArgumentsForRequest chatViewArgumentsForRequest = ChatViewArgumentsForRequest( + chatTypeEnum: ChatTypeEnum.requestOffer, + receiverId: "${offersModel.providerUserId}", + senderId: AppState().getUser.data!.userInfo!.userId.toString(), + requestId: widget.requestId, + providerIndex: index, + requestModel: context.read().currentSelectedRequest, + requestIndex: -1, + ); - ChatViewArguments chatViewArguments = ChatViewArguments(chatTypeEnum: ChatTypeEnum.requestOffer, chatViewArgumentsForRequest: chatViewArgumentsForRequest); + ChatViewArguments chatViewArguments = ChatViewArguments(chatTypeEnum: ChatTypeEnum.requestOffer, chatViewArgumentsForRequest: chatViewArgumentsForRequest); - final chatVM = context.read(); + final chatVM = context.read(); - await chatVM - .getRequestsChatMessagesForCustomer( - context: context, - providerId: offersModel.providerId ?? 0, - requestOfferId: 0, - requestId: offerListPageArguments.requestId ?? 0, - providerOfferIndex: index, - ) - .whenComplete(() => navigateWithName(context, AppRoutes.chatView, arguments: chatViewArguments)); - }).toContainer(isShadowEnabled: true); - }, - separatorBuilder: (context, index) => 16.height, - ), - ); + await chatVM + .getRequestsChatMessagesForCustomer( + context: context, + providerId: offersModel.providerId ?? 0, + requestOfferId: 0, + requestId: widget.requestId, + providerOfferIndex: index, + ) + .whenComplete(() => 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 668ec50..01ad4be 100644 --- a/lib/views/requests/request_bottomsheets.dart +++ b/lib/views/requests/request_bottomsheets.dart @@ -48,7 +48,7 @@ Future buildSendOfferBottomSheet({required BuildContext context, required Reques numbersOnly: true, onChanged: (v) => requestsVM.updateOfferPrice(v), ), - if (requestDetail.requestType == RequestsTypeEnum.serviceRequest.getIdFromRequestTypeStatusEnum()) ...[ + if (requestDetail.requestType == RequestsTypeEnum.serviceRequest.getIdFromRequestTypeEnum()) ...[ 12.height, TxtField( value: requestsVM.serviceItem, @@ -87,7 +87,7 @@ Future buildSendOfferBottomSheet({required BuildContext context, required Reques onChanged: (v) => requestsVM.updateOfferDescription(v), ), 12.height, - if (requestDetail.requestType == RequestsTypeEnum.serviceRequest.getIdFromRequestTypeStatusEnum()) ...[ + if (requestDetail.requestType == RequestsTypeEnum.serviceRequest.getIdFromRequestTypeEnum()) ...[ LocaleKeys.deliveryAvailable.tr().toText(fontSize: 16), 8.height, Container( diff --git a/lib/views/requests/request_detail_page.dart b/lib/views/requests/request_detail_page.dart index fea77d6..f1230ff 100644 --- a/lib/views/requests/request_detail_page.dart +++ b/lib/views/requests/request_detail_page.dart @@ -8,7 +8,9 @@ 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/appointments_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/advertisement/components/ads_images_corousel_widget.dart'; import 'package:mc_common_app/views/chat/widgets/chat_bottom_sheets.dart'; import 'package:mc_common_app/views/requests/request_bottomsheets.dart'; @@ -21,17 +23,22 @@ import 'package:easy_localization/easy_localization.dart'; class RequestDetailPage extends StatelessWidget { final RequestDetailPageArguments requestDetailPageArguments; - const RequestDetailPage({Key? key, required this.requestDetailPageArguments}) : super(key: key); + const RequestDetailPage({super.key, required this.requestDetailPageArguments}); - Widget buildRequestDetailActionFooter({required int requestId, required RequestStatusEnum requestStatus, required RequestsTypeEnum requestTypeEnum, required String statusText, required BuildContext context}) { - // final requestsVM = context.read(); + Widget buildRequestDetailActionFooter({ + required int requestId, + required RequestStatusEnum requestStatus, + required RequestsTypeEnum requestTypeEnum, + required String statusText, + required BuildContext context, + }) { switch (requestStatus) { case RequestStatusEnum.submitted: return ShowFillButton( maxWidth: double.infinity, margin: const EdgeInsets.all(15), maxHeight: 55, - title: LocaleKeys.viewChat.tr(), + title: LocaleKeys.specialRequestChat.tr(), isBold: false, onPressed: () => onViewChatTapped(context), ); @@ -115,7 +122,7 @@ class RequestDetailPage extends StatelessWidget { appBar: CustomAppBar( title: LocaleKeys.requestDetail.tr(), actions: [ - (requestDetailPageArguments.requestModel.isChatted) + (context.read().isOfferSent || requestDetailPageArguments.requestModel.isChatted) ? Padding( padding: const EdgeInsets.only(top: 8, bottom: 8, right: 21), child: const Icon(Icons.messenger_outline_rounded, color: Colors.black, size: 18).toContainer( @@ -144,7 +151,7 @@ class RequestDetailPage extends StatelessWidget { allPading: 12, margin: const EdgeInsets.only(top: 21, right: 21, left: 21), ), - if (!requestDetailPageArguments.requestModel.isChatted) ...[ + if (!context.read().isOfferSent && !requestDetailPageArguments.requestModel.isChatted) ...[ ShowFillButton( maxWidth: double.infinity, margin: const EdgeInsets.all(15), @@ -160,7 +167,7 @@ class RequestDetailPage extends StatelessWidget { requestStatus: requestDetailPageArguments.requestModel.requestStatus, statusText: "Offer ${requestDetailPageArguments.requestModel.requestStatusName}", context: context, - requestTypeEnum: requestDetailPageArguments.requestModel.requestType.toRequestTypeStatusEnum(), + requestTypeEnum: requestDetailPageArguments.requestModel.requestType.toRequestTypeEnum(), ), ], ], @@ -211,6 +218,30 @@ class RequestDetailPage extends StatelessWidget { ], showItem("${LocaleKeys.description.tr()}: ", requestDetail.description), 16.height, + + if (AppState().currentAppType == AppType.provider && + (requestDetailPageArguments.requestModel.requestStatus == RequestStatusEnum.inProgress || requestDetailPageArguments.requestModel.requestStatus == RequestStatusEnum.submitted)) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ("${LocaleKeys.reportComplaint.tr()}?").toText(fontSize: 14, color: MyColors.darkTextColor), + ], + ).onPress( + () { + int customerID = requestDetailPageArguments.requestModel.customerId; + int complainType = 2; + String customerName = requestDetailPageArguments.requestModel.customerName; + + context.read().buildComplaintBottomSheet( + customerID: customerID, + customerName: customerName, + complainType: complainType, + context: context, + ); + }, + ), + ], + // showItem("${LocaleKeys.priceRange.tr()}:", ""), // Row( // children: [ diff --git a/lib/views/requests/widget/request_item.dart b/lib/views/requests/widget/request_item.dart index 52afc08..57e3c4d 100644 --- a/lib/views/requests/widget/request_item.dart +++ b/lib/views/requests/widget/request_item.dart @@ -20,28 +20,44 @@ class RequestItem extends StatelessWidget { final RequestModel request; final AppType appType; final int requestIndex; + final bool shouldShowStatuses; - const RequestItem({super.key, required this.request, required this.appType, required this.requestIndex}); + const RequestItem({super.key, required this.request, required this.appType, required this.requestIndex, this.shouldShowStatuses = true}); @override Widget build(BuildContext context) { + String imageUrl = ""; + if (request.requestImages != null && request.requestImages!.isNotEmpty) { + imageUrl = request.requestImages!.first.imageUrl ?? ""; + } return Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ + imageUrl.buildNetworkImage( + width: 80, + height: 80, + fit: BoxFit.cover, + ), + 12.width, Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.statusContainerChip(text: request.requestStatusName.toLowerCase() == "InProgress".toLowerCase() ? "In Progress" : request.requestStatusName, chipColor: Utils.getChipColorByRequestStatus(request.requestStatus)), + if (shouldShowStatuses) ...[ + Utils.statusContainerChip( + text: request.requestStatusName.toLowerCase() == "InProgress".toLowerCase() ? "In Progress" : request.requestStatusName, + chipColor: Utils.getChipColorByRequestStatus(request.requestStatus)), + ], 6.height, "${request.brand} ${request.model} | ${request.id}".toText(fontSize: 16, letterSpacing: -0.64), showItem("${LocaleKeys.year.tr()}:", "${request.year}"), if (request.customerName.isNotEmpty) ...[ showItem("${LocaleKeys.customerName.tr()}:", request.customerName), ], + showItem("${LocaleKeys.description.tr()}:", request.description), ], ), ), @@ -72,7 +88,6 @@ class RequestItem extends StatelessWidget { ) ], ), - showItem("${LocaleKeys.description.tr()}:", request.description), // showItem("${LocaleKeys.priceRange.tr()}: ", ""), // Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -106,10 +121,7 @@ class RequestItem extends StatelessWidget { RequestDetailPageArguments requestDetailPageArguments = RequestDetailPageArguments(requestIndex: requestIndex, requestModel: request); navigateWithName(context, AppRoutes.requestsDetailPage, arguments: requestDetailPageArguments); } else { - ChatVM chatVM = context.read(); - await chatVM.getOffersFromProvidersByRequest(requestId: request.id, context: context); - OfferListPageArguments offerListPageArguments = OfferListPageArguments(serviceProviderOffers: chatVM.serviceProviderOffersList, requestId: request.id); - navigateWithName(context, AppRoutes.offersListPage, arguments: offerListPageArguments); + navigateWithName(context, AppRoutes.offersListPage, arguments: request.id); } }); } diff --git a/lib/views/setting_options/provider_accepted_requests_view.dart b/lib/views/setting_options/provider_accepted_requests_view.dart new file mode 100644 index 0000000..b6c8b51 --- /dev/null +++ b/lib/views/setting_options/provider_accepted_requests_view.dart @@ -0,0 +1,98 @@ +import 'dart:async'; + +import 'package:flutter/gestures.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/generated/locale_keys.g.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'; +import 'package:flutter/material.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/views/requests/widget/request_item.dart'; +import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; +import 'package:mc_common_app/widgets/common_widgets/categories_list.dart'; +import 'package:provider/provider.dart'; +import 'package:easy_localization/easy_localization.dart'; + +class ProviderAcceptedRequestsView extends StatefulWidget { + const ProviderAcceptedRequestsView({super.key}); + + @override + State createState() => _ProviderAcceptedRequestsViewState(); +} + +class _ProviderAcceptedRequestsViewState extends State { + @override + void initState() { + scheduleMicrotask(() async => context.read().applyFilterOnProvidersAcceptedRequests(requestsTypeEnum: RequestsTypeEnum.specialCarRequest)); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Consumer(builder: (BuildContext context, RequestsVM requestsVM, Widget? child) { + return Scaffold( + appBar: CustomAppBar(title: LocaleKeys.acceptedRequests.tr()), + body: Container( + color: MyColors.backgroundColor, + width: double.infinity, + height: double.infinity, + child: Column( + children: [ + 16.height, + FiltersList( + filterList: requestsVM.acceptedRequestsTypeFilterOptions, + onFilterTapped: (index, selectedFilterId) { + requestsVM.applyFilterOnProvidersAcceptedRequests(requestsTypeEnum: selectedFilterId.toRequestTypeEnum()); + }, + ), + 8.height, + Expanded( + child: RefreshIndicator( + onRefresh: () async => await requestsVM.getProvidersAcceptedRequests(), + child: requestsVM.state == ViewState.busy + ? const Center(child: CircularProgressIndicator()) + : requestsVM.providersAcceptedRequestsList.isEmpty + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LocaleKeys.noRequeststoShow.tr().toText(fontSize: 16, color: MyColors.lightTextColor), + if (requestsVM.requestsFiltersCounter > 0) ...[ + 8.height, + InkWell( + onTap: () async { + requestsVM.clearRequestsFilters(); + await requestsVM.getRequestsBasedOnFilters(); + }, + child: LocaleKeys.clearFilters.tr().toText( + fontSize: 14, + isBold: true, + color: MyColors.darkPrimaryColor, + ), + ), + ], + ], + ) + : ListView.separated( + itemBuilder: (context, index) { + return RequestItem(request: requestsVM.providersAcceptedRequestsList[index], appType: AppState().currentAppType, requestIndex: index); + }, + separatorBuilder: (context, index) { + return 16.height; + }, + itemCount: requestsVM.providersAcceptedRequestsList.length, + padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16, top: 8), + ), + )) + ], + ), + ), + ); + }); + } +} diff --git a/lib/views/setting_options/setting_options_contact_us.dart b/lib/views/setting_options/setting_options_contact_us.dart index 817c3de..b30b99b 100644 --- a/lib/views/setting_options/setting_options_contact_us.dart +++ b/lib/views/setting_options/setting_options_contact_us.dart @@ -90,13 +90,15 @@ class _SettingOptionsContactUsState extends State { padding: const EdgeInsets.all(16), itemBuilder: (context, index) { ContactInfoModel contactInfoModel = settingsOptionsVM.contactInfosList[index]; - return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ contactInfoModel.companyName.toString().toText(fontSize: 16), 5.height, - if (contactInfoModel.latitude != null && contactInfoModel.latitude!.isNotEmpty && contactInfoModel.longitude != null && contactInfoModel.longitude!.isNotEmpty) ...[ + if (contactInfoModel.latitude != null && + contactInfoModel.latitude!.isNotEmpty && + contactInfoModel.longitude != null && + contactInfoModel.longitude!.isNotEmpty) ...[ LocaleKeys.openMapLocation.tr().toText(isUnderLine: true, color: MyColors.darkPrimaryColor, fontSize: 10).onPress(() async { double latitude, longitude = 0.0; latitude = double.parse(contactInfoModel.latitude!); diff --git a/lib/views/setting_options/setting_options_more.dart b/lib/views/setting_options/setting_options_more.dart index 2bb5078..0907b63 100644 --- a/lib/views/setting_options/setting_options_more.dart +++ b/lib/views/setting_options/setting_options_more.dart @@ -87,6 +87,19 @@ class _SettingOptionsMoreState extends State { onTap: () => navigateWithName(context, AppRoutes.favoriteListView), ), ] else ...[ + CustomSettingOptionsTile( + leadingWidget: SvgPicture.asset( + MyAssets.icRequests, + height: 20, + width: 20, + color: MyColors.darkIconColor, + ), + titleText: LocaleKeys.acceptedRequests.tr(), + needBorderBelow: true, + onTap: () { + navigateWithName(context, AppRoutes.providerAcceptedRequestsView); + }, + ), CustomSettingOptionsTile( leadingWidget: const Icon(Icons.local_shipping, size: 20), titleText: LocaleKeys.shippingManagement.tr(), diff --git a/lib/views/shipping_management/shipping_management_view.dart b/lib/views/shipping_management/shipping_management_view.dart index 8f54e8e..4d01e4c 100644 --- a/lib/views/shipping_management/shipping_management_view.dart +++ b/lib/views/shipping_management/shipping_management_view.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:developer'; import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; @@ -43,7 +44,18 @@ class _ShippingManagementViewState extends State { super.initState(); } - Future buildUpdateShippingStatusBottomSheet({required int shippingRequestId}) { + List getAvailableStatusesList(List list, ShippingRequestStatusEnum shippingRequestStatusEnum) { + List newList = []; + + for (var element in list) { + if (element.id > shippingRequestStatusEnum.getIdFromShippingStatusEnum()) { + newList.add(element); + } + } + return newList; + } + + Future buildUpdateShippingStatusBottomSheet({required int shippingRequestId, required ShippingRequestStatusEnum shippingRequestStatusEnum}) { return showModalBottomSheet( context: context, isScrollControlled: true, @@ -63,7 +75,7 @@ class _ShippingManagementViewState extends State { 12.height, ListView.separated( shrinkWrap: true, - itemCount: shippingManagementVM.shippingRequestStatusesList.length, + itemCount: getAvailableStatusesList(shippingManagementVM.shippingRequestStatusesList, shippingRequestStatusEnum).length, separatorBuilder: (BuildContext context, int index) { return const Padding( padding: EdgeInsets.symmetric(vertical: 3.0), @@ -71,13 +83,18 @@ class _ShippingManagementViewState extends State { ); }, itemBuilder: (BuildContext context, int index) { - List list = shippingManagementVM.shippingRequestStatusesList; + List list = getAvailableStatusesList(shippingManagementVM.shippingRequestStatusesList, shippingRequestStatusEnum); FilterListModel shippingFilterListModel = list[index]; + int id = list[index].id; return CircleCheckBoxWithTitle( isChecked: shippingFilterListModel.isSelected, title: shippingFilterListModel.title, onSelected: () { - shippingManagementVM.updateSelectionInShippingRequestStatuses(index); + int i = shippingManagementVM.shippingRequestStatusesList.indexWhere((element) => element.id == id); + if (i == -1) { + return; + } + shippingManagementVM.updateSelectionInShippingRequestStatuses(i); }, selectedColor: MyColors.darkPrimaryColor, ); @@ -103,7 +120,8 @@ class _ShippingManagementViewState extends State { bool status = await shippingManagementVM.onUpdateShippingStatusTapped(shippingStatusEnum: shippingStatusEnum, shippingRequestId: shippingRequestId, context: context); if (status) { pop(context); - await shippingManagementVM.getShippingRequestsListByFilters(); + int index = shippingManagementVM.shippingRequestFilterOptions.indexWhere((element) => element.isSelected); + await shippingManagementVM.getShippingRequestsListByFilters(shippingStatusEnum: index.toShippingStatusEnum()); } }, maxWidth: double.infinity, @@ -166,26 +184,35 @@ class _ShippingManagementViewState extends State { text: Utils.getNameByShippingRequestStatusEnum(shippingRequest.shippingStatusEnum ?? ShippingRequestStatusEnum.initiated), chipColor: Utils.getChipColorByShippingRequestStatusEnum(shippingRequest.shippingStatusEnum ?? ShippingRequestStatusEnum.initiated), ), - ("Request Name | ${shippingRequest.id.toString()}").toText(fontSize: 16), - 8.height, + ("${shippingRequest.request!.brand} ${shippingRequest.request!.model} | ${shippingRequest.id.toString()}").toText(fontSize: 16), + (shippingRequest.request!.description ?? "").toText(color: MyColors.lightTextColor, fontSize: 12, fontWeight: MyFonts.Medium), + if (shippingRequest.comment != null && shippingRequest.comment!.isNotEmpty) ...[ + ("${LocaleKeys.comment.tr()}: ${shippingRequest.comment ?? ""}").toText(color: MyColors.lightTextColor, fontSize: 12, fontWeight: MyFonts.Medium), + ], + 4.height, Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ - "customer name".toText(color: MyColors.lightTextColor, fontSize: 14), + (shippingRequest.customerName ?? "").toText(color: MyColors.lightTextColor, fontSize: 12, fontWeight: MyFonts.Medium), if (shippingRequest.createdOn != null && shippingRequest.createdOn!.isNotEmpty) ...[ - " | ${DateTime.parse(shippingRequest.createdOn!).getTimeAgo()}".toText(color: MyColors.lightTextColor, fontSize: 14), + " | ${DateTime.parse(shippingRequest.createdOn!).getTimeAgo()}".toText(color: MyColors.lightTextColor, fontSize: 12, fontWeight: MyFonts.Medium), ], ], ), - const Icon(Icons.arrow_forward, color: MyColors.darkIconColor, size: 18), + if (shippingRequest.shippingStatusEnum != ShippingRequestStatusEnum.delivered) ...[ + const Icon(Icons.arrow_forward, color: MyColors.darkIconColor, size: 18), + ], ], ), ], ).onPress(() { - buildUpdateShippingStatusBottomSheet(shippingRequestId: shippingRequest.id!); + if (shippingRequest.shippingStatusEnum == ShippingRequestStatusEnum.delivered) { + return; + } + buildUpdateShippingStatusBottomSheet(shippingRequestId: shippingRequest.id!, shippingRequestStatusEnum: shippingRequest.shippingStatusEnum!); }).toContainer(isShadowEnabled: true); }, separatorBuilder: (context, index) => 16.height, diff --git a/lib/widgets/checkbox_with_title_desc.dart b/lib/widgets/checkbox_with_title_desc.dart index 4cf774d..73a9a2a 100644 --- a/lib/widgets/checkbox_with_title_desc.dart +++ b/lib/widgets/checkbox_with_title_desc.dart @@ -8,8 +8,16 @@ class CheckBoxWithTitleDescription extends StatelessWidget { final bool isSelected; final String title, description; final Function(bool) onSelection; + final bool isDisabled; - const CheckBoxWithTitleDescription({required this.isSelected, required this.title, required this.description, required this.onSelection, Key? key}) : super(key: key); + const CheckBoxWithTitleDescription({ + required this.isSelected, + required this.title, + required this.description, + required this.onSelection, + this.isDisabled = false, + super.key, + }); @override Widget build(BuildContext context) { @@ -21,7 +29,9 @@ class CheckBoxWithTitleDescription extends StatelessWidget { children: [ Checkbox( value: isSelected, + activeColor: isDisabled ? MyColors.lightIconColor : MyColors.darkPrimaryColor, onChanged: (bool? v) { + if (isDisabled) return; onSelection(v ?? false); }, ), diff --git a/lib/widgets/common_widgets/branch_details_card.dart b/lib/widgets/common_widgets/branch_details_card.dart index f5fd70c..d1f0359 100644 --- a/lib/widgets/common_widgets/branch_details_card.dart +++ b/lib/widgets/common_widgets/branch_details_card.dart @@ -69,7 +69,7 @@ class BranchDetailCard extends StatelessWidget { if (providerName != null) Row( children: [ - ("${LocaleKeys.providers.tr()}:").toText(color: MyColors.lightTextColor, fontSize: 12), + ("${LocaleKeys.provider.tr()}:").toText(color: MyColors.lightTextColor, fontSize: 12), 4.width, providerName!.toText(fontSize: 12, isBold: true), ], diff --git a/lib/widgets/image_viewer/image_viewer_screen.dart b/lib/widgets/image_viewer/image_viewer_screen.dart index e4c84d7..48dddd4 100644 --- a/lib/widgets/image_viewer/image_viewer_screen.dart +++ b/lib/widgets/image_viewer/image_viewer_screen.dart @@ -1,8 +1,5 @@ -import 'dart:developer'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:mc_common_app/extensions/int_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/theme/colors.dart'; @@ -21,43 +18,58 @@ class MediaViewerScreen extends StatefulWidget { class MediaViewerScreenState extends State { int selectedIndex = 0; // Track selected image index + PageController pageController = PageController(initialPage: 0); + @override Widget build(BuildContext context) { - debugPrint("loaded"); return Scaffold( appBar: CustomAppBar(title: LocaleKeys.pictures.tr()), body: Column( children: [ - // Main Image Display (selected image) + // Main Image Display in PageView Expanded( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: InteractiveViewer( + child: PageView.builder( + itemCount: widget.images.length, + controller: pageController, + onPageChanged: (index) { + setState(() { + selectedIndex = index; // Update the selected index to reflect the current page + }); - panEnabled: true, - // Allow panning - scaleEnabled: true, - // Allow zooming - minScale: 1.0, - // Minimum zoom level - maxScale: 4.0, - child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: (widget.images[selectedIndex].isFromNetwork ?? false) - ? widget.images[selectedIndex].imageUrl.buildNetworkImage( - fit: BoxFit.cover, - width: double.infinity, - height: double.infinity, - ) - : widget.images[selectedIndex].imagePath.buildFileImage( - fit: BoxFit.cover, - width: double.infinity, - height: double.infinity, - ), + // Use proper duration and curve + pageController.animateToPage( + index, + duration: const Duration(milliseconds: 500), // Correct duration in milliseconds + curve: Curves.easeInOut, // Smooth easing curve + ); + }, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: InteractiveViewer( + panEnabled: true, + scaleEnabled: true, + minScale: 1.0, + maxScale: 4.0, + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: (widget.images[index].isFromNetwork ?? false) + ? widget.images[index].imageUrl.buildNetworkImage( + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + ) + : widget.images[index].imagePath.buildFileImage( + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + ), + ), + ), ), - ), - ), + ); + }, ), ), // Thumbnail List at the bottom @@ -71,8 +83,15 @@ class MediaViewerScreenState extends State { return GestureDetector( onTap: () { setState(() { - selectedIndex = index; // Change the selected image + selectedIndex = index; // Update the selected index to reflect the current page }); + + // Use proper duration and curve + pageController.animateToPage( + index, + duration: const Duration(milliseconds: 100), // Correct duration in milliseconds + curve: Curves.easeInOut, // Smooth easing curve + ); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), @@ -106,7 +125,6 @@ class MediaViewerScreenState extends State { }, ), ), - 10.height, ], ), ); diff --git a/lib/widgets/tab/role_type_tab.dart b/lib/widgets/tab/role_type_tab.dart index 689bbfb..458b546 100644 --- a/lib/widgets/tab/role_type_tab.dart +++ b/lib/widgets/tab/role_type_tab.dart @@ -7,11 +7,11 @@ import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; class RoleTypeTab extends StatefulWidget { int selectedIndex; - List dropList; - Function(DropValue value) onSelect; - double? width; + final List dropList; + final Function(DropValue value) onSelect; + final double? width; - RoleTypeTab(this.selectedIndex, this.dropList, {required this.onSelect, this.width}); + RoleTypeTab(this.selectedIndex, this.dropList, {super.key, required this.onSelect, this.width}); @override State createState() => _RoleTypeTabState(); diff --git a/pubspec.yaml b/pubspec.yaml index affefda..dfc2ea3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,9 +16,9 @@ dependencies: injector: ^4.0.0 provider: ^6.0.0 easy_localization: ^3.0.3 - http: ^0.13.6 + http: ^1.2.2 permission_handler: ^11.3.1 - flutter_svg: ^2.0.9 + flutter_svg: ^2.0.14 sizer: ^3.0.4 fluttertoast: ^8.0.8 shared_preferences: ^2.0.6 @@ -61,9 +61,9 @@ dependencies: # Auth local_auth: ^2.2.0 - #huawei_fido: ^6.3.0+305 +# huawei_fido: ^6.3.0+305 device_info_plus: ^11.0.0 - flutter_lints: any + dev_dependencies: flutter_test: