diff --git a/VIEWONLY_CUSTOMERID_CHANGES.md b/VIEWONLY_CUSTOMERID_CHANGES.md new file mode 100644 index 0000000..9551f0d --- /dev/null +++ b/VIEWONLY_CUSTOMERID_CHANGES.md @@ -0,0 +1,63 @@ +# View-Only Mode: CustomerID Parameter Exclusion + +## Summary +Updated all API calls to exclude `customerID` parameter when `AppState().getIsViewOnly` is `true`. + +## Files Modified + +### 1. **request_repo.dart** +- ✅ `createRequest()` - Excludes customerID in view-only mode +- ✅ `getRequests()` - Excludes customerID for customer app type in view-only mode +- ✅ `getRequestBasedOnFilters()` - Excludes customerID for customer app type in view-only mode +- ✅ `getOffersFromProvidersByRequest()` - Excludes customerID in view-only mode + +### 2. **ads_repo.dart** +- ✅ `cancelMyAdReservation()` - Excludes customerID in view-only mode +- ✅ `createReserveAd()` - Excludes customerID in view-only mode +- ✅ `uploadBankReceiptsOnReserveDealDone()` - Excludes customerID in view-only mode + +### 3. **appointment_repo.dart** +- ✅ `createAppointmentsMulti()` - Excludes customerID in view-only mode +- ✅ `getMyAppointmentsForCustomersByFilters()` - Excludes customerID in view-only mode + +### 4. **branch_repo.dart** +- ✅ `submitBranchRatings()` - Excludes customerID in view-only mode +- ✅ `addProviderToFavourite()` - Excludes customerID in view-only mode +- ✅ `getMyFavoriteProviders()` - Excludes customerID in view-only mode + +### 5. **setting_options_repo.dart** +- ✅ `appInvitationCreate()` - Excludes customerID for customer app type in view-only mode + +## How It Works + +When `AppState().getIsViewOnly` is `true`, the application is in demonstration/view-only mode. In this mode: + +1. **API parameters are conditionally added**: Instead of always including `customerID`, we check the view-only flag first +2. **Pattern used**: + ```dart + var params = { + "otherParam": value, + }; + + if (!appState.getIsViewOnly) { + params["customerID"] = customerId; + } + ``` + +3. **App types considered**: The changes properly handle both customer and provider app types + +## Testing Recommendations + +1. Test in normal mode (getIsViewOnly = false): + - All APIs should include customerID as before + - All existing functionality should work + +2. Test in view-only mode (getIsViewOnly = true): + - APIs should NOT include customerID parameter + - Read-only operations should work + - Write operations should be blocked by view-only mode + +## Note + +The `createComplainFromProvider()` method in `common_repo.dart` was NOT modified because the `customerID` in that API represents the target customer being complained about, not the authenticated user making the request. + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 63ab985..3567120 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -413,6 +413,13 @@ "deleteAdConfirmationMessage": "سيتم حذف إعلانك نهائيًا ولا يمكنك التراجع عن هذا الإجراء", "mileage": "المسافة المقطوعة", "transmission": "ناقل الحركة", + "odometer": "عداد المسافات", + "accidentFree": "خالية من الحوادث", + "brandNew": "جديدة", + "used": "مستعملة", + "hasYourCarHadAccident": "هل تعرضت سيارتك لأي حادث؟", + "odometerReading": "قراءة عداد المسافات (كم)", + "currentlyInUse": "قيد الاستخدام حاليًا", "demand": "الطلب", "adDurationExpired": "انتهت مدة عرض الإعلان الخاص بك", "bankDetails": "تفاصيل البنك", @@ -720,6 +727,8 @@ "ownerInformation": "معلومات المالك", "acceptedRequests": "الطلبات المقبولة", "specialRequestChat": "دردشة الطلب الخاص", + "sparePartRequestChat": "دردشة طلب قطع الغيار", + "specialCarRequestChat": "دردشة طلب السيارة الخاصة", "companyName": "اسم الشركة", "noAvailableItems": "لا توجد عناصر متاحة.", "serviceDeliveryType": "نوع تقديم الخدمة", @@ -814,5 +823,7 @@ "selfPickupStatus": "حالة الالتقاط الذاتي", "myDraftAds": "مسودتي للإعلانات", "scheduleDeletedSuccessfully": "تم حذف الجدول بنجاح", - "addValidAddress": "رجى إضافة عنوان صالح" + "addValidAddress": "رجى إضافة عنوان صالح", + "termsPrivacyPolicy": "الشروط وسياسة الخصوصية" + } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 299d4c0..0e72d6e 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -414,6 +414,13 @@ "deleteAdConfirmationMessage": "Your ad will be permanently deleted and you cannot undo this action", "mileage": "Mileage", "transmission": "Transmission", + "odometer": "Odometer", + "accidentFree": "Accident Free", + "brandNew": "Brand New", + "used": "Used", + "hasYourCarHadAccident": "Has your car had any accident?", + "odometerReading": "Odometer Reading (Km)", + "currentlyInUse": "Currently in Use", "demand": "Demand", "adDurationExpired": "Your Ad Duration time is over", "bankDetails": "Bank Details", @@ -715,6 +722,8 @@ "noItemsToShow": "There are no Items no show.", "acceptedRequests": "Accepted Requests", "specialRequestChat": "Special Request Chat", + "sparePartRequestChat": "Spare Part Request Chat", + "specialCarRequestChat": "Special Car Request Chat", "companyName": "Company Name", "noAvailableItems": "There are no available items.", "serviceDeliveryType": "Service Delivery Type", @@ -812,5 +821,6 @@ "selfPickupStatus": "Self Pickup Status", "myDraftAds": "My Draft Ads", "scheduleDeletedSuccessfully": "The schedule has been deleted successfully", - "addValidAddress": "Please add a valid address" + "addValidAddress": "Please add a valid address", + "termsPrivacyPolicy": "Terms & Privacy Policy" } \ No newline at end of file diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index e0571b3..48bdef2 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -1,6 +1,6 @@ // DO NOT EDIT. This is code generated via package:easy_localization/generate.dart -// ignore_for_file: prefer_single_quotes, avoid_renaming_method_parameters +// ignore_for_file: prefer_single_quotes, avoid_renaming_method_parameters, constant_identifier_names import 'dart:ui'; @@ -14,7 +14,7 @@ class CodegenLoader extends AssetLoader{ return Future.value(mapLocales[locale.toString()]); } - static const Map ar_SA = { + static const Map _ar_SA = { "firstTimeLogIn": "تسجيل الدخول لأول مره", "signUp": "التسجيل", "changeMobile": "تغيير رقم الجوال", @@ -429,6 +429,13 @@ class CodegenLoader extends AssetLoader{ "deleteAdConfirmationMessage": "سيتم حذف إعلانك نهائيًا ولا يمكنك التراجع عن هذا الإجراء", "mileage": "المسافة المقطوعة", "transmission": "ناقل الحركة", + "odometer": "عداد المسافات", + "accidentFree": "خالية من الحوادث", + "brandNew": "جديدة", + "used": "مستعملة", + "hasYourCarHadAccident": "هل تعرضت سيارتك لأي حادث؟", + "odometerReading": "قراءة عداد المسافات (كم)", + "currentlyInUse": "قيد الاستخدام حاليًا", "demand": "الطلب", "adDurationExpired": "انتهت مدة عرض الإعلان الخاص بك", "bankDetails": "تفاصيل البنك", @@ -736,6 +743,8 @@ class CodegenLoader extends AssetLoader{ "ownerInformation": "معلومات المالك", "acceptedRequests": "الطلبات المقبولة", "specialRequestChat": "دردشة الطلب الخاص", + "sparePartRequestChat": "دردشة طلب قطع الغيار", + "specialCarRequestChat": "دردشة طلب السيارة الخاصة", "companyName": "اسم الشركة", "noAvailableItems": "لا توجد عناصر متاحة.", "serviceDeliveryType": "نوع تقديم الخدمة", @@ -830,9 +839,10 @@ class CodegenLoader extends AssetLoader{ "selfPickupStatus": "حالة الالتقاط الذاتي", "myDraftAds": "مسودتي للإعلانات", "scheduleDeletedSuccessfully": "تم حذف الجدول بنجاح", - "addValidAddress": "رجى إضافة عنوان صالح" + "addValidAddress": "رجى إضافة عنوان صالح", + "termsPrivacyPolicy": "الشروط وسياسة الخصوصية" }; -static const Map en_US = { +static const Map _en_US = { "firstTimeLogIn": "First Time Log In", "signUp": "Sign Up", "changeMobile": "Change Mobile", @@ -1248,6 +1258,13 @@ static const Map en_US = { "deleteAdConfirmationMessage": "Your ad will be permanently deleted and you cannot undo this action", "mileage": "Mileage", "transmission": "Transmission", + "odometer": "Odometer", + "accidentFree": "Accident Free", + "brandNew": "Brand New", + "used": "Used", + "hasYourCarHadAccident": "Has your car had any accident?", + "odometerReading": "Odometer Reading (Km)", + "currentlyInUse": "Currently in Use", "demand": "Demand", "adDurationExpired": "Your Ad Duration time is over", "bankDetails": "Bank Details", @@ -1549,6 +1566,8 @@ static const Map en_US = { "noItemsToShow": "There are no Items no show.", "acceptedRequests": "Accepted Requests", "specialRequestChat": "Special Request Chat", + "sparePartRequestChat": "Spare Part Request Chat", + "specialCarRequestChat": "Special Car Request Chat", "companyName": "Company Name", "noAvailableItems": "There are no available items.", "serviceDeliveryType": "Service Delivery Type", @@ -1646,7 +1665,8 @@ static const Map en_US = { "selfPickupStatus": "Self Pickup Status", "myDraftAds": "My Draft Ads", "scheduleDeletedSuccessfully": "The schedule has been deleted successfully", - "addValidAddress": "Please add a valid address" + "addValidAddress": "Please add a valid address", + "termsPrivacyPolicy": "Terms & Privacy Policy" }; -static const Map> mapLocales = {"ar_SA": ar_SA, "en_US": en_US}; +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 cb1efef..ae088cf 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1,5 +1,7 @@ // DO NOT EDIT. This is code generated via package:easy_localization/generate.dart +// ignore_for_file: constant_identifier_names + abstract class LocaleKeys { static const firstTimeLogIn = 'firstTimeLogIn'; static const signUp = 'signUp'; @@ -392,6 +394,13 @@ abstract class LocaleKeys { static const deleteAdConfirmationMessage = 'deleteAdConfirmationMessage'; static const mileage = 'mileage'; static const transmission = 'transmission'; + static const odometer = 'odometer'; + static const accidentFree = 'accidentFree'; + static const brandNew = 'brandNew'; + static const used = 'used'; + static const hasYourCarHadAccident = 'hasYourCarHadAccident'; + static const odometerReading = 'odometerReading'; + static const currentlyInUse = 'currentlyInUse'; static const demand = 'demand'; static const adDurationExpired = 'adDurationExpired'; static const bankDetails = 'bankDetails'; @@ -699,6 +708,8 @@ abstract class LocaleKeys { static const ownerInformation = 'ownerInformation'; static const acceptedRequests = 'acceptedRequests'; static const specialRequestChat = 'specialRequestChat'; + static const sparePartRequestChat = 'sparePartRequestChat'; + static const specialCarRequestChat = 'specialCarRequestChat'; static const companyName = 'companyName'; static const noAvailableItems = 'noAvailableItems'; static const serviceDeliveryType = 'serviceDeliveryType'; @@ -794,5 +805,6 @@ abstract class LocaleKeys { static const myDraftAds = 'myDraftAds'; static const scheduleDeletedSuccessfully = 'scheduleDeletedSuccessfully'; static const addValidAddress = 'addValidAddress'; + static const termsPrivacyPolicy = 'termsPrivacyPolicy'; } diff --git a/lib/models/advertisment_models/ad_details_model.dart b/lib/models/advertisment_models/ad_details_model.dart index 06a1cce..03f340d 100644 --- a/lib/models/advertisment_models/ad_details_model.dart +++ b/lib/models/advertisment_models/ad_details_model.dart @@ -190,7 +190,11 @@ class Vehicle { int? vehicleType; String? vehicleVIN; int? countryID; + String? countryName; String? currency; + int? odometer; + int? isInUsed; + bool? isAccidentFree; Vehicle( {this.id, @@ -217,7 +221,11 @@ class Vehicle { this.vehicleType, this.vehicleVIN, this.countryID, - this.currency}); + this.countryName, + this.currency, + this.odometer, + this.isInUsed, + this.isAccidentFree}); Vehicle.fromJson(Map json) { id = json['id']; @@ -254,7 +262,11 @@ class Vehicle { vehicleType = json['vehicleType']; vehicleVIN = json['vehicleVIN']; countryID = json['countryID']; + countryName = json['duration'] != null && json['duration']['country'] != null ? json['duration']['country']['label'] : null; currency = json['currency']; + odometer = json['odometer']; + isInUsed = json['isInUsed']; + isAccidentFree = json['isAccidentFree']; } } diff --git a/lib/models/general_models/generic_resp_model.dart b/lib/models/general_models/generic_resp_model.dart index 38892c9..6c8d0c0 100644 --- a/lib/models/general_models/generic_resp_model.dart +++ b/lib/models/general_models/generic_resp_model.dart @@ -124,6 +124,9 @@ class VehiclePosting { List? vehiclePostingDamageParts; String? phoneNo; String? whatsAppNo; + int? odometer; + int? isInUsed; + bool? isAccidentFree; VehiclePosting({ this.id, @@ -151,6 +154,9 @@ class VehiclePosting { this.whatsAppNo, this.vehiclePostingImages, this.vehiclePostingDamageParts, + this.odometer, + this.isInUsed, + this.isAccidentFree, }); VehiclePosting.fromJson(Map json) { @@ -177,6 +183,9 @@ class VehiclePosting { adStatus = json['adStatus']; phoneNo = json['phoneNo']; whatsAppNo = json['whatsAppNo']; + odometer = json['odometer']; + isInUsed = json['isInUsed']; + isAccidentFree = json['isAccidentFree']; if (json['vehiclePostingImages'] != null) { vehiclePostingImages = []; json['vehiclePostingImages'].forEach((v) { @@ -193,7 +202,7 @@ class VehiclePosting { @override String toString() { - return 'VehiclePosting{id: $id, userID: $userID, vehicleType: $vehicleType, vehicleModelID: $vehicleModelID, vehicleModelYearID: $vehicleModelYearID, vehicleColorID: $vehicleColorID, vehicleCategoryID: $vehicleCategoryID, vehicleConditionID: $vehicleConditionID, vehicleMileageID: $vehicleMileageID, vehicleTransmissionID: $vehicleTransmissionID, vehicleSellerTypeID: $vehicleSellerTypeID, cityID: $cityID, price: $price, vehicleVIN: $vehicleVIN, vehicleDescription: $vehicleDescription, vehicleTitle: $vehicleTitle, vehicleDescriptionN: $vehicleDescriptionN, isFinanceAvailable: $isFinanceAvailable, warantyYears: $warantyYears, demandAmount: $demandAmount, adStatus: $adStatus, vehiclePostingImages: $vehiclePostingImages, vehiclePostingDamageParts: $vehiclePostingDamageParts, phoneNo: $phoneNo, whatsAppNo: $whatsAppNo}'; + return 'VehiclePosting{id: $id, userID: $userID, vehicleType: $vehicleType, vehicleModelID: $vehicleModelID, vehicleModelYearID: $vehicleModelYearID, vehicleColorID: $vehicleColorID, vehicleCategoryID: $vehicleCategoryID, vehicleConditionID: $vehicleConditionID, vehicleMileageID: $vehicleMileageID, vehicleTransmissionID: $vehicleTransmissionID, vehicleSellerTypeID: $vehicleSellerTypeID, cityID: $cityID, price: $price, vehicleVIN: $vehicleVIN, vehicleDescription: $vehicleDescription, vehicleTitle: $vehicleTitle, vehicleDescriptionN: $vehicleDescriptionN, isFinanceAvailable: $isFinanceAvailable, warantyYears: $warantyYears, demandAmount: $demandAmount, adStatus: $adStatus, vehiclePostingImages: $vehiclePostingImages, vehiclePostingDamageParts: $vehiclePostingDamageParts, phoneNo: $phoneNo, whatsAppNo: $whatsAppNo, odometer: $odometer, isInUsed: $isInUsed, isAccidentFree: $isAccidentFree}'; } } diff --git a/lib/repositories/ads_repo.dart b/lib/repositories/ads_repo.dart index cf55335..c0a3716 100644 --- a/lib/repositories/ads_repo.dart +++ b/lib/repositories/ads_repo.dart @@ -199,6 +199,9 @@ class AdsRepoImp implements AdsRepo { "vehiclePostingDamageParts": vehiclePostingDamageParts, "mobileNo": adsCreationPayloadModel.vehiclePosting!.phoneNo, "whatsAppNo": adsCreationPayloadModel.vehiclePosting!.whatsAppNo, + "odometer": adsCreationPayloadModel.vehiclePosting!.odometer, + "isInUsed": adsCreationPayloadModel.vehiclePosting!.isInUsed, + "isAccidentFree": adsCreationPayloadModel.vehiclePosting!.isAccidentFree, } }; @@ -290,6 +293,9 @@ class AdsRepoImp implements AdsRepo { "vehiclePostingDamagePartsDraft": vehiclePostingDamageParts, "mobileNo": adsCreationPayloadModel.vehiclePosting!.phoneNo, "whatsAppNo": adsCreationPayloadModel.vehiclePosting!.whatsAppNo, + "odometer": adsCreationPayloadModel.vehiclePosting!.odometer, + "isInUsed": adsCreationPayloadModel.vehiclePosting!.isInUsed, + "isAccidentFree": adsCreationPayloadModel.vehiclePosting!.isAccidentFree, // "adStatus": 1, }, "stepNo": stepNo.toIntFromStepsEnum(), @@ -585,11 +591,14 @@ class AdsRepoImp implements AdsRepo { var postParams = { "adsID": adId, - "customerID": customerID, "adsReserveStatus": adsReserveStatus, "comment": reason, }; + if (!AppState().getIsViewOnly) { + postParams["customerID"] = customerID; + } + String token = appState.getUser.data!.accessToken ?? ""; GenericRespModel adsGenericModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), @@ -607,10 +616,13 @@ class AdsRepoImp implements AdsRepo { var postParams = { "adsID": adId, - "customerID": customerID, "adsReserveStatus": 0, }; + if (!AppState().getIsViewOnly) { + postParams["customerID"] = customerID; + } + String token = appState.getUser.data!.accessToken ?? ""; GenericRespModel adsGenericModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), @@ -667,11 +679,14 @@ class AdsRepoImp implements AdsRepo { var postParams = { "adsID": adId, - "customerID": customerId, "detailNote": detailNote, "receiptImages": receiptImages, }; + if (!AppState().getIsViewOnly) { + postParams["customerID"] = customerId; + } + String token = appState.getUser.data!.accessToken ?? ""; GenericRespModel adsGenericModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), diff --git a/lib/repositories/appointment_repo.dart b/lib/repositories/appointment_repo.dart index 2a26f98..4a4d4ef 100644 --- a/lib/repositories/appointment_repo.dart +++ b/lib/repositories/appointment_repo.dart @@ -174,16 +174,21 @@ class AppointmentRepoImp implements AppointmentRepo { slotId = schedule.selectedCustomTimeDateSlotModel!.availableSlots![index].slotId; - mapList.add({ + Map appointmentMap = { "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(), - }); + }; + + if (!appState.getIsViewOnly) { + appointmentMap["customerID"] = customerId; + } + + mapList.add(appointmentMap); } log("maplist: ${mapList.toString()}"); @@ -278,8 +283,7 @@ class AppointmentRepoImp implements AppointmentRepo { List? branchIdsList, AppointmentStatusEnum? appointmentStatusEnum, }) async { - var params = { - "customerID": appState.getUser.data!.userInfo!.customerId.toString(), + var params = { "ServiceProviderIDs": providerIdsList ?? [], "ProviderBranchIDs": branchIdsList ?? [], "ServiceIDs": serviceIdsList ?? [], @@ -287,8 +291,12 @@ class AppointmentRepoImp implements AppointmentRepo { "CategoryIDs": categoryIdsList ?? [], }; + if (!appState.getIsViewOnly) { + params["customerID"] = appState.getUser.data!.userInfo!.customerId.toString(); + } + if (appointmentStatusEnum != null) { - params.addAll({"AppointmentStatusID": appointmentStatusEnum.getIdFromAppointmentStatusEnum().toString()}); + params["AppointmentStatusID"] = appointmentStatusEnum.getIdFromAppointmentStatusEnum().toString(); } GenericRespModel genericRespModel = await apiClient.getJsonForObject( token: appState.getUser.data!.accessToken, diff --git a/lib/repositories/branch_repo.dart b/lib/repositories/branch_repo.dart index dc12110..3f264db 100644 --- a/lib/repositories/branch_repo.dart +++ b/lib/repositories/branch_repo.dart @@ -587,7 +587,11 @@ class BranchRepoImp implements BranchRepo { @override Future submitBranchRatings({required int serviceProviderBranchID, required String title, required String review, required double ratingNo}) async { final customerID = appState.getUser.data!.userInfo!.customerId; - final parameters = {"title": title, "review": review, "ratNo": ratingNo, "serviceProviderBranchID": serviceProviderBranchID, "customerID": "$customerID"}; + final parameters = {"title": title, "review": review, "ratNo": ratingNo, "serviceProviderBranchID": serviceProviderBranchID}; + + if (!appState.getIsViewOnly) { + parameters["customerID"] = "$customerID"; + } GenericRespModel adsGenericModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), ApiConsts.createBranchRatings, @@ -602,8 +606,12 @@ class BranchRepoImp implements BranchRepo { final customerID = appState.getUser.data!.userInfo!.customerId; final parameters = { "providerID": providerID.toString(), - "customerID": customerID.toString(), }; + + if (!appState.getIsViewOnly) { + parameters["customerID"] = customerID.toString(); + } + GenericRespModel adsGenericModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), ApiConsts.favouriteServiceProviderCreate, @@ -629,13 +637,17 @@ class BranchRepoImp implements BranchRepo { Future> getMyFavoriteProviders() async { final customerID = appState.getUser.data!.userInfo!.customerId; - var postParams = {"customerID": customerID.toString()}; + var postParams = {}; + + if (!appState.getIsViewOnly) { + postParams["customerID"] = customerID.toString(); + } GenericRespModel adsGenericModel = await apiClient.getJsonForObject( (json) => GenericRespModel.fromJson(json), ApiConsts.favouriteServiceProviderGet, token: appState.getUser.data!.accessToken, - queryParameters: postParams, + queryParameters: postParams.isNotEmpty ? postParams : null, ); List favProviders = List.generate(adsGenericModel.data.length, (index) => ProviderProfileModel.fromJson(adsGenericModel.data[index])); return favProviders; diff --git a/lib/repositories/request_repo.dart b/lib/repositories/request_repo.dart index 88df75c..021369c 100644 --- a/lib/repositories/request_repo.dart +++ b/lib/repositories/request_repo.dart @@ -98,7 +98,6 @@ class RequestRepoImp implements RequestRepo { required List requestImages, }) async { Map postParams = { - "customerID": appState.getUser.data!.userInfo!.customerId ?? 0, "requestType": requestTypeId, "vehicleTypeID": vehicleTypeId, "brand": brand, @@ -113,6 +112,10 @@ class RequestRepoImp implements RequestRepo { "isSpecialServiceNeeded": false, "requestImages": requestImages, }; + + if (!appState.getIsViewOnly) { + postParams["customerID"] = appState.getUser.data!.userInfo!.customerId ?? 0; + } GenericRespModel enumGenericModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), ApiConsts.createRequest, @@ -132,7 +135,9 @@ class RequestRepoImp implements RequestRepo { if (appState.currentAppType == AppType.provider) { paramsForGetRequests.addEntries([MapEntry("providerID", providerOrCustomerID)]); } else { - paramsForGetRequests.addEntries([MapEntry("customerID", providerOrCustomerID)]); + if (!appState.getIsViewOnly) { + paramsForGetRequests.addEntries([MapEntry("customerID", providerOrCustomerID)]); + } } GenericRespModel enumGenericModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), @@ -180,7 +185,9 @@ class RequestRepoImp implements RequestRepo { }; if (appState.currentAppType == AppType.customer) { - paramsForGetRequests.addEntries([MapEntry("customerID", appState.getUser.data!.userInfo!.customerId ?? 0)]); + if (!appState.getIsViewOnly) { + paramsForGetRequests.addEntries([MapEntry("customerID", appState.getUser.data!.userInfo!.customerId ?? 0)]); + } } else { paramsForGetRequests.addEntries([MapEntry("providerID", appState.getUser.data!.userInfo!.providerId.toString())]); } @@ -322,9 +329,12 @@ class RequestRepoImp implements RequestRepo { Future getOffersFromProvidersByRequest({required int requestId}) async { final customerId = appState.getUser.data!.userInfo!.customerId; var queryParameters = { - "customerID": customerId.toString(), "requestID": requestId.toString(), }; + + if (!appState.getIsViewOnly) { + queryParameters["customerID"] = customerId.toString(); + } GenericRespModel genericRespModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), ApiConsts.requestOffersSpsGet, diff --git a/lib/repositories/setting_options_repo.dart b/lib/repositories/setting_options_repo.dart index dda9f2e..1e5a899 100644 --- a/lib/repositories/setting_options_repo.dart +++ b/lib/repositories/setting_options_repo.dart @@ -166,10 +166,16 @@ class SettingOptionsRepoImp extends SettingOptionsRepo { }; if (AppState().currentAppType == AppType.customer) { - params.addAll({ - "customerID": (appState.getUser.data!.userInfo!.customerId ?? "0").toString(), - "channelID": "3", - }); + if (!appState.getIsViewOnly) { + params.addAll({ + "customerID": (appState.getUser.data!.userInfo!.customerId ?? "0").toString(), + "channelID": "3", + }); + } else { + params.addAll({ + "channelID": "3", + }); + } } else { params.addAll({ "providerID": (appState.getUser.data!.userInfo!.providerId ?? "0").toString(), diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index ef2d521..2467ca7 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -729,6 +729,29 @@ class AdVM extends BaseVM { vehicleDemandAmount = amount; } + String odometer = ""; + + void updateOdometer(String value) { + odometer = value; + notifyListeners(); + } + + int isInUsed = 0; + + void updateIsInUsed(int value) { + isInUsed = value; + notifyListeners(); + } + + bool isAccidentFree = false; + bool isAccidentFreeSet = false; + + void updateIsAccidentFree(bool value) { + isAccidentFree = value; + isAccidentFreeSet = true; + notifyListeners(); + } + String reservationCancelReason = ""; String reservationCancelError = ""; @@ -1310,6 +1333,9 @@ class AdVM extends BaseVM { vehiclePostingDamageParts: vehicleDamageImages, phoneNo: isPhoneNumberShown ? adPhoneNumberDialCode + adPhoneNumber : null, whatsAppNo: (isPhoneNumberShown && isNumberOnWhatsApp) ? adPhoneNumberDialCode + adPhoneNumber : null, + odometer: odometer.isNotEmpty ? int.tryParse(odometer) : 0, + isInUsed: isInUsed, + isAccidentFree: isAccidentFree, ); AdsCreationPayloadModel adsCreationPayloadModel = AdsCreationPayloadModel(ads: ads, vehiclePosting: vehiclePosting); @@ -1437,7 +1463,7 @@ class AdVM extends BaseVM { // Added By Aamir if (pickedPostingImages.length > GlobalConsts.maxFileCount) { pickedPostingImages = pickedPostingImages.sublist(0, GlobalConsts.maxFileCount); - Utils.showToast(LocaleKeys.maxFileSelection); + Utils.showToast(LocaleKeys.maxFileSelection.tr()); } if (pickedPostingImages.isNotEmpty) vehicleImageError = ""; @@ -1485,7 +1511,7 @@ class AdVM extends BaseVM { // Added By Aamir if (vehicleDamageCards[index].partImages!.length > GlobalConsts.maxFileCount) { vehicleDamageCards[index].partImages = vehicleDamageCards[index].partImages!.sublist(0, GlobalConsts.maxFileCount); - Utils.showToast(LocaleKeys.maxFileSelection); + Utils.showToast(LocaleKeys.maxFileSelection.tr()); } } @@ -1541,7 +1567,7 @@ class AdVM extends BaseVM { if (pickedDamageImages.length > GlobalConsts.maxFileCount) { pickedDamageImages = pickedDamageImages.sublist(0, GlobalConsts.maxFileCount); - Utils.showToast(LocaleKeys.maxFileSelection); + Utils.showToast(LocaleKeys.maxFileSelection.tr()); } if (pickedDamageImages.isNotEmpty) vehicleDamageImageError = ""; notifyListeners(); @@ -1577,6 +1603,10 @@ class AdVM extends BaseVM { isNumberOnWhatsApp = false; adPhoneNumberDialCode = ""; adPhoneNumber = ""; + odometer = ""; + isInUsed = 0; + isAccidentFree = false; + isAccidentFreeSet = false; clearSpecialServiceCard(); updateFinanceAvailableStatus(false); notifyListeners(); @@ -1792,6 +1822,9 @@ class AdVM extends BaseVM { vehiclePostingDamageParts: vehicleDamageImages, phoneNo: isPhoneNumberShown ? adPhoneNumberDialCode + adPhoneNumber : null, whatsAppNo: (isPhoneNumberShown && isNumberOnWhatsApp) ? adPhoneNumberDialCode + adPhoneNumber : null, + odometer: odometer.isNotEmpty ? int.tryParse(odometer) : 0, + isInUsed: isInUsed, + isAccidentFree: isAccidentFree, ); AdsCreationPayloadModel adsCreationPayloadModel = AdsCreationPayloadModel(ads: ads, vehiclePosting: vehiclePosting); @@ -2455,6 +2488,10 @@ class AdVM extends BaseVM { vehicleTitle = previousAdDetails!.vehicle!.vehicleTitle.toString(); vehicleDescription = previousAdDetails!.vehicle!.vehicleDescription.toString(); financeAvailableStatus = previousAdDetails!.vehicle!.isFinanceAvailable ?? false; + odometer = previousAdDetails!.vehicle!.odometer != null ? previousAdDetails!.vehicle!.odometer.toString() : "0"; + isInUsed = previousAdDetails!.vehicle!.isInUsed ?? 0; + isAccidentFree = previousAdDetails!.vehicle!.isAccidentFree ?? false; + isAccidentFreeSet = true; pickedPostingImages.clear(); if (previousAdDetails!.vehicle!.image != null && previousAdDetails!.vehicle!.image!.isNotEmpty) { for (var element in previousAdDetails!.vehicle!.image!) { diff --git a/lib/view_models/chat_view_model.dart b/lib/view_models/chat_view_model.dart index 0de3c45..fd09446 100644 --- a/lib/view_models/chat_view_model.dart +++ b/lib/view_models/chat_view_model.dart @@ -447,7 +447,7 @@ class ChatVM extends BaseVM { pickedImagesForMessage.addAll(imageModels); if (pickedImagesForMessage.length > GlobalConsts.maxFileCount) { pickedImagesForMessage = pickedImagesForMessage.sublist(0, GlobalConsts.maxFileCount); - Utils.showToast(LocaleKeys.maxFileSelection); + Utils.showToast(LocaleKeys.maxFileSelection.tr()); } notifyListeners(); diff --git a/lib/view_models/requests_view_model.dart b/lib/view_models/requests_view_model.dart index 85e8b6f..94dc060 100644 --- a/lib/view_models/requests_view_model.dart +++ b/lib/view_models/requests_view_model.dart @@ -294,7 +294,7 @@ class RequestsVM extends BaseVM { pickedVehicleImages.addAll(imageModels); if (pickedVehicleImages.length > GlobalConsts.maxFileCount) { pickedVehicleImages = pickedVehicleImages.sublist(0, GlobalConsts.maxFileCount); - Utils.showToast(LocaleKeys.maxFileSelection); + Utils.showToast(LocaleKeys.maxFileSelection.tr()); } if (pickedVehicleImages.isNotEmpty) vehicleImageError = ""; diff --git a/lib/view_models/service_view_model.dart b/lib/view_models/service_view_model.dart index be89fe7..959f02a 100644 --- a/lib/view_models/service_view_model.dart +++ b/lib/view_models/service_view_model.dart @@ -142,7 +142,7 @@ class ServiceVM extends BaseVM { pickedBranchImages.addAll(imageModels); if (pickedBranchImages.length > GlobalConsts.maxFileCount) { pickedBranchImages = pickedBranchImages.sublist(0, GlobalConsts.maxFileCount); - Utils.showToast(LocaleKeys.maxFileSelection); + Utils.showToast(LocaleKeys.maxFileSelection.tr()); } if (pickedBranchImages.isNotEmpty) branchImageError = ""; notifyListeners(); 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 af32a90..ab785b2 100644 --- a/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart +++ b/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart @@ -78,6 +78,10 @@ class VehicleDetailsReview extends StatelessWidget { 16.height, SingleDetailWidget(text: adVM.vehicleCityId.selectedOption, type: LocaleKeys.vehicleCity.tr()), 16.height, + SingleDetailWidget(text: adVM.isAccidentFree ? LocaleKeys.yes.tr() : LocaleKeys.no.tr(), type: LocaleKeys.accidentFree.tr()), + 16.height, + SingleDetailWidget(text: "${adVM.odometer} Km", type: LocaleKeys.odometer.tr()), + 16.height, SingleDetailWidget(text: adVM.vehicleVin, type: LocaleKeys.vehicleVIN.tr()), 16.height, SingleDetailWidget(text: ("${adVM.warrantyDuration} ${LocaleKeys.years.tr()}"), type: LocaleKeys.warrantyAvailable.tr()), @@ -104,7 +108,9 @@ class VehicleDetailsReview extends StatelessWidget { 16.height, SingleDetailWidget(text: adVM.vehicleTitle, type: LocaleKeys.vehicleTitle.tr()), 16.height, - SingleDetailWidget(text: adVM.financeAvailableStatus ? "Yes" : "No", type: LocaleKeys.financeAvailable.tr()), + SingleDetailWidget(text: adVM.financeAvailableStatus ? LocaleKeys.yes.tr() : LocaleKeys.no.tr(), type: LocaleKeys.financeAvailable.tr()), + 16.height, + SingleDetailWidget(text: adVM.isInUsed == 1 ? LocaleKeys.yes.tr() : LocaleKeys.no.tr(), type: LocaleKeys.currentlyInUse.tr()), 16.height, ], ), 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 1b64231..2b5bf2b 100644 --- a/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart +++ b/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart @@ -237,6 +237,28 @@ class VehicleDetails extends StatelessWidget { errorValue: adVM.vehicleCityId.errorValue, ); }), + 8.height, + Builder(builder: (context) { + List accidentFreeDrop = [ + DropValue(1, LocaleKeys.yes.tr(), ""), + DropValue(0, LocaleKeys.no.tr(), ""), + ]; + return DropdownField( + (DropValue value) => adVM.updateIsAccidentFree(value.id == 1), + list: accidentFreeDrop, + dropdownValue: adVM.isAccidentFreeSet ? DropValue(adVM.isAccidentFree ? 1 : 0, adVM.isAccidentFree ? LocaleKeys.yes.tr() : LocaleKeys.no.tr(), "") : null, + hint: LocaleKeys.hasYourCarHadAccident.tr(), + errorValue: "", + ); + }), + 8.height, + TxtField( + value: adVM.odometer, + keyboardType: TextInputType.number, + numbersOnly: true, + hint: LocaleKeys.odometerReading.tr(), + onChanged: (v) => adVM.updateOdometer(v), + ), ], ], 8.height, @@ -282,28 +304,71 @@ class VehicleDetails extends StatelessWidget { onChanged: (v) => adVM.updateVehicleDescription(v), ), 22.height, - LocaleKeys.financeAvailable.tr().toText(fontSize: 16), - 8.height, - Container( - width: 50, - height: 30, - decoration: BoxDecoration( - color: adVM.financeAvailableStatus ? MyColors.darkPrimaryColor : MyColors.white, - borderRadius: BorderRadius.circular(25.0), - border: Border.all(color: MyColors.black, width: 1), - ), - child: Transform.scale( - scale: 0.8, - child: CupertinoSwitch( - activeColor: MyColors.darkPrimaryColor, - trackColor: MyColors.white, - thumbColor: MyColors.grey98Color, - value: adVM.financeAvailableStatus, - onChanged: (value) { - adVM.updateFinanceAvailableStatus(value); - }, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.financeAvailable.tr().toText(fontSize: 16), + 8.height, + Container( + width: 50, + height: 30, + decoration: BoxDecoration( + color: adVM.financeAvailableStatus ? MyColors.darkPrimaryColor : MyColors.white, + borderRadius: BorderRadius.circular(25.0), + border: Border.all(color: MyColors.black, width: 1), + ), + child: Transform.scale( + scale: 0.8, + child: CupertinoSwitch( + activeColor: MyColors.darkPrimaryColor, + trackColor: MyColors.white, + thumbColor: MyColors.grey98Color, + value: adVM.financeAvailableStatus, + onChanged: (value) { + adVM.updateFinanceAvailableStatus(value); + }, + ), + ), + ), + ], + ), ), - ), + 8.width, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.currentlyInUse.tr().toText(fontSize: 16), + 8.height, + Container( + width: 50, + height: 30, + decoration: BoxDecoration( + color: adVM.isInUsed == 1 ? MyColors.darkPrimaryColor : MyColors.white, + borderRadius: BorderRadius.circular(25.0), + border: Border.all(color: MyColors.black, width: 1), + ), + child: Transform.scale( + scale: 0.8, + child: CupertinoSwitch( + activeColor: MyColors.darkPrimaryColor, + trackColor: MyColors.white, + thumbColor: MyColors.grey98Color, + value: adVM.isInUsed == 1, + onChanged: (value) { + adVM.updateIsInUsed(value ? 1 : 0); + }, + ), + ), + ), + ], + ), + ), + ], ), 28.height, LocaleKeys.vehiclePictures.tr().toText(fontSize: 18, isBold: true), 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 9a7c175..a6da85c 100644 --- a/lib/views/advertisement/ads_detail_view/ads_detail_view.dart +++ b/lib/views/advertisement/ads_detail_view/ads_detail_view.dart @@ -1,7 +1,5 @@ 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'; @@ -106,13 +104,20 @@ class _AdsDetailViewState extends State { children: [ Row( children: [ - ("${LocaleKeys.model.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), - "${widget.adDetails.vehicle!.modelyear!.label}".toText(fontSize: 12), + ("${LocaleKeys.vehicleModel.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), + "${widget.adDetails.vehicle!.model!.label}".toText(fontSize: 12), ], ), - "${widget.adDetails.vehicle!.countryID}".toText(fontSize: 10, color: MyColors.lightTextColor), + (widget.adDetails.vehicle!.countryName ?? '').toText(fontSize: 10, color: MyColors.lightTextColor), ], ), + Row( + children: [ + ("${LocaleKeys.vehicleModelYear.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), + "${widget.adDetails.vehicle!.modelyear!.label}".toText(fontSize: 12), + ], + ), + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -131,6 +136,24 @@ class _AdsDetailViewState extends State { "${widget.adDetails.vehicle!.transmission!.label}".toText(fontSize: 12), ], ), + Row( + children: [ + ("${LocaleKeys.odometer.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), + "${widget.adDetails.vehicle!.odometer ?? 0} Km".toText(fontSize: 12), + ], + ), + Row( + children: [ + ("${LocaleKeys.vehicleCondition.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), + (widget.adDetails.vehicle!.isInUsed == 0 ? LocaleKeys.brandNew.tr() : LocaleKeys.used.tr()).toText(fontSize: 12), + ], + ), + Row( + children: [ + ("${LocaleKeys.accidentFree.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), + ((widget.adDetails.vehicle!.isAccidentFree ?? false) ? LocaleKeys.yes.tr() : LocaleKeys.no.tr()).toText(fontSize: 12), + ], + ), Row( children: [ ("${LocaleKeys.financeAvailable.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), @@ -390,7 +413,7 @@ class _AdsDetailViewState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible(child: ("${widget.adDetails.specialservice![index].name}").toText(fontSize: 14, color: MyColors.lightTextColor)), - ("${widget.adDetails.specialservice![index].price} ${LocaleKeys.sar.tr()} ").toText(fontSize: 14, color: MyColors.lightTextColor), + ("${widget.adDetails.specialservice![index].price} ${widget.adDetails.vehicle!.currency ?? LocaleKeys.sar.tr()} ").toText(fontSize: 14, color: MyColors.lightTextColor), ], )), ), diff --git a/lib/views/advertisement/ads_detail_view/components.dart b/lib/views/advertisement/ads_detail_view/components.dart index 9fdabe1..6824f93 100644 --- a/lib/views/advertisement/ads_detail_view/components.dart +++ b/lib/views/advertisement/ads_detail_view/components.dart @@ -56,7 +56,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { children: [ "${adDetailsModel.reservePrice}".toText(fontSize: 19, isBold: true), 2.width, - LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, fontSize: 10, isBold: true).paddingOnly(bottom: 3), + (adDetailsModel.vehicle!.currency ?? LocaleKeys.sar.tr()).toText(color: MyColors.lightTextColor, fontSize: 10, isBold: true).paddingOnly(bottom: 3), ], ) ], @@ -73,7 +73,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { children: [ "${adDetailsModel.vehicle!.demandAmount ?? 0.0}".toText(fontSize: 19, isBold: true), 2.width, - LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, fontSize: 10, isBold: true).paddingOnly(bottom: 3), + (adDetailsModel.vehicle!.currency ?? LocaleKeys.sar.tr()).toText(color: MyColors.lightTextColor, fontSize: 10, isBold: true).paddingOnly(bottom: 3), ], ) ], @@ -123,7 +123,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { children: [ "${(adDetailsModel.vehicle!.demandAmount ?? 0.0)}".toText(fontSize: 19, isBold: true), 2.width, - LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, fontSize: 10, isBold: true).paddingOnly(bottom: 3), + (adDetailsModel.vehicle!.currency ?? LocaleKeys.sar.tr()).toText(color: MyColors.lightTextColor, fontSize: 10, isBold: true).paddingOnly(bottom: 3), ], ) ], diff --git a/lib/views/advertisement/ads_filter_view.dart b/lib/views/advertisement/ads_filter_view.dart index e6c1e4a..dab1f1d 100644 --- a/lib/views/advertisement/ads_filter_view.dart +++ b/lib/views/advertisement/ads_filter_view.dart @@ -349,6 +349,7 @@ class _AdsFilterViewState extends State { title: LocaleKeys.search.tr(), onPressed: () { Navigator.pop(context); + adVM.updateIsExploreAds(true); adVM.getAdsBasedOnFilters(isFromLazyLoad: false, pageIndex: 0); }, backgroundColor: MyColors.darkPrimaryColor, @@ -360,7 +361,10 @@ class _AdsFilterViewState extends State { ), 8.height, InkWell( - onTap: () => adVM.clearAdsFilters(), + onTap: () { + adVM.updateIsExploreAds(true); + adVM.clearAdsFilters(); + }, child: LocaleKeys.clearFilters.tr().toText( fontSize: 14, isBold: true, diff --git a/lib/views/advertisement/bottom_sheets/ads_damage_part_pictures_sheet.dart b/lib/views/advertisement/bottom_sheets/ads_damage_part_pictures_sheet.dart index 9c3e1f4..0334f6e 100644 --- a/lib/views/advertisement/bottom_sheets/ads_damage_part_pictures_sheet.dart +++ b/lib/views/advertisement/bottom_sheets/ads_damage_part_pictures_sheet.dart @@ -36,17 +36,21 @@ class _AdDamagePartPicturesSheetState extends State { void populateDamagePartPictures() { List adDamageReportList = widget.adDamageReportList; // Assuming this is your data source. - Map groupedDamageCards = {}; + + Map groupedDamageCards = {}; for (var element in adDamageReportList) { + int partId = element.vehicleDamagePartID ?? 0; String partName = element.partName ?? "Unknown"; + ImageModel imageModel = ImageModel(id: element.id!, filePath: element.imageUrl!, isFromNetwork: true); - if (groupedDamageCards.containsKey(partName)) { - groupedDamageCards[partName]!.partImages!.add(imageModel); + + if (groupedDamageCards.containsKey(partId)) { + groupedDamageCards[partId]!.partImages!.add(imageModel); } else { - groupedDamageCards[partName] = VehicleDamageCard( + groupedDamageCards[partId] = VehicleDamageCard( partSelectedId: SelectionModel( - selectedId: element.vehicleDamagePartID!, + selectedId: partId, selectedOption: partName, ), damagePartDescription: element.comment ?? "", diff --git a/lib/views/advertisement/components/ads_list_widget.dart b/lib/views/advertisement/components/ads_list_widget.dart index b74d27c..e1be33c 100644 --- a/lib/views/advertisement/components/ads_list_widget.dart +++ b/lib/views/advertisement/components/ads_list_widget.dart @@ -39,11 +39,30 @@ class AdsListWidget extends StatelessWidget { @override Widget build(BuildContext context) { + final adVM = context.watch(); + if (isAdsFragment && adsList.isEmpty) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - LocaleKeys.noAdsShow.tr().toText(fontSize: 16, color: MyColors.lightTextColor, fontWeight: MyFonts.Medium), + const SizedBox(height: 25), + (adVM.adsFiltersCounter > 0 ? "No Ads that matches your search" : LocaleKeys.noAdsShow.tr()).toText(fontSize: 16, color: MyColors.lightTextColor, fontWeight: MyFonts.Medium), + if (adVM.adsFiltersCounter > 0) ...[ + const SizedBox(height: 16), + GestureDetector( + onTap: () { + adVM.clearAdsFilters(); + }, + child: Text( + LocaleKeys.clearFilters.tr(), + style: const TextStyle( + color: MyColors.darkPrimaryColor, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ), + ], ], ); } diff --git a/lib/views/appointments/appointment_detail_view.dart b/lib/views/appointments/appointment_detail_view.dart index 8ef0594..9bc8fd1 100644 --- a/lib/views/appointments/appointment_detail_view.dart +++ b/lib/views/appointments/appointment_detail_view.dart @@ -328,7 +328,16 @@ class AppointmentDetailView extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - ((service.currentTotalServicePrice).toString()).toText(fontSize: 19, isBold: true), + if (service.isHomeSelected) ...[ + Builder(builder: (context) { + double totalKms = double.parse(service.servicelocationInfo.distanceToBranch.toStringAsFixed(2)); + double pricePerKm = double.parse(service.servicelocationInfo.homeChargesInCurrentService.toStringAsFixed(2)); + return ((service.currentTotalServicePrice + (pricePerKm * totalKms)).toStringAsFixed(2)) + .toText(fontSize: 19, isBold: true); + }), + ] else ...[ + ((service.currentTotalServicePrice).toString()).toText(fontSize: 19, isBold: true), + ], 2.width, LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, fontSize: 12, isBold: true).paddingOnly(bottom: 5), const Icon(Icons.arrow_drop_down, size: 25) diff --git a/lib/views/appointments/book_appointment_schedules_view.dart b/lib/views/appointments/book_appointment_schedules_view.dart index 51fe789..a3c8856 100644 --- a/lib/views/appointments/book_appointment_schedules_view.dart +++ b/lib/views/appointments/book_appointment_schedules_view.dart @@ -102,11 +102,12 @@ class BookAppointmentSchedulesView extends StatelessWidget { Column( children: [ CustomCalenderAppointmentWidget( - customTimeDateSlotList: scheduleData.customTimeDateSlotList ?? [], - onDateSelected: (int dateIndex) { - appointmentsVM.updateSelectedAppointmentDate(scheduleIndex: scheduleIndex, dateIndex: dateIndex); - }, - selectedCustomTimeDateSlotModel: scheduleData.selectedCustomTimeDateSlotModel), + customTimeDateSlotList: scheduleData.customTimeDateSlotList ?? [], + onDateSelected: (int dateIndex) { + appointmentsVM.updateSelectedAppointmentDate(scheduleIndex: scheduleIndex, dateIndex: dateIndex); + }, + selectedCustomTimeDateSlotModel: scheduleData.selectedCustomTimeDateSlotModel, + ), if (appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex != null) ...[ 5.height, Row( diff --git a/lib/views/appointments/book_appointment_services_view.dart b/lib/views/appointments/book_appointment_services_view.dart index 95d5fea..a8cf8e3 100644 --- a/lib/views/appointments/book_appointment_services_view.dart +++ b/lib/views/appointments/book_appointment_services_view.dart @@ -107,8 +107,12 @@ class BookAppointmentServicesView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ if (serviceData.isHomeSelected) ...[ - ((serviceData.currentTotalServicePrice + (serviceData.servicelocationInfo.distanceToBranch * double.parse(serviceData.rangePricePerKm!))).toStringAsFixed(2)) - .toText(fontSize: 32, isBold: true), + Builder(builder: (context) { + double totalKms = double.parse(serviceData.servicelocationInfo.distanceToBranch.toStringAsFixed(2)); + double pricePerKm = double.parse(serviceData.servicelocationInfo.homeChargesInCurrentService.toStringAsFixed(2)); + return ((serviceData.currentTotalServicePrice + (pricePerKm * totalKms)).toStringAsFixed(2)) + .toText(fontSize: 32, isBold: true); + }), ] else ...[ ((serviceData.currentTotalServicePrice).toString()).toText(fontSize: 32, isBold: true), ], diff --git a/lib/views/chat/widgets/chat_bottom_sheets.dart b/lib/views/chat/widgets/chat_bottom_sheets.dart index 1a534ca..1577793 100644 --- a/lib/views/chat/widgets/chat_bottom_sheets.dart +++ b/lib/views/chat/widgets/chat_bottom_sheets.dart @@ -75,6 +75,8 @@ void dealCompletedConsentBottomSheet({ requestVM.myFilteredRequests[index].requestStatus = requestStatusEnum; } chatVM.updateAcknowledgePaymentToMowaterStatus(false); + // Refresh requests list before navigating + await requestVM.getRequestsBasedOnFilters(); mainContext.read().onNavbarTapped(4); navigateReplaceWithNameUntilRoute(mainContext, AppRoutes.dashboard); } @@ -93,6 +95,8 @@ void dealCompletedConsentBottomSheet({ requestVM.myFilteredRequests[index].requestStatus = requestStatusEnum; } chatVM.updateAcknowledgePaymentToMowaterStatus(false); + // Refresh requests list before navigating + await requestVM.getRequestsBasedOnFilters(); mainContext.read().onNavbarTapped(4); navigateReplaceWithNameUntilRoute(mainContext, AppRoutes.dashboard); } diff --git a/lib/views/chat/widgets/chat_message_widget.dart b/lib/views/chat/widgets/chat_message_widget.dart index ab938bb..3d5495c 100644 --- a/lib/views/chat/widgets/chat_message_widget.dart +++ b/lib/views/chat/widgets/chat_message_widget.dart @@ -148,6 +148,8 @@ class _ChatMessageCustomWidgetState extends State { } setState(() {}); chatVM.updateRejectOfferDescription(''); + // Refresh requests list + await requestVM.getRequestsBasedOnFilters(); Utils.showToast(LocaleKeys.offerRejected.tr()); // navigateReplaceWithName(context, AppRoutes.dashboard); } @@ -250,6 +252,8 @@ class _ChatMessageCustomWidgetState extends State { chatVM.serviceProviderOffersList[index].requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum; } setState(() {}); + // Refresh requests list + await requestVM.getRequestsBasedOnFilters(); Utils.showToast(LocaleKeys.offerAccepted.tr()); return true; } else { @@ -399,6 +403,8 @@ class _ChatMessageCustomWidgetState extends State { setState(() {}); // Navigator.pop(context); chatVM.updateRejectOfferDescription(''); + // Refresh requests list + await requestVM.getRequestsBasedOnFilters(); Utils.showToast("Offer ${requestOfferStatusEnum == RequestOfferStatusEnum.rejected ? "Rejected" : "Cancelled"}"); // navigateReplaceWithName(context, AppRoutes.dashboard); } @@ -459,6 +465,8 @@ class _ChatMessageCustomWidgetState extends State { } setState(() {}); // Navigator.pop(context); + // Refresh requests list + await requestVM.getRequestsBasedOnFilters(); Utils.showToast(LocaleKeys.offerAccepted.tr()); // navigateReplaceWithName(context, AppRoutes.dashboard); } diff --git a/lib/views/requests/request_detail_page.dart b/lib/views/requests/request_detail_page.dart index 59e510c..5efe370 100644 --- a/lib/views/requests/request_detail_page.dart +++ b/lib/views/requests/request_detail_page.dart @@ -141,11 +141,15 @@ class RequestDetailPage extends StatelessWidget { }) { switch (requestStatus) { case RequestStatusEnum.submitted: + String chatButtonText = requestTypeEnum == RequestsTypeEnum.specialCarRequest + ? LocaleKeys.specialCarRequestChat.tr() + : LocaleKeys.sparePartRequestChat.tr(); + return ShowFillButton( maxWidth: double.infinity, margin: const EdgeInsets.all(15), maxHeight: 55, - title: LocaleKeys.specialRequestChat.tr(), + title: chatButtonText, isBold: false, onPressed: () => onViewChatTapped(context), ); diff --git a/lib/views/requests/review_request_offer.dart b/lib/views/requests/review_request_offer.dart index e385018..fc839ff 100644 --- a/lib/views/requests/review_request_offer.dart +++ b/lib/views/requests/review_request_offer.dart @@ -127,12 +127,12 @@ class _ReviewRequestOfferState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.locationInformation.tr().toText(fontSize: 18), + LocaleKeys.serviceDeliveryType.tr().toText(fontSize: 18), // Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ - // LocaleKeys.locationInformation.tr().toText(fontSize: 18), + // LocaleKeys.serviceDeliveryType.tr().toText(fontSize: 18), // MyAssets.icEdit.buildSvg().onPress(() => buildLocationInformationEditBottomSheet(context, requestVM)), // ], // ), @@ -207,7 +207,7 @@ class _ReviewRequestOfferState extends State { } Widget buildServiceInformation(BuildContext context) { - final requestVM = context.read(); + final requestVM = context.watch(); String manufacturedOnFormattedDate = ""; if (requestVM.acceptedRequestOffer!.manufacturedOn != null) { @@ -217,6 +217,23 @@ class _ReviewRequestOfferState extends State { if (requestVM.currentSelectedRequest!.createdOn != null) { requestCreatedOn = DateHelper.formatAsDayMonthYear(DateHelper.parseStringToDate(DateHelper.formatDateT(requestVM.currentSelectedRequest!.createdOn.toString() ?? ""))); } + + // Get selected delivery type + String selectedDeliveryType = "-"; + String deliveryAddress = ""; + bool showMapButton = false; + + if (requestVM.selectedDeliveryOptionEnum != null) { + selectedDeliveryType = requestVM.selectedDeliveryOptionEnum!.getStringFromRequestDeliveryOptionEnum(); + + if (requestVM.selectedDeliveryOptionEnum == RequestDeliveryOptionEnum.delivery) { + deliveryAddress = requestVM.currentSelectedRequest!.address; + } else if (requestVM.selectedDeliveryOptionEnum == RequestDeliveryOptionEnum.selfPickup) { + deliveryAddress = requestVM.currentSelectedOffer!.providerAddress ?? ""; + showMapButton = true; + } + } + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -237,6 +254,8 @@ class _ReviewRequestOfferState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + SingleDetailWidget(text: requestVM.currentSelectedRequest!.requestTypeName, type: LocaleKeys.serviceCategory.tr()), + 16.height, SingleDetailWidget(text: requestVM.currentSelectedRequest!.vehicleTypeName, type: LocaleKeys.vehicleType.tr()), 16.height, SingleDetailWidget(text: '${requestVM.currentSelectedRequest!.model} ${requestVM.currentSelectedRequest!.year}', type: LocaleKeys.model.tr()), @@ -244,11 +263,8 @@ class _ReviewRequestOfferState extends State { 16.height, SingleDetailWidget(text: (requestVM.acceptedRequestOffer!.manufacturedByName ?? "").toString(), type: LocaleKeys.manufacturedBy.tr()), ], - // 16.height, - // SingleDetailWidget(text: "${requestVM.acceptedRequestOffer!.price.toString()} ${LocaleKeys.sar.tr()}", type: LocaleKeys.offerPrice.tr()), 16.height, SingleDetailWidget(text: requestVM.acceptedRequestOfferProviderName ?? "", type: LocaleKeys.providerName.tr()), - 16.height, SingleDetailWidget(text: LocaleKeys.online.tr(), type: LocaleKeys.paymentType.tr()), ], @@ -260,6 +276,8 @@ class _ReviewRequestOfferState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ + SingleDetailWidget(text: selectedDeliveryType, type: LocaleKeys.serviceDeliveryType.tr()), + 16.height, SingleDetailWidget(text: requestVM.currentSelectedRequest!.brand, type: LocaleKeys.vehicleBrand.tr()), 16.height, SingleDetailWidget(text: requestVM.acceptedRequestOffer!.serviceItemName ?? "", type: LocaleKeys.serviceName.tr()), @@ -280,6 +298,52 @@ class _ReviewRequestOfferState extends State { ), 16.height, SingleDetailWidget(text: requestVM.currentSelectedRequest!.description, type: LocaleKeys.description.tr()), + if (requestVM.selectedDeliveryOptionEnum != null && deliveryAddress.isNotEmpty) ...[ + 16.height, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SingleDetailWidget(text: deliveryAddress, type: LocaleKeys.location.tr()), + ), + if (showMapButton) ...[ + 8.width, + Row( + children: [ + LocaleKeys.openMapLocation.tr().toText( + fontSize: 12, + isBold: true, + color: MyColors.primaryColor, + isUnderLine: true, + ), + 4.width, + Image.asset( + MyAssets.icRightUpPng, + height: 6, + width: 6, + color: MyColors.primaryColor, + ), + ], + ).onPress(() async { + double latitude, longitude = 0.0; + latitude = double.parse(requestVM.currentSelectedOffer!.providerLatitude ?? "0.0"); + longitude = double.parse(requestVM.currentSelectedOffer!.providerLongitude ?? "0.0"); + await Utils.openLocationInMaps(latitude: latitude, longitude: longitude); + }), + ], + ], + ), + if (requestVM.selectedDeliveryOptionEnum == RequestDeliveryOptionEnum.delivery && requestVM.additionalAddressSparePartRequestDelivery.isNotEmpty) ...[ + 8.height, + SingleDetailWidget(text: requestVM.additionalAddressSparePartRequestDelivery, type: LocaleKeys.additionalAddressDetails.tr()), + ], + ], + ), + ], ], ); } diff --git a/lib/views/requests/widget/request_item.dart b/lib/views/requests/widget/request_item.dart index 724abda..e3f15e1 100644 --- a/lib/views/requests/widget/request_item.dart +++ b/lib/views/requests/widget/request_item.dart @@ -48,10 +48,21 @@ class RequestItem extends StatelessWidget { ], 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), ], + if (request.brand.isNotEmpty) ...[ + showItem("${LocaleKeys.brand.tr()}:", request.brand), + ], + if (request.model.isNotEmpty) ...[ + showItem("${LocaleKeys.model.tr()}:", request.model), + ], + if (request.year > 0) ...[ + showItem("${LocaleKeys.year.tr()}:", "${request.year}"), + ], + if (request.vehicleTypeName.isNotEmpty) ...[ + showItem("${LocaleKeys.vehicleType.tr()}:", request.vehicleTypeName), + ], showItem("${LocaleKeys.description.tr()}:", request.description), ], ), diff --git a/lib/views/setting_options/setting_option_help.dart b/lib/views/setting_options/setting_option_help.dart index 21b62d0..48ec512 100644 --- a/lib/views/setting_options/setting_option_help.dart +++ b/lib/views/setting_options/setting_option_help.dart @@ -55,7 +55,7 @@ class SettingOptionsHelp extends StatelessWidget { size: 20, color: MyColors.greyColor, ), - titleText: LocaleKeys.termPrivacy.tr(), + titleText: LocaleKeys.termsPrivacyPolicy.tr(), needBorderBelow: true, onTap: () => navigateWithName(context, AppRoutes.settingOptionsTermsAndConditions), ), diff --git a/lib/views/setting_options/setting_options_terms_and_conditions.dart b/lib/views/setting_options/setting_options_terms_and_conditions.dart index c00db1b..50e147d 100644 --- a/lib/views/setting_options/setting_options_terms_and_conditions.dart +++ b/lib/views/setting_options/setting_options_terms_and_conditions.dart @@ -60,7 +60,7 @@ class _SettingOptionsTermsAndConditionsState extends State Navigator.pop(context), diff --git a/lib/widgets/common_widgets/add_phone_num_wiget.dart b/lib/widgets/common_widgets/add_phone_num_wiget.dart index ca9e0f3..04c11ed 100644 --- a/lib/widgets/common_widgets/add_phone_num_wiget.dart +++ b/lib/widgets/common_widgets/add_phone_num_wiget.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -20,7 +19,7 @@ import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; class AddPhoneNumWidget extends StatefulWidget { - const AddPhoneNumWidget({Key? key}) : super(key: key); + const AddPhoneNumWidget({super.key}); @override State createState() => _AddPhoneNumWidgetState(); @@ -82,11 +81,34 @@ class _AddPhoneNumWidgetState extends State { 8.height, TxtField( keyboardType: TextInputType.phone, - hint: "546758594", + hint: "Phone without country code ", value: phoneNum, isSidePaddingZero: true, onChanged: (v) { - phoneNum = v; + // Remove country code if user enters it + String cleanedPhone = v.trim(); + + // If country code is selected and phone starts with it, remove it + if (countryCode.isNotEmpty && cleanedPhone.startsWith(countryCode)) { + cleanedPhone = cleanedPhone.substring(countryCode.length).trim(); + } + + // Also handle cases where user adds + before country code + if (countryCode.isNotEmpty && cleanedPhone.startsWith('+$countryCode')) { + cleanedPhone = cleanedPhone.substring(countryCode.length + 1).trim(); + } + + // Handle case where user just types + followed by digits matching country code + if (countryCode.isNotEmpty && cleanedPhone.startsWith('+')) { + String codeWithoutPlus = countryCode.replaceAll('+', ''); + if (cleanedPhone.substring(1).startsWith(codeWithoutPlus)) { + cleanedPhone = cleanedPhone.substring(codeWithoutPlus.length + 1).trim(); + } + } + + setState(() { + phoneNum = cleanedPhone; + }); }, ), 16.height, @@ -161,10 +183,10 @@ class CustomBottomPicker extends StatelessWidget { final void Function(DropValue selectedItem) onItemSelected; const CustomBottomPicker({ - Key? key, + super.key, required this.items, required this.onItemSelected, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/widgets/dropdown/dropdow_field.dart b/lib/widgets/dropdown/dropdow_field.dart index 37999e3..ad27687 100644 --- a/lib/widgets/dropdown/dropdow_field.dart +++ b/lib/widgets/dropdown/dropdow_field.dart @@ -58,6 +58,7 @@ class _DropdownFieldState extends State { dropdownValue = widget.dropdownValue; return Column( children: [ + // Always show hint as label when provided if (widget.hint != null) ...[ Row( mainAxisAlignment: MainAxisAlignment.start, @@ -71,19 +72,41 @@ class _DropdownFieldState extends State { ), 4.height, ], - IgnorePointer( - ignoring: !widget.isSelectAble, - child: Container( - decoration: - widget.showAppointmentPickerVariant ? null : Utils.containerColorRadiusBorderWidth(MyColors.white, 0, widget.isSelectAble ? MyColors.darkPrimaryColor : MyColors.greyACColor, 2), + // Show disabled text field when not selectable + if (!widget.isSelectAble) + Container( + decoration: Utils.containerColorRadiusBorderWidth( + MyColors.greyShadowColor, + 0, + MyColors.greyACColor, + 2 + ), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), + width: double.infinity, + child: (dropdownValue?.value ?? "").toText( + color: MyColors.darkTextColor, + fontSize: 15, + fontWeight: MyFonts.Medium, + ), + ) + else + // Show normal dropdown when selectable + Container( + decoration: widget.showAppointmentPickerVariant + ? null + : Utils.containerColorRadiusBorderWidth( + MyColors.white, + 0, + MyColors.darkPrimaryColor, + 2 + ), margin: const EdgeInsets.all(0), padding: const EdgeInsets.only(left: 8, right: 8), width: widget.showAppointmentPickerVariant ? 170 : null, child: DropdownButton( value: dropdownValue, - icon: Icon( + icon: const Icon( Icons.keyboard_arrow_down_sharp, - color: !widget.isSelectAble ? Colors.transparent : null, size: 21, ), elevation: 16, @@ -95,7 +118,6 @@ class _DropdownFieldState extends State { color: borderColor, fontSize: 15, ), - // hint: (widget.hint ?? "").toText(color: borderColor, fontSize: 15, fontWeight: MyFonts.Medium), underline: Container(height: 0), onChanged: (DropValue? newValue) { setState(() { @@ -105,17 +127,20 @@ class _DropdownFieldState extends State { }, onTap: widget.onTap, items: (widget.list ?? defaultV).map>( - (DropValue value) { + (DropValue value) { return DropdownMenuItem( value: value, enabled: value.isEnabled ?? true, - child: value.value.toText(fontSize: 15, color: value.isEnabled == false ? MyColors.darkTextColor : null, fontWeight: MyFonts.Medium), + child: value.value.toText( + fontSize: 15, + color: value.isEnabled == false ? MyColors.darkTextColor : null, + fontWeight: MyFonts.Medium + ), ); }, ).toList(), ), ), - ), if (widget.errorValue != "") Row( mainAxisAlignment: MainAxisAlignment.end,