Compare commits

...

11 Commits

Author SHA1 Message Date
Rand Alshammari f76b204c2d merged 1 month ago
Rand Alshammari 3ceb98c045 merged 2 months ago
Rand Alshammari 1bbb706d8c Merge branch 'faiz_development_common' into fatima_dev
# Conflicts:
#	lib/views/advertisement/components/ads_list_widget.dart
2 months ago
Faiz Hashmi 3146a9433b Completed the Enhancement CR 2 months ago
Rand Alshammari 87ec0f2966 fixed A & E 2 months ago
Faiz Hashmi ff10b66b14 fixing jira bugs 2 months ago
FaizHashmiCS22 388e5b4aed Merge pull request 'fatima_dev' (#5) from fatima_dev into faiz_development_common
Reviewed-on: #5
2 months ago
Rand Alshammari 13966f60a9 Merge remote-tracking branch 'origin/faiz_development_common' into fatima_dev
# Conflicts:
#	lib/views/advertisement/select_ad_type_view.dart
2 months ago
Rand Alshammari 38bda8e8ed fixed A & E 2 months ago
Faiz Hashmi b88aee4632 fixing jira bugs 2 months ago
Fatimah.Alshammari c893cd73ec fix localization and UI updates 3 months ago

@ -1,63 +0,0 @@
# 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

@ -132,14 +132,14 @@
"insert_otp_code": "ادخل الرمز", "insert_otp_code": "ادخل الرمز",
"type_code": "الرجاء كتابة الرمز الذي أرسلناه إلى", "type_code": "الرجاء كتابة الرمز الذي أرسلناه إلى",
"resend_code": "أعد إرسال الرمز", "resend_code": "أعد إرسال الرمز",
"check_code": "التحقق من الشفرة", "check_code": "التحقق من الرمز",
"time_will_expire": "إعادة إرسال الرمز بتنسيق", "time_will_expire": "إعادة إرسال الرمز بتنسيق",
"sec": "ثانية", "sec": "ثانية",
"providers": "الموفرون", "providers": "الموفرون",
"appointments": "تعيينات", "appointments": "المواعيد",
"home": "بيت", "home": "بيت",
"ads": "إعلانات", "ads": "إعلانات",
"settings": "إعدادات", "settings": "الإعدادات",
"view_all": "مشاهدة الكل", "view_all": "مشاهدة الكل",
"my_active_Ads": "إعلاناتي النشطة", "my_active_Ads": "إعلاناتي النشطة",
"recommended_ads": "الإعلانات الموصى بها", "recommended_ads": "الإعلانات الموصى بها",
@ -212,8 +212,8 @@
"noItemSelectedYet": "لم يتم تحديد أي عنصر بعد", "noItemSelectedYet": "لم يتم تحديد أي عنصر بعد",
"totalAdditionalAmount": "إجمالي المبلغ الإضافي", "totalAdditionalAmount": "إجمالي المبلغ الإضافي",
"sar": "ريال سعودي", "sar": "ريال سعودي",
"cancel": "يلغي", "cancel": "الغاء",
"add": "يضيف", "add": "اضافه",
"itemsAddedSuccessfully": "تمت إضافة العناصر بنجاح", "itemsAddedSuccessfully": "تمت إضافة العناصر بنجاح",
"pleaseSelectItems": "يرجى اختيار العناصر", "pleaseSelectItems": "يرجى اختيار العناصر",
"totalAmount": "المبلغ الإجمالي", "totalAmount": "المبلغ الإجمالي",
@ -226,10 +226,10 @@
"typeHere": "اكتب هنا...", "typeHere": "اكتب هنا...",
"pleaseSelectReason": "يرجى تحديد السبب", "pleaseSelectReason": "يرجى تحديد السبب",
"noAppointmentFound": "لم يتم العثور على مواعيد", "noAppointmentFound": "لم يتم العثور على مواعيد",
"slotsOverview": "نظرة عامة على الفتحات", "slotsOverview": "نظرة عامة على الحجوزات",
"empty": "فارغ", "empty": "فارغ",
"occupied": "محجوز", "occupied": "محجوز",
"totalSlots": "إجمالي الفتحات", "totalSlots": "إجمالي الحجوزات",
"selectAppointments": "اختيار المواعيد", "selectAppointments": "اختيار المواعيد",
"appointmentMergeSuccessfully": "دمج المواعيد تم بنجاح", "appointmentMergeSuccessfully": "دمج المواعيد تم بنجاح",
"updateAppointment": "تحديث الموعد", "updateAppointment": "تحديث الموعد",
@ -238,11 +238,11 @@
"appointmentisCompleted": "تم اكتمال الموعد", "appointmentisCompleted": "تم اكتمال الموعد",
"workStart": "بدء العمل", "workStart": "بدء العمل",
"payNow": "ادفع الآن", "payNow": "ادفع الآن",
"arrived": "وصل", "arrived": "تم الوصول",
"payLater": "ادفع لاحقًا", "payLater": "ادفع لاحقًا",
"complete": "اكتمل", "complete": "اكتمل",
"makeAnOffer": "قدم عرضًا", "makeAnOffer": "قدم عرضًا",
"provideOffer": "تقديم العرض", "provideOffer": "تقديم عرض",
"enterAmount": "أدخل المبلغ", "enterAmount": "أدخل المبلغ",
"submit": "إرسال", "submit": "إرسال",
"viewChat": "عرض الدردشة", "viewChat": "عرض الدردشة",
@ -254,6 +254,9 @@
"sendOTP": "إرسال رمز OTP", "sendOTP": "إرسال رمز OTP",
"noUserAddedYet": "لم يتم إضافة مستخدم بعد", "noUserAddedYet": "لم يتم إضافة مستخدم بعد",
"assignDealerUser": "تعيين مستخدم تاجر", "assignDealerUser": "تعيين مستخدم تاجر",
"confirmAssignDealerUser": "هل أنت متأكد من رغبتك في تعيين مستخدم التاجر هذا للفرع؟",
"removeDealerUser": "إزالة مستخدم تاجر",
"confirmRemoveDealerUser": "هل أنت متأكد من رغبتك في إزالة مستخدم التاجر هذا من الفرع؟",
"createNewUser": "إنشاء مستخدم جديد", "createNewUser": "إنشاء مستخدم جديد",
"dealerUser": "مستخدم تاجر", "dealerUser": "مستخدم تاجر",
"setorEditBranchSchedule": "تعيين أو تحرير جدول الفروع", "setorEditBranchSchedule": "تعيين أو تحرير جدول الفروع",
@ -406,7 +409,7 @@
"passwordIsStrong": "كلمة المرور الخاصة بك قوية", "passwordIsStrong": "كلمة المرور الخاصة بك قوية",
"passwordVeryStrong": "كلمة المرور الخاصة بك قوية جدًا", "passwordVeryStrong": "كلمة المرور الخاصة بك قوية جدًا",
"selectDuration": "اختر المدة", "selectDuration": "اختر المدة",
"daysVar": "الأيام", "daysVar": "يوم",
"adCharges": "رسوم الإعلان", "adCharges": "رسوم الإعلان",
"select": "اختر", "select": "اختر",
"deleteAdConfirmation": "هل تريد حذف الإعلان؟", "deleteAdConfirmation": "هل تريد حذف الإعلان؟",
@ -417,8 +420,12 @@
"accidentFree": "خالية من الحوادث", "accidentFree": "خالية من الحوادث",
"brandNew": "جديدة", "brandNew": "جديدة",
"used": "مستعملة", "used": "مستعملة",
"all": "الكل",
"newSparePart": "قطع غيار جديدة",
"usedSparePart": "قطع غيار مستعملة",
"hasYourCarHadAccident": "هل تعرضت سيارتك لأي حادث؟", "hasYourCarHadAccident": "هل تعرضت سيارتك لأي حادث؟",
"odometerReading": "قراءة عداد المسافات (كم)", "odometerReading": "قراءة عداد المسافات (كم)",
"canMileageIncrease": "هل يمكن زيادة المسافة المقطوعة عند الفحص؟",
"currentlyInUse": "قيد الاستخدام حاليًا", "currentlyInUse": "قيد الاستخدام حاليًا",
"demand": "الطلب", "demand": "الطلب",
"adDurationExpired": "انتهت مدة عرض الإعلان الخاص بك", "adDurationExpired": "انتهت مدة عرض الإعلان الخاص بك",
@ -590,6 +597,9 @@
"myNearbyBranches": "فروعي القريبة", "myNearbyBranches": "فروعي القريبة",
"myRecentBranches": "فروعي الأخيرة", "myRecentBranches": "فروعي الأخيرة",
"favoriteList": "قائمة المفضلة", "favoriteList": "قائمة المفضلة",
"myFavouriteProviders": "مزودي الخدمة المفضلين",
"myFavouriteAds": "إعلاناتي المفضلة",
"noFavouriteAds": "لا توجد إعلانات مفضلة",
"noReviewsBranch": "هذا الفرع لا يوجد لديه تعليقات.", "noReviewsBranch": "هذا الفرع لا يوجد لديه تعليقات.",
"readLess": "اقرأ أقل", "readLess": "اقرأ أقل",
"readMoreReviews": "اقرأ المزيد من التقييمات", "readMoreReviews": "اقرأ المزيد من التقييمات",
@ -655,7 +665,7 @@
"servicesNotAvailableHomeLocation": "بعض الخدمات غير متوفرة في الموقع المنزلي.", "servicesNotAvailableHomeLocation": "بعض الخدمات غير متوفرة في الموقع المنزلي.",
"additionalChargesNotice": "هذه الرسوم إضافية على رسوم الخدمة الفعلية. قد تختلف الرسوم للعناصر الثقيلة.", "additionalChargesNotice": "هذه الرسوم إضافية على رسوم الخدمة الفعلية. قد تختلف الرسوم للعناصر الثقيلة.",
"selectServiceLocation": "اختر موقع الخدمة", "selectServiceLocation": "اختر موقع الخدمة",
"apptOn": "التعيين في", "apptOn": "الموعد في",
"merged": "مُدمج", "merged": "مُدمج",
"date": "التاريخ", "date": "التاريخ",
"selectAll": "اختر الكل", "selectAll": "اختر الكل",
@ -817,13 +827,80 @@
"specialRequestsChats": "دردشات الطلبات الخاصة", "specialRequestsChats": "دردشات الطلبات الخاصة",
"selectDeliveryOption": "اختر خيار التوصيل", "selectDeliveryOption": "اختر خيار التوصيل",
"deliveryOptions": "خيارات التوصيل", "deliveryOptions": "خيارات التوصيل",
"selfPickup": "الالتقاط الذاتي", "selfPickup": "استلام ذاتي",
"updateYourLocationInfo": "قم بتحديث معلومات موقعك", "updateYourLocationInfo": "قم بتحديث معلومات موقعك",
"initiateSelfPickup": "بدء الاستلام الذاتي", "initiateSelfPickup": "بدء الاستلام الذاتي",
"selfPickupStatus": "حالة الالتقاط الذاتي", "selfPickupStatus": "حالة الاستلام الذاتي",
"myDraftAds": "مسودتي للإعلانات", "myDraftAds": "مسودتي للإعلانات",
"scheduleDeletedSuccessfully": "تم حذف الجدول بنجاح", "scheduleDeletedSuccessfully": "تم حذف الجدول بنجاح",
"addValidAddress": "رجى إضافة عنوان صالح", "addValidAddress": "رجى إضافة عنوان صالح",
"termsPrivacyPolicy": "الشروط وسياسة الخصوصية" "termsPrivacyPolicy": "الشروط وسياسة الخصوصية",
"pendingApproval": "قيد الموافقة",
"inReview": "قيد المراجعة",
"rejected": "مرفوض",
"deactivated": "معطل",
"blocked": "محظور",
"allRequests": "جميع الطلبات",
"pending": "قيد الانتظار",
"preparingToCollect": "جاري التحضير للاستلام",
"readyToCollect": "جاهز للاستلام",
"collected": "تم الاستلام",
"initiated": "تم البدء",
"inTransit": "قيد النقل",
"outForDelivery": "قيد التسليم",
"delivered": "تم التسليم",
"negotiate": "تفاوض",
"accepted": "مقبول",
"cancelled": "ملغى",
"loginToAnotherAccount": "تسجيل الدخول إلى حساب آخر",
"silver": "فضي",
"phoneWithoutCountryCode": "رقم الهاتف بدون رمز الدوله",
"sunday": "الأحد",
"monday": "الإثنين",
"tuesday": "الثلاثاء",
"wednesday": "الأربعاء",
"thursday": "الخميس",
"friday": "الجمعة",
"saturday": "السبت",
"adsWith": "الإعلانات:",
"usersWith": "المستخدمون:",
"branchesWith": "الفروع:",
"subscribed": "مشترك",
"expires": "تنتهي",
"renew": "تجديد",
"km": "كم",
"items": "عناصر",
"no_items": "لا توجد عناصر",
"allAds": "جميع الاعلانات",
"reserveCancel": "الغاء الحجز",
"pendingForReview": "قيد المراجعة",
"pendingForPayment": "قيد الدفع",
"rejected_n": "مرفوض",
"canceled": "ملغى",
"pendingForPost": "قيد النشر",
"active_n": "نشط",
"expired_n": "منتهي الصلاحية",
"sold_n": "مباع",
"reserved_n": "محجوز",
"buyingService": "خدمة الشراء",
"deActive": "غير نشط",
"pendingForActive": "قيد التفعيل",
"updateItem": "تحديث العنصر",
"createItem": "إنشاء عنصر",
"pleaseAddAtLeastOneItemImage": "يرجى إضافة صورة واحدة على الأقل للعنصر",
"pleaseAddValidItemName": "يرجى إضافة اسم عنصر صحيح",
"pleaseAddValidItemDescription": "يرجى إضافة وصف عنصر صحيح",
"pleaseAddValidItemPrice": "يرجى إضافة سعر عنصر صحيح",
"remaining": "متبقي",
"month": "شهر",
"currentPackage": "الحزمة الحالية",
"forVar": "لمدة",
"branch": "الفرع",
"shareAd": "مشاركة الإعلان",
"checkOutThisAd": "تفقد هذه السيارة الرائعة!",
"downloadApp": "تحميل تطبيق موّاتر:",
"androidApp": "أندرويد",
"iosApp": "آيفون",
"pleaseWait": "يرجى الانتظار..."
} }

@ -254,6 +254,9 @@
"sendOTP": "Send OTP", "sendOTP": "Send OTP",
"noUserAddedYet": "No User Added Yet", "noUserAddedYet": "No User Added Yet",
"assignDealerUser": "Assign Dealer User", "assignDealerUser": "Assign Dealer User",
"confirmAssignDealerUser": "Are you sure you want to assign this dealer user to the branch?",
"removeDealerUser": "Remove Dealer User",
"confirmRemoveDealerUser": "Are you sure you want to remove this dealer user from the branch?",
"createNewUser": "Create New User", "createNewUser": "Create New User",
"dealerUser": "Dealer User", "dealerUser": "Dealer User",
"setorEditBranchSchedule": "Set or Edit Branch Schedule", "setorEditBranchSchedule": "Set or Edit Branch Schedule",
@ -418,8 +421,12 @@
"accidentFree": "Accident Free", "accidentFree": "Accident Free",
"brandNew": "Brand New", "brandNew": "Brand New",
"used": "Used", "used": "Used",
"all": "All",
"newSparePart": "New Spare Part",
"usedSparePart": "Used Spare Part",
"hasYourCarHadAccident": "Has your car had any accident?", "hasYourCarHadAccident": "Has your car had any accident?",
"odometerReading": "Odometer Reading (Km)", "odometerReading": "Odometer Reading (Km)",
"canMileageIncrease": "Can the mileage be increased when checking?",
"currentlyInUse": "Currently in Use", "currentlyInUse": "Currently in Use",
"demand": "Demand", "demand": "Demand",
"adDurationExpired": "Your Ad Duration time is over", "adDurationExpired": "Your Ad Duration time is over",
@ -591,6 +598,9 @@
"myNearbyBranches": "My Nearby Branches", "myNearbyBranches": "My Nearby Branches",
"myRecentBranches": "My Recent Branches", "myRecentBranches": "My Recent Branches",
"favoriteList": "Favorite List", "favoriteList": "Favorite List",
"myFavouriteProviders": "My Favourite Providers",
"myFavouriteAds": "My Favourite Ads",
"noFavouriteAds": "No Favourite Ads Found",
"noReviewsBranch": "This branch has no reviews.", "noReviewsBranch": "This branch has no reviews.",
"readLess": "Read Less", "readLess": "Read Less",
"readMoreReviews": "Read More Reviews", "readMoreReviews": "Read More Reviews",
@ -822,5 +832,72 @@
"myDraftAds": "My Draft Ads", "myDraftAds": "My Draft Ads",
"scheduleDeletedSuccessfully": "The schedule has been deleted successfully", "scheduleDeletedSuccessfully": "The schedule has been deleted successfully",
"addValidAddress": "Please add a valid address", "addValidAddress": "Please add a valid address",
"termsPrivacyPolicy": "Terms & Privacy Policy" "termsPrivacyPolicy": "Terms & Privacy Policy",
"pendingApproval": "Pending Approval",
"inReview": "In Review",
"rejected": "Rejected",
"deactivated": "Deactivated",
"blocked": "Blocked",
"allRequests": "All Requests",
"pending": "Pending",
"preparingToCollect": "Preparing To Collect",
"readyToCollect": "Ready To Collect",
"collected": "Collected",
"initiated": "Initiated",
"inTransit": "In Transit",
"outForDelivery": "Out For De,livery",
"negotiate": "Negotiate",
"accepted": "Accepted",
"cancelled": "Cancelled",
"delivered": "Delivered",
"loginToAnotherAccount": "Login To Another Account",
"silver": "Silver",
"phoneWithoutCountryCode": "Phone without country code",
"sunday": "Sunday",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"adsWith": "Ads:",
"usersWith": "Users:",
"branchesWith": "Branches:",
"subscribed": "Subscribed",
"expires": "Expires",
"renew": "Renew",
"km": "km",
"items": "items",
"no_items": "No items",
"allAds": "All Ads",
"reserveCancel": "Reserve Cancel",
"pendingForReview": "Pending For Review",
"pendingForPayment": "Pending For Payment",
"rejected_n": "Rejected",
"canceled": "Cancelled",
"pendingForPost": "Pending For Post",
"active_n": "Active",
"expired_n": "Expired",
"sold_n": "Sold",
"reserved_n": "Reserved",
"buyingService": "Buying Service",
"deActive": "De Active",
"pendingForActive": "Pending For Active",
"updateItem": "Update Item",
"createItem": "Create Item",
"pleaseAddAtLeastOneItemImage": "Please add at least one item image",
"pleaseAddValidItemName": "Please add valid item name",
"pleaseAddValidItemDescription": "Please add valid item description",
"pleaseAddValidItemPrice": "Please add valid item price",
"remaining": "remaining",
"month": "Month",
"currentPackage": "Current Package",
"forVar": "for",
"branch": "branch",
"shareAd": "Share Ad",
"checkOutThisAd": "Check out this amazing vehicle!",
"downloadApp": "Download MoWater App:",
"androidApp": "Android",
"iosApp": "iOS",
"pleaseWait": "Please wait..."
} }

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

@ -138,7 +138,7 @@ class ApiClientImp implements ApiClient {
logger.i(url); logger.i(url);
logger.d("------Payload------"); logger.d("------Payload------");
logger.i(jsonDecode(requestBody)); logger.i(jsonDecode(requestBody));
logger.d("------Response------"); // logger.d("------Response------");
logger.i(jsonDecode(response.body)); logger.i(jsonDecode(response.body));
} }
if (response.statusCode >= 200 && response.statusCode < 500) { if (response.statusCode >= 200 && response.statusCode < 500) {
@ -249,7 +249,7 @@ class ApiClientImp implements ApiClient {
logger.i(url); logger.i(url);
logger.i("$queryParameters"); logger.i("$queryParameters");
} }
var response = await _get(Uri.parse(url), headers: headers0).timeout(const Duration(seconds: 60)); var response = await _get(Uri.parse(url), headers: headers0).timeout(const Duration(seconds: 120));
if (!kReleaseMode) { if (!kReleaseMode) {
logger.i(jsonDecode(response.body)); logger.i(jsonDecode(response.body));

@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:mc_common_app/models/general_models/post_params_model.dart'; import 'package:mc_common_app/models/general_models/post_params_model.dart';
import 'package:mc_common_app/models/subscriptions_models/provider_subscription_model.dart'; import 'package:mc_common_app/models/subscriptions_models/provider_subscription_model.dart';
@ -35,6 +36,9 @@ class AppState {
_deviceType = value; _deviceType = value;
} }
bool isArabic(BuildContext context) =>
Localizations.localeOf(context).languageCode == 'ar';
AppType currentAppType = AppType.provider; AppType currentAppType = AppType.provider;
UserType userType = UserType.customer; UserType userType = UserType.customer;
@ -66,7 +70,8 @@ class AppState {
List<Subscription>? _providerSubscription; List<Subscription>? _providerSubscription;
List<Subscription> get getproviderSubscription => _providerSubscription ?? [Subscription(name: "")]; List<Subscription> get getproviderSubscription =>
_providerSubscription ?? [Subscription(name: "")];
set setproviderSubscription(List<Subscription>? value) { set setproviderSubscription(List<Subscription>? value) {
_providerSubscription = value; _providerSubscription = value;
@ -74,7 +79,8 @@ class AppState {
DropValue? _userRegisterCountrySelection; DropValue? _userRegisterCountrySelection;
DropValue get getUserRegisterCountrySelection => _userRegisterCountrySelection!; DropValue get getUserRegisterCountrySelection =>
_userRegisterCountrySelection!;
set setUserRegisterCountrySelection(DropValue value) { set setUserRegisterCountrySelection(DropValue value) {
_userRegisterCountrySelection = value; _userRegisterCountrySelection = value;
@ -87,6 +93,4 @@ class AppState {
set setIsViewOnly(bool value) { set setIsViewOnly(bool value) {
_isViewOnly = value; _isViewOnly = value;
} }
} }

@ -127,6 +127,9 @@ class ApiConsts {
static String vehicleAdsSingleStepUpdate = "${baseUrlServices}api/Advertisement/AdsSingleStep_Update"; static String vehicleAdsSingleStepUpdate = "${baseUrlServices}api/Advertisement/AdsSingleStep_Update";
static String vehicleAdsSingleStepUpdateExtend = "${baseUrlServices}api/Advertisement/AdsSingleStep_Update_Extend"; static String vehicleAdsSingleStepUpdateExtend = "${baseUrlServices}api/Advertisement/AdsSingleStep_Update_Extend";
static String vehicleAdsGet = "${baseUrlServices}api/Advertisement/Ads_Get"; static String vehicleAdsGet = "${baseUrlServices}api/Advertisement/Ads_Get";
static String favouriteAdsGet = "${baseUrlServices}api/Advertisement/FavouriteAds_Get";
static String favouriteAdsCreate = "${baseUrlServices}api/Advertisement/FavouriteAds_Create";
static String unFavouriteAds = "${baseUrlServices}api/Advertisement/UnFavourite_Ads";
static String myAdsReserveGet = "${baseUrlServices}api/Advertisement/AdsReserve_Get"; static String myAdsReserveGet = "${baseUrlServices}api/Advertisement/AdsReserve_Get";
static String reserveAdsBankDetailsGet = "${baseUrlServices}api/Advertisement/MCBankAccountAd_Get"; static String reserveAdsBankDetailsGet = "${baseUrlServices}api/Advertisement/MCBankAccountAd_Get";
static String adsUpdateStatus = "${baseUrlServices}api/Advertisement/Ads_UpdateStatus"; static String adsUpdateStatus = "${baseUrlServices}api/Advertisement/Ads_UpdateStatus";
@ -181,9 +184,8 @@ class ApiConsts {
static String requestOfferUpdateDeliveryOptionByCustomer = "${baseUrlServices}api/RequestManagement/RequestOffer_UpdateDeliveryByCustomer"; static String requestOfferUpdateDeliveryOptionByCustomer = "${baseUrlServices}api/RequestManagement/RequestOffer_UpdateDeliveryByCustomer";
static String requestUpdateAdditionalAddress = "${baseUrlServices}api/RequestManagement/Request_UpdateAdditionalAddress"; static String requestUpdateAdditionalAddress = "${baseUrlServices}api/RequestManagement/Request_UpdateAdditionalAddress";
//Shipping //Shipping
static String shippingRequestStatusUpdate = "${baseUrlServices}api/RequestManagement/ShippingRequestStatus_Update"; static String shippingRequestStatusUpdate = "${baseUrlServices}api/RequestManagement/ShippingRequestStatus_Update";
static String shippingRequestStatusGet = "${baseUrlServices}api/RequestManagement/ShippingRequestStatus_Get"; static String shippingRequestStatusGet = "${baseUrlServices}api/RequestManagement/ShippingRequestStatus_Get";
static String selfPickupRequestStatusGet = "${baseUrlServices}api/RequestManagement/SelfPickUpRequestStatus_Get"; static String selfPickupRequestStatusGet = "${baseUrlServices}api/RequestManagement/SelfPickUpRequestStatus_Get";
static String selfPickupRequestStatusUpdate = "${baseUrlServices}api/RequestManagement/SelfPickUpRequestStatus_Update"; static String selfPickupRequestStatusUpdate = "${baseUrlServices}api/RequestManagement/SelfPickUpRequestStatus_Update";
@ -290,6 +292,7 @@ class MyAssets {
//JPEG //JPEG
static String bnCar = "${assetPath}images/bn_car.jpeg"; static String bnCar = "${assetPath}images/bn_car.jpeg";
static String bnCarFlashingLights = "${assetPath}images/bn_car_flashing_lights.gif";
static String carBanner = "${assetPath}images/bn_car.jpeg"; static String carBanner = "${assetPath}images/bn_car.jpeg";
//SVG //SVG
@ -468,3 +471,4 @@ class GuestConsts {
}, },
); );
} }

@ -143,7 +143,8 @@ class AppRoutes {
static const String branchDetailView = "/branchDetailPage"; static const String branchDetailView = "/branchDetailPage";
static const String branchSearchFilterPage = "/branchSearchFilterPage"; static const String branchSearchFilterPage = "/branchSearchFilterPage";
static const String providerProfileView = "/providerProfilePage"; static const String providerProfileView = "/providerProfilePage";
static const String favoriteListView = "/favoriteListView"; static const String favoriteProvidersView = "/favoriteProvidersView";
static const String favouriteAdsView = "/favouriteAdsView";
//Requests //Requests
static const String requestsDetailPage = "/requestsDetailPage"; static const String requestsDetailPage = "/requestsDetailPage";

@ -8,6 +8,8 @@ import 'package:mc_common_app/classes/consts.dart';
import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/utils/date_helper.dart'; import 'package:mc_common_app/utils/date_helper.dart';
import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/enums.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:mc_common_app/generated/locale_keys.g.dart';
extension EmailValidator on String { extension EmailValidator on String {
Widget toText( Widget toText(
@ -288,6 +290,8 @@ extension AppointmentStatusToInt on AppointmentStatusEnum {
case AppointmentStatusEnum.upcoming: case AppointmentStatusEnum.upcoming:
return 11; return 11;
case AppointmentStatusEnum.served:
return 12;
case AppointmentStatusEnum.allAppointments: case AppointmentStatusEnum.allAppointments:
return 0; return 0;
@ -384,6 +388,8 @@ extension AppointmentEnum on int {
return AppointmentStatusEnum.past; return AppointmentStatusEnum.past;
} else if (this == 11) { } else if (this == 11) {
return AppointmentStatusEnum.upcoming; return AppointmentStatusEnum.upcoming;
} else if (this == 12) {
return AppointmentStatusEnum.served;
} else { } else {
return AppointmentStatusEnum.allAppointments; return AppointmentStatusEnum.allAppointments;
} }
@ -438,6 +444,8 @@ extension RequestTypeTypEnum on int {
return RequestsTypeEnum.specialCarRequest; return RequestsTypeEnum.specialCarRequest;
} else if (this == 2) { } else if (this == 2) {
return RequestsTypeEnum.serviceRequest; return RequestsTypeEnum.serviceRequest;
} else if (this == 3) {
return RequestsTypeEnum.other;
} }
return RequestsTypeEnum.specialCarRequest; return RequestsTypeEnum.specialCarRequest;
} }
@ -452,6 +460,9 @@ extension RequestTypeStatusToInt on RequestsTypeEnum {
case RequestsTypeEnum.serviceRequest: case RequestsTypeEnum.serviceRequest:
return 2; return 2;
case RequestsTypeEnum.other:
return 3;
default: default:
return 0; return 0;
} }
@ -675,30 +686,89 @@ extension BranchServiceEnumStatusToInt on ServiceStatusEnum {
} }
} }
// extension DateTimeConversions on DateTime {
// String getTimeAgo({bool numericDates = true, bool isArabic = false,}) {
// final date2 = DateTime.now();
// final difference = date2.difference(this);
// if ((difference.inDays / 7).floor() >= 2) {
// return DateHelper.formatAsDayMonthYear(this, isArabic: isArabic,);
// } else if ((difference.inDays / 7).floor() >= 1) {
// return (numericDates) ? '1 week ago' : 'Last week';
// } else if (difference.inDays >= 2) {
// return '${difference.inDays} days ago';
// } else if (difference.inDays >= 1) {
// return (numericDates) ? '1 day ago' : 'Yesterday';
// } else if (difference.inHours >= 2) {
// return '${difference.inHours} hours ago';
// } else if (difference.inHours >= 1) {
// return (numericDates) ? '1 hour ago' : 'An hour ago';
// } else if (difference.inMinutes >= 2) {
// return '${difference.inMinutes} min ago';
// } else if (difference.inMinutes >= 1) {
// return (numericDates) ? '1 min ago' : 'A min ago';
// } else if (difference.inSeconds >= 3) {
// return '${difference.inSeconds} sec ago';
// } else {
// return 'Just now';
// }
// }
// }
extension DateTimeConversions on DateTime { extension DateTimeConversions on DateTime {
String getTimeAgo({bool numericDates = true}) { String getTimeAgo({
final date2 = DateTime.now(); bool numericDates = true,
final difference = date2.difference(this); bool isArabic = false,
}) {
final now = DateTime.now();
final difference = now.difference(this);
if ((difference.inDays / 7).floor() >= 2) { if ((difference.inDays / 7).floor() >= 2) {
return DateHelper.formatAsDayMonthYear(this); return DateHelper.formatAsDayMonthYear(
this,
isArabic: isArabic,
);
} else if ((difference.inDays / 7).floor() >= 1) { } else if ((difference.inDays / 7).floor() >= 1) {
return (numericDates) ? '1 week ago' : 'Last week'; if (isArabic) {
return numericDates ? 'منذ أسبوع' : 'الأسبوع الماضي';
}
return numericDates ? '1 week ago' : 'Last week';
} else if (difference.inDays >= 2) { } else if (difference.inDays >= 2) {
return '${difference.inDays} days ago'; return isArabic
? 'منذ ${difference.inDays} أيام'
: '${difference.inDays} days ago';
} else if (difference.inDays >= 1) { } else if (difference.inDays >= 1) {
return (numericDates) ? '1 day ago' : 'Yesterday'; if (isArabic) {
return numericDates ? 'منذ يوم' : 'أمس';
}
return numericDates ? '1 day ago' : 'Yesterday';
} else if (difference.inHours >= 2) { } else if (difference.inHours >= 2) {
return '${difference.inHours} hours ago'; return isArabic
? 'منذ ${difference.inHours} ساعات'
: '${difference.inHours} hours ago';
} else if (difference.inHours >= 1) { } else if (difference.inHours >= 1) {
return (numericDates) ? '1 hour ago' : 'An hour ago'; if (isArabic) {
return numericDates ? 'منذ ساعة' : 'قبل ساعة';
}
return numericDates ? '1 hour ago' : 'An hour ago';
} else if (difference.inMinutes >= 2) { } else if (difference.inMinutes >= 2) {
return '${difference.inMinutes} min ago'; return isArabic
? 'منذ ${difference.inMinutes} دقائق'
: '${difference.inMinutes} min ago';
} else if (difference.inMinutes >= 1) { } else if (difference.inMinutes >= 1) {
return (numericDates) ? '1 min ago' : 'A min ago'; if (isArabic) {
return numericDates ? 'منذ دقيقة' : 'قبل دقيقة';
}
return numericDates ? '1 min ago' : 'A min ago';
} else if (difference.inSeconds >= 3) { } else if (difference.inSeconds >= 3) {
return '${difference.inSeconds} sec ago'; return isArabic
? 'منذ ${difference.inSeconds} ثوانٍ'
: '${difference.inSeconds} sec ago';
} else { } else {
return 'Just now'; return isArabic ? 'الآن' : 'Just now';
} }
} }
} }
@ -1075,13 +1145,13 @@ extension RequestDeliveryOptionEnumTosTRINGExt on RequestDeliveryOptionEnum {
String getStringFromRequestDeliveryOptionEnum() { String getStringFromRequestDeliveryOptionEnum() {
switch (this) { switch (this) {
case RequestDeliveryOptionEnum.delivery: case RequestDeliveryOptionEnum.delivery:
return "Delivery"; return LocaleKeys.delivery.tr();
case RequestDeliveryOptionEnum.selfPickup: case RequestDeliveryOptionEnum.selfPickup:
return "Self Pickup"; return LocaleKeys.selfPickup.tr();
case RequestDeliveryOptionEnum.both: case RequestDeliveryOptionEnum.both:
return "Delivery, Self Pickup"; return "${LocaleKeys.delivery.tr()}, ${LocaleKeys.selfPickup.tr()}";
default: default:
return "Self Pickup"; return LocaleKeys.selfPickup.tr();
} }
} }
} }

@ -148,14 +148,14 @@ class CodegenLoader extends AssetLoader{
"insert_otp_code": "ادخل الرمز", "insert_otp_code": "ادخل الرمز",
"type_code": "الرجاء كتابة الرمز الذي أرسلناه إلى", "type_code": "الرجاء كتابة الرمز الذي أرسلناه إلى",
"resend_code": "أعد إرسال الرمز", "resend_code": "أعد إرسال الرمز",
"check_code": "التحقق من الشفرة", "check_code": "التحقق من الرمز",
"time_will_expire": "إعادة إرسال الرمز بتنسيق", "time_will_expire": "إعادة إرسال الرمز بتنسيق",
"sec": "ثانية", "sec": "ثانية",
"providers": "الموفرون", "providers": "الموفرون",
"appointments": "تعيينات", "appointments": "المواعيد",
"home": "بيت", "home": "بيت",
"ads": "إعلانات", "ads": "إعلانات",
"settings": "إعدادات", "settings": "الإعدادات",
"view_all": "مشاهدة الكل", "view_all": "مشاهدة الكل",
"my_active_Ads": "إعلاناتي النشطة", "my_active_Ads": "إعلاناتي النشطة",
"recommended_ads": "الإعلانات الموصى بها", "recommended_ads": "الإعلانات الموصى بها",
@ -228,8 +228,8 @@ class CodegenLoader extends AssetLoader{
"noItemSelectedYet": "لم يتم تحديد أي عنصر بعد", "noItemSelectedYet": "لم يتم تحديد أي عنصر بعد",
"totalAdditionalAmount": "إجمالي المبلغ الإضافي", "totalAdditionalAmount": "إجمالي المبلغ الإضافي",
"sar": "ريال سعودي", "sar": "ريال سعودي",
"cancel": "يلغي", "cancel": "الغاء",
"add": "يضيف", "add": "اضافه",
"itemsAddedSuccessfully": "تمت إضافة العناصر بنجاح", "itemsAddedSuccessfully": "تمت إضافة العناصر بنجاح",
"pleaseSelectItems": "يرجى اختيار العناصر", "pleaseSelectItems": "يرجى اختيار العناصر",
"totalAmount": "المبلغ الإجمالي", "totalAmount": "المبلغ الإجمالي",
@ -242,10 +242,10 @@ class CodegenLoader extends AssetLoader{
"typeHere": "اكتب هنا...", "typeHere": "اكتب هنا...",
"pleaseSelectReason": "يرجى تحديد السبب", "pleaseSelectReason": "يرجى تحديد السبب",
"noAppointmentFound": "لم يتم العثور على مواعيد", "noAppointmentFound": "لم يتم العثور على مواعيد",
"slotsOverview": "نظرة عامة على الفتحات", "slotsOverview": "نظرة عامة على الحجوزات",
"empty": "فارغ", "empty": "فارغ",
"occupied": "محجوز", "occupied": "محجوز",
"totalSlots": "إجمالي الفتحات", "totalSlots": "إجمالي الحجوزات",
"selectAppointments": "اختيار المواعيد", "selectAppointments": "اختيار المواعيد",
"appointmentMergeSuccessfully": "دمج المواعيد تم بنجاح", "appointmentMergeSuccessfully": "دمج المواعيد تم بنجاح",
"updateAppointment": "تحديث الموعد", "updateAppointment": "تحديث الموعد",
@ -254,11 +254,11 @@ class CodegenLoader extends AssetLoader{
"appointmentisCompleted": "تم اكتمال الموعد", "appointmentisCompleted": "تم اكتمال الموعد",
"workStart": "بدء العمل", "workStart": "بدء العمل",
"payNow": "ادفع الآن", "payNow": "ادفع الآن",
"arrived": "وصل", "arrived": "تم الوصول",
"payLater": "ادفع لاحقًا", "payLater": "ادفع لاحقًا",
"complete": "اكتمل", "complete": "اكتمل",
"makeAnOffer": "قدم عرضًا", "makeAnOffer": "قدم عرضًا",
"provideOffer": "تقديم العرض", "provideOffer": "تقديم عرض",
"enterAmount": "أدخل المبلغ", "enterAmount": "أدخل المبلغ",
"submit": "إرسال", "submit": "إرسال",
"viewChat": "عرض الدردشة", "viewChat": "عرض الدردشة",
@ -270,6 +270,9 @@ class CodegenLoader extends AssetLoader{
"sendOTP": "إرسال رمز OTP", "sendOTP": "إرسال رمز OTP",
"noUserAddedYet": "لم يتم إضافة مستخدم بعد", "noUserAddedYet": "لم يتم إضافة مستخدم بعد",
"assignDealerUser": "تعيين مستخدم تاجر", "assignDealerUser": "تعيين مستخدم تاجر",
"confirmAssignDealerUser": "هل أنت متأكد من رغبتك في تعيين مستخدم التاجر هذا للفرع؟",
"removeDealerUser": "إزالة مستخدم تاجر",
"confirmRemoveDealerUser": "هل أنت متأكد من رغبتك في إزالة مستخدم التاجر هذا من الفرع؟",
"createNewUser": "إنشاء مستخدم جديد", "createNewUser": "إنشاء مستخدم جديد",
"dealerUser": "مستخدم تاجر", "dealerUser": "مستخدم تاجر",
"setorEditBranchSchedule": "تعيين أو تحرير جدول الفروع", "setorEditBranchSchedule": "تعيين أو تحرير جدول الفروع",
@ -422,7 +425,7 @@ class CodegenLoader extends AssetLoader{
"passwordIsStrong": "كلمة المرور الخاصة بك قوية", "passwordIsStrong": "كلمة المرور الخاصة بك قوية",
"passwordVeryStrong": "كلمة المرور الخاصة بك قوية جدًا", "passwordVeryStrong": "كلمة المرور الخاصة بك قوية جدًا",
"selectDuration": "اختر المدة", "selectDuration": "اختر المدة",
"daysVar": "الأيام", "daysVar": "يوم",
"adCharges": "رسوم الإعلان", "adCharges": "رسوم الإعلان",
"select": "اختر", "select": "اختر",
"deleteAdConfirmation": "هل تريد حذف الإعلان؟", "deleteAdConfirmation": "هل تريد حذف الإعلان؟",
@ -433,8 +436,12 @@ class CodegenLoader extends AssetLoader{
"accidentFree": "خالية من الحوادث", "accidentFree": "خالية من الحوادث",
"brandNew": "جديدة", "brandNew": "جديدة",
"used": "مستعملة", "used": "مستعملة",
"all": "الكل",
"newSparePart": "قطع غيار جديدة",
"usedSparePart": "قطع غيار مستعملة",
"hasYourCarHadAccident": "هل تعرضت سيارتك لأي حادث؟", "hasYourCarHadAccident": "هل تعرضت سيارتك لأي حادث؟",
"odometerReading": "قراءة عداد المسافات (كم)", "odometerReading": "قراءة عداد المسافات (كم)",
"canMileageIncrease": "هل يمكن زيادة المسافة المقطوعة عند الفحص؟",
"currentlyInUse": "قيد الاستخدام حاليًا", "currentlyInUse": "قيد الاستخدام حاليًا",
"demand": "الطلب", "demand": "الطلب",
"adDurationExpired": "انتهت مدة عرض الإعلان الخاص بك", "adDurationExpired": "انتهت مدة عرض الإعلان الخاص بك",
@ -606,6 +613,9 @@ class CodegenLoader extends AssetLoader{
"myNearbyBranches": "فروعي القريبة", "myNearbyBranches": "فروعي القريبة",
"myRecentBranches": "فروعي الأخيرة", "myRecentBranches": "فروعي الأخيرة",
"favoriteList": "قائمة المفضلة", "favoriteList": "قائمة المفضلة",
"myFavouriteProviders": "مزودي الخدمة المفضلين",
"myFavouriteAds": "إعلاناتي المفضلة",
"noFavouriteAds": "لا توجد إعلانات مفضلة",
"noReviewsBranch": "هذا الفرع لا يوجد لديه تعليقات.", "noReviewsBranch": "هذا الفرع لا يوجد لديه تعليقات.",
"readLess": "اقرأ أقل", "readLess": "اقرأ أقل",
"readMoreReviews": "اقرأ المزيد من التقييمات", "readMoreReviews": "اقرأ المزيد من التقييمات",
@ -671,7 +681,7 @@ class CodegenLoader extends AssetLoader{
"servicesNotAvailableHomeLocation": "بعض الخدمات غير متوفرة في الموقع المنزلي.", "servicesNotAvailableHomeLocation": "بعض الخدمات غير متوفرة في الموقع المنزلي.",
"additionalChargesNotice": "هذه الرسوم إضافية على رسوم الخدمة الفعلية. قد تختلف الرسوم للعناصر الثقيلة.", "additionalChargesNotice": "هذه الرسوم إضافية على رسوم الخدمة الفعلية. قد تختلف الرسوم للعناصر الثقيلة.",
"selectServiceLocation": "اختر موقع الخدمة", "selectServiceLocation": "اختر موقع الخدمة",
"apptOn": "التعيين في", "apptOn": "الموعد في",
"merged": "مُدمج", "merged": "مُدمج",
"date": "التاريخ", "date": "التاريخ",
"selectAll": "اختر الكل", "selectAll": "اختر الكل",
@ -833,14 +843,81 @@ class CodegenLoader extends AssetLoader{
"specialRequestsChats": "دردشات الطلبات الخاصة", "specialRequestsChats": "دردشات الطلبات الخاصة",
"selectDeliveryOption": "اختر خيار التوصيل", "selectDeliveryOption": "اختر خيار التوصيل",
"deliveryOptions": "خيارات التوصيل", "deliveryOptions": "خيارات التوصيل",
"selfPickup": "الالتقاط الذاتي", "selfPickup": "استلام ذاتي",
"updateYourLocationInfo": "قم بتحديث معلومات موقعك", "updateYourLocationInfo": "قم بتحديث معلومات موقعك",
"initiateSelfPickup": "بدء الاستلام الذاتي", "initiateSelfPickup": "بدء الاستلام الذاتي",
"selfPickupStatus": "حالة الالتقاط الذاتي", "selfPickupStatus": "حالة الاستلام الذاتي",
"myDraftAds": "مسودتي للإعلانات", "myDraftAds": "مسودتي للإعلانات",
"scheduleDeletedSuccessfully": "تم حذف الجدول بنجاح", "scheduleDeletedSuccessfully": "تم حذف الجدول بنجاح",
"addValidAddress": "رجى إضافة عنوان صالح", "addValidAddress": "رجى إضافة عنوان صالح",
"termsPrivacyPolicy": "الشروط وسياسة الخصوصية" "termsPrivacyPolicy": "الشروط وسياسة الخصوصية",
"pendingApproval": "قيد الموافقة",
"inReview": "قيد المراجعة",
"rejected": "مرفوض",
"deactivated": "معطل",
"blocked": "محظور",
"allRequests": "جميع الطلبات",
"pending": "قيد الانتظار",
"preparingToCollect": "جاري التحضير للاستلام",
"readyToCollect": "جاهز للاستلام",
"collected": "تم الاستلام",
"initiated": "تم البدء",
"inTransit": "قيد النقل",
"outForDelivery": "قيد التسليم",
"delivered": "تم التسليم",
"negotiate": "تفاوض",
"accepted": "مقبول",
"cancelled": "ملغى",
"loginToAnotherAccount": "تسجيل الدخول إلى حساب آخر",
"silver": "فضي",
"phoneWithoutCountryCode": "رقم الهاتف بدون رمز الدوله",
"sunday": "الأحد",
"monday": "الإثنين",
"tuesday": "الثلاثاء",
"wednesday": "الأربعاء",
"thursday": "الخميس",
"friday": "الجمعة",
"saturday": "السبت",
"adsWith": "الإعلانات:",
"usersWith": "المستخدمون:",
"branchesWith": "الفروع:",
"subscribed": "مشترك",
"expires": "تنتهي",
"renew": "تجديد",
"km": "كم",
"items": "عناصر",
"no_items": "لا توجد عناصر",
"allAds": "جميع الاعلانات",
"reserveCancel": "الغاء الحجز",
"pendingForReview": "قيد المراجعة",
"pendingForPayment": "قيد الدفع",
"rejected_n": "مرفوض",
"canceled": "ملغى",
"pendingForPost": "قيد النشر",
"active_n": "نشط",
"expired_n": "منتهي الصلاحية",
"sold_n": "مباع",
"reserved_n": "محجوز",
"buyingService": "خدمة الشراء",
"deActive": "غير نشط",
"pendingForActive": "قيد التفعيل",
"updateItem": "تحديث العنصر",
"createItem": "إنشاء عنصر",
"pleaseAddAtLeastOneItemImage": "يرجى إضافة صورة واحدة على الأقل للعنصر",
"pleaseAddValidItemName": "يرجى إضافة اسم عنصر صحيح",
"pleaseAddValidItemDescription": "يرجى إضافة وصف عنصر صحيح",
"pleaseAddValidItemPrice": "يرجى إضافة سعر عنصر صحيح",
"remaining": "متبقي",
"month": "شهر",
"currentPackage": "الحزمة الحالية",
"forVar": "لمدة",
"branch": "الفرع",
"shareAd": "مشاركة الإعلان",
"checkOutThisAd": "تفقد هذه السيارة الرائعة!",
"downloadApp": "تحميل تطبيق موّاتر:",
"androidApp": "أندرويد",
"iosApp": "آيفون",
"pleaseWait": "يرجى الانتظار..."
}; };
static const Map<String,dynamic> _en_US = { static const Map<String,dynamic> _en_US = {
"firstTimeLogIn": "First Time Log In", "firstTimeLogIn": "First Time Log In",
@ -1098,6 +1175,9 @@ static const Map<String,dynamic> _en_US = {
"sendOTP": "Send OTP", "sendOTP": "Send OTP",
"noUserAddedYet": "No User Added Yet", "noUserAddedYet": "No User Added Yet",
"assignDealerUser": "Assign Dealer User", "assignDealerUser": "Assign Dealer User",
"confirmAssignDealerUser": "Are you sure you want to assign this dealer user to the branch?",
"removeDealerUser": "Remove Dealer User",
"confirmRemoveDealerUser": "Are you sure you want to remove this dealer user from the branch?",
"createNewUser": "Create New User", "createNewUser": "Create New User",
"dealerUser": "Dealer User", "dealerUser": "Dealer User",
"setorEditBranchSchedule": "Set or Edit Branch Schedule", "setorEditBranchSchedule": "Set or Edit Branch Schedule",
@ -1262,8 +1342,12 @@ static const Map<String,dynamic> _en_US = {
"accidentFree": "Accident Free", "accidentFree": "Accident Free",
"brandNew": "Brand New", "brandNew": "Brand New",
"used": "Used", "used": "Used",
"all": "All",
"newSparePart": "New Spare Part",
"usedSparePart": "Used Spare Part",
"hasYourCarHadAccident": "Has your car had any accident?", "hasYourCarHadAccident": "Has your car had any accident?",
"odometerReading": "Odometer Reading (Km)", "odometerReading": "Odometer Reading (Km)",
"canMileageIncrease": "Can the mileage be increased when checking?",
"currentlyInUse": "Currently in Use", "currentlyInUse": "Currently in Use",
"demand": "Demand", "demand": "Demand",
"adDurationExpired": "Your Ad Duration time is over", "adDurationExpired": "Your Ad Duration time is over",
@ -1435,6 +1519,9 @@ static const Map<String,dynamic> _en_US = {
"myNearbyBranches": "My Nearby Branches", "myNearbyBranches": "My Nearby Branches",
"myRecentBranches": "My Recent Branches", "myRecentBranches": "My Recent Branches",
"favoriteList": "Favorite List", "favoriteList": "Favorite List",
"myFavouriteProviders": "My Favourite Providers",
"myFavouriteAds": "My Favourite Ads",
"noFavouriteAds": "No Favourite Ads Found",
"noReviewsBranch": "This branch has no reviews.", "noReviewsBranch": "This branch has no reviews.",
"readLess": "Read Less", "readLess": "Read Less",
"readMoreReviews": "Read More Reviews", "readMoreReviews": "Read More Reviews",
@ -1666,7 +1753,74 @@ static const Map<String,dynamic> _en_US = {
"myDraftAds": "My Draft Ads", "myDraftAds": "My Draft Ads",
"scheduleDeletedSuccessfully": "The schedule has been deleted successfully", "scheduleDeletedSuccessfully": "The schedule has been deleted successfully",
"addValidAddress": "Please add a valid address", "addValidAddress": "Please add a valid address",
"termsPrivacyPolicy": "Terms & Privacy Policy" "termsPrivacyPolicy": "Terms & Privacy Policy",
"pendingApproval": "Pending Approval",
"inReview": "In Review",
"rejected": "Rejected",
"deactivated": "Deactivated",
"blocked": "Blocked",
"allRequests": "All Requests",
"pending": "Pending",
"preparingToCollect": "Preparing To Collect",
"readyToCollect": "Ready To Collect",
"collected": "Collected",
"initiated": "Initiated",
"inTransit": "In Transit",
"outForDelivery": "Out For De,livery",
"negotiate": "Negotiate",
"accepted": "Accepted",
"cancelled": "Cancelled",
"delivered": "Delivered",
"loginToAnotherAccount": "Login To Another Account",
"silver": "Silver",
"phoneWithoutCountryCode": "Phone without country code",
"sunday": "Sunday",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"adsWith": "Ads:",
"usersWith": "Users:",
"branchesWith": "Branches:",
"subscribed": "Subscribed",
"expires": "Expires",
"renew": "Renew",
"km": "km",
"items": "items",
"no_items": "No items",
"allAds": "All Ads",
"reserveCancel": "Reserve Cancel",
"pendingForReview": "Pending For Review",
"pendingForPayment": "Pending For Payment",
"rejected_n": "Rejected",
"canceled": "Cancelled",
"pendingForPost": "Pending For Post",
"active_n": "Active",
"expired_n": "Expired",
"sold_n": "Sold",
"reserved_n": "Reserved",
"buyingService": "Buying Service",
"deActive": "De Active",
"pendingForActive": "Pending For Active",
"updateItem": "Update Item",
"createItem": "Create Item",
"pleaseAddAtLeastOneItemImage": "Please add at least one item image",
"pleaseAddValidItemName": "Please add valid item name",
"pleaseAddValidItemDescription": "Please add valid item description",
"pleaseAddValidItemPrice": "Please add valid item price",
"remaining": "remaining",
"month": "Month",
"currentPackage": "Current Package",
"forVar": "for",
"branch": "branch",
"shareAd": "Share Ad",
"checkOutThisAd": "Check out this amazing vehicle!",
"downloadApp": "Download MoWater App:",
"androidApp": "Android",
"iosApp": "iOS",
"pleaseWait": "Please wait..."
}; };
static const Map<String, Map<String,dynamic>> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US}; static const Map<String, Map<String,dynamic>> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US};
} }

@ -236,6 +236,9 @@ abstract class LocaleKeys {
static const sendOTP = 'sendOTP'; static const sendOTP = 'sendOTP';
static const noUserAddedYet = 'noUserAddedYet'; static const noUserAddedYet = 'noUserAddedYet';
static const assignDealerUser = 'assignDealerUser'; static const assignDealerUser = 'assignDealerUser';
static const confirmAssignDealerUser = 'confirmAssignDealerUser';
static const removeDealerUser = 'removeDealerUser';
static const confirmRemoveDealerUser = 'confirmRemoveDealerUser';
static const createNewUser = 'createNewUser'; static const createNewUser = 'createNewUser';
static const dealerUser = 'dealerUser'; static const dealerUser = 'dealerUser';
static const setorEditBranchSchedule = 'setorEditBranchSchedule'; static const setorEditBranchSchedule = 'setorEditBranchSchedule';
@ -398,8 +401,12 @@ abstract class LocaleKeys {
static const accidentFree = 'accidentFree'; static const accidentFree = 'accidentFree';
static const brandNew = 'brandNew'; static const brandNew = 'brandNew';
static const used = 'used'; static const used = 'used';
static const all = 'all';
static const newSparePart = 'newSparePart';
static const usedSparePart = 'usedSparePart';
static const hasYourCarHadAccident = 'hasYourCarHadAccident'; static const hasYourCarHadAccident = 'hasYourCarHadAccident';
static const odometerReading = 'odometerReading'; static const odometerReading = 'odometerReading';
static const canMileageIncrease = 'canMileageIncrease';
static const currentlyInUse = 'currentlyInUse'; static const currentlyInUse = 'currentlyInUse';
static const demand = 'demand'; static const demand = 'demand';
static const adDurationExpired = 'adDurationExpired'; static const adDurationExpired = 'adDurationExpired';
@ -571,6 +578,9 @@ abstract class LocaleKeys {
static const myNearbyBranches = 'myNearbyBranches'; static const myNearbyBranches = 'myNearbyBranches';
static const myRecentBranches = 'myRecentBranches'; static const myRecentBranches = 'myRecentBranches';
static const favoriteList = 'favoriteList'; static const favoriteList = 'favoriteList';
static const myFavouriteProviders = 'myFavouriteProviders';
static const myFavouriteAds = 'myFavouriteAds';
static const noFavouriteAds = 'noFavouriteAds';
static const noReviewsBranch = 'noReviewsBranch'; static const noReviewsBranch = 'noReviewsBranch';
static const readLess = 'readLess'; static const readLess = 'readLess';
static const readMoreReviews = 'readMoreReviews'; static const readMoreReviews = 'readMoreReviews';
@ -806,5 +816,72 @@ abstract class LocaleKeys {
static const scheduleDeletedSuccessfully = 'scheduleDeletedSuccessfully'; static const scheduleDeletedSuccessfully = 'scheduleDeletedSuccessfully';
static const addValidAddress = 'addValidAddress'; static const addValidAddress = 'addValidAddress';
static const termsPrivacyPolicy = 'termsPrivacyPolicy'; static const termsPrivacyPolicy = 'termsPrivacyPolicy';
static const pendingApproval = 'pendingApproval';
static const inReview = 'inReview';
static const rejected = 'rejected';
static const deactivated = 'deactivated';
static const blocked = 'blocked';
static const allRequests = 'allRequests';
static const pending = 'pending';
static const preparingToCollect = 'preparingToCollect';
static const readyToCollect = 'readyToCollect';
static const collected = 'collected';
static const initiated = 'initiated';
static const inTransit = 'inTransit';
static const outForDelivery = 'outForDelivery';
static const delivered = 'delivered';
static const negotiate = 'negotiate';
static const accepted = 'accepted';
static const cancelled = 'cancelled';
static const loginToAnotherAccount = 'loginToAnotherAccount';
static const silver = 'silver';
static const phoneWithoutCountryCode = 'phoneWithoutCountryCode';
static const sunday = 'sunday';
static const monday = 'monday';
static const tuesday = 'tuesday';
static const wednesday = 'wednesday';
static const thursday = 'thursday';
static const friday = 'friday';
static const saturday = 'saturday';
static const adsWith = 'adsWith';
static const usersWith = 'usersWith';
static const branchesWith = 'branchesWith';
static const subscribed = 'subscribed';
static const expires = 'expires';
static const renew = 'renew';
static const km = 'km';
static const items = 'items';
static const no_items = 'no_items';
static const allAds = 'allAds';
static const reserveCancel = 'reserveCancel';
static const pendingForReview = 'pendingForReview';
static const pendingForPayment = 'pendingForPayment';
static const rejected_n = 'rejected_n';
static const canceled = 'canceled';
static const pendingForPost = 'pendingForPost';
static const active_n = 'active_n';
static const expired_n = 'expired_n';
static const sold_n = 'sold_n';
static const reserved_n = 'reserved_n';
static const buyingService = 'buyingService';
static const deActive = 'deActive';
static const pendingForActive = 'pendingForActive';
static const updateItem = 'updateItem';
static const createItem = 'createItem';
static const pleaseAddAtLeastOneItemImage = 'pleaseAddAtLeastOneItemImage';
static const pleaseAddValidItemName = 'pleaseAddValidItemName';
static const pleaseAddValidItemDescription = 'pleaseAddValidItemDescription';
static const pleaseAddValidItemPrice = 'pleaseAddValidItemPrice';
static const remaining = 'remaining';
static const month = 'month';
static const currentPackage = 'currentPackage';
static const forVar = 'forVar';
static const branch = 'branch';
static const shareAd = 'shareAd';
static const checkOutThisAd = 'checkOutThisAd';
static const downloadApp = 'downloadApp';
static const androidApp = 'androidApp';
static const iosApp = 'iosApp';
static const pleaseWait = 'pleaseWait';
} }

@ -15,6 +15,7 @@ class AdDetailsModel {
// List<Null>? reserved; // List<Null>? reserved;
int? statusID; int? statusID;
String? statuslabel; String? statuslabel;
String? statuslabelN;
double? adsDurationPrice; double? adsDurationPrice;
double? adsDurationDiscount; double? adsDurationDiscount;
double? adsDurationDiscountPrice; double? adsDurationDiscountPrice;
@ -45,6 +46,7 @@ class AdDetailsModel {
AdReserveStatus? adReserveStatus; AdReserveStatus? adReserveStatus;
bool? isMyAd; bool? isMyAd;
bool? isReservedByMe; bool? isReservedByMe;
bool? isAdFavorite;
String? phoneNo; String? phoneNo;
String? whatsAppNo; String? whatsAppNo;
String? adOwnerName; String? adOwnerName;
@ -64,6 +66,7 @@ class AdDetailsModel {
// this.reserved, // this.reserved,
this.statusID, this.statusID,
this.statuslabel, this.statuslabel,
this.statuslabelN,
this.adsDurationPrice, this.adsDurationPrice,
this.adsDurationDiscount, this.adsDurationDiscount,
this.adsDurationDiscountPrice, this.adsDurationDiscountPrice,
@ -93,6 +96,7 @@ class AdDetailsModel {
this.adReserveStatus, this.adReserveStatus,
this.isMyAd, this.isMyAd,
this.isReservedByMe, this.isReservedByMe,
this.isAdFavorite,
this.phoneNo, this.phoneNo,
this.whatsAppNo, this.whatsAppNo,
this.adOwnerName, this.adOwnerName,
@ -124,6 +128,7 @@ class AdDetailsModel {
} }
statusID = json['statusID']; statusID = json['statusID'];
statuslabel = json['statuslabel']; statuslabel = json['statuslabel'];
statuslabelN = json['statuslabelN'];
adsDurationPrice = json['adsDurationPrice']; adsDurationPrice = json['adsDurationPrice'];
adsDurationDiscount = json['adsDurationDiscount']; adsDurationDiscount = json['adsDurationDiscount'];
adsDurationDiscountPrice = json['adsDurationDiscountPrice']; adsDurationDiscountPrice = json['adsDurationDiscountPrice'];
@ -161,6 +166,7 @@ class AdDetailsModel {
createdByRoleEnum = (json['createdByRole'] as int).toCreatedByRoleEnum(); createdByRoleEnum = (json['createdByRole'] as int).toCreatedByRoleEnum();
isMyAd = isMyAds; isMyAd = isMyAds;
isReservedByMe = false; isReservedByMe = false;
isAdFavorite = json['isFavourite'] ?? false;
totalItemsCount = totalItems; totalItemsCount = totalItems;
} }
} }
@ -169,6 +175,7 @@ class Vehicle {
int? id; int? id;
int? cityID; int? cityID;
String? cityName; String? cityName;
String? cityNameN;
double? demandAmount; double? demandAmount;
bool? isActive; bool? isActive;
bool? isFinanceAvailable; bool? isFinanceAvailable;
@ -191,6 +198,7 @@ class Vehicle {
String? vehicleVIN; String? vehicleVIN;
int? countryID; int? countryID;
String? countryName; String? countryName;
String? countryNameN;
String? currency; String? currency;
int? odometer; int? odometer;
int? isInUsed; int? isInUsed;
@ -200,6 +208,7 @@ class Vehicle {
{this.id, {this.id,
this.cityID, this.cityID,
this.cityName, this.cityName,
this.cityNameN,
this.demandAmount, this.demandAmount,
this.isActive, this.isActive,
this.isFinanceAvailable, this.isFinanceAvailable,
@ -222,6 +231,7 @@ class Vehicle {
this.vehicleVIN, this.vehicleVIN,
this.countryID, this.countryID,
this.countryName, this.countryName,
this.countryNameN,
this.currency, this.currency,
this.odometer, this.odometer,
this.isInUsed, this.isInUsed,
@ -231,6 +241,7 @@ class Vehicle {
id = json['id']; id = json['id'];
cityID = json['cityID']; cityID = json['cityID'];
cityName = json['cityName']; cityName = json['cityName'];
cityNameN = json['cityNameN'];
demandAmount = json['demandAmount']; demandAmount = json['demandAmount'];
isActive = json['isActive']; isActive = json['isActive'];
isFinanceAvailable = json['isFinanceAvailable']; isFinanceAvailable = json['isFinanceAvailable'];
@ -263,6 +274,7 @@ class Vehicle {
vehicleVIN = json['vehicleVIN']; vehicleVIN = json['vehicleVIN'];
countryID = json['countryID']; countryID = json['countryID'];
countryName = json['duration'] != null && json['duration']['country'] != null ? json['duration']['country']['label'] : null; countryName = json['duration'] != null && json['duration']['country'] != null ? json['duration']['country']['label'] : null;
countryNameN = json['duration'] != null && json['duration']['country'] != null ? json['duration']['country']['label'] : null;
currency = json['currency']; currency = json['currency'];
odometer = json['odometer']; odometer = json['odometer'];
isInUsed = json['isInUsed']; isInUsed = json['isInUsed'];
@ -347,6 +359,7 @@ class Mileage {
class ModelYear { class ModelYear {
int? id; int? id;
String? label; String? label;
String? labelN;
ModelYear({this.id, this.label}); ModelYear({this.id, this.label});

@ -2,6 +2,7 @@ class EnumsModel {
int id; int id;
int enumTypeId; int enumTypeId;
String enumValueStr; String enumValueStr;
String enumValueStrN;
String enumValueStrDes; String enumValueStrDes;
int enumValue; int enumValue;
bool isActive; bool isActive;
@ -10,6 +11,7 @@ class EnumsModel {
required this.id, required this.id,
required this.enumTypeId, required this.enumTypeId,
required this.enumValueStr, required this.enumValueStr,
required this.enumValueStrN,
required this.enumValueStrDes, required this.enumValueStrDes,
required this.enumValue, required this.enumValue,
required this.isActive, required this.isActive,
@ -19,6 +21,7 @@ class EnumsModel {
id: json["id"], id: json["id"],
enumTypeId: json["enumTypeID"], enumTypeId: json["enumTypeID"],
enumValueStr: json["enumValueStr"], enumValueStr: json["enumValueStr"],
enumValueStrN: json["enumValueStrN"] ?? json["enumValueStr"],
enumValueStrDes: json["enumValueStrDes"] ?? json["enumValueStr"], enumValueStrDes: json["enumValueStrDes"] ?? json["enumValueStr"],
enumValue: json["enumValue"], enumValue: json["enumValue"],
isActive: json["isActive"], isActive: json["isActive"],
@ -26,6 +29,7 @@ class EnumsModel {
@override @override
String toString() { String toString() {
return 'EnumsModel{id: $id, enumTypeId: $enumTypeId, enumValueStr: $enumValueStr,enumValueStrDes: $enumValueStrDes, enumValue: $enumValue, isActive: $isActive}'; return 'EnumsModel{id: $id, enumTypeId: $enumTypeId, enumValueStr: $enumValueStr,enumValueStrDes: $enumValueStrDes, enumValue: $enumValue, isActive: $isActive,'
'enumValueStrN: $enumValueStrN}';
} }
} }

@ -1,10 +1,14 @@
class FilterListModel { class FilterListModel {
String title; String title;
String titleN;
String description;
String descriptionN;
String iconUrl; String iconUrl;
int id; int id;
bool isSelected; bool isSelected;
bool isCustom;
FilterListModel({required this.id, required this.isSelected, this.iconUrl = "", required this.title}); FilterListModel({required this.id, required this.isSelected, this.isCustom = false, this.iconUrl = "", required this.title, this.titleN = "", this.description = "", this.descriptionN = "",});
} }
class SelectionModel { class SelectionModel {

@ -32,6 +32,7 @@ class BranchDetailModel {
String? countryName; String? countryName;
bool isExpanded = false; bool isExpanded = false;
bool? isFavorite; bool? isFavorite;
String? cityNameN;
BranchDetailModel({ BranchDetailModel({
@ -62,6 +63,7 @@ class BranchDetailModel {
this.countryName, this.countryName,
this.isFavorite, this.isFavorite,
required this.isExpanded, required this.isExpanded,
this.cityNameN,
}); });
List<GenericImageModel> populateBranchImages(value) { List<GenericImageModel> populateBranchImages(value) {
@ -106,6 +108,7 @@ class BranchDetailModel {
branchAuditTrial = json["branchAuditTrial"] == null ? [] : List<BranchAuditTrial>.from(json["branchAuditTrial"]!.map((x) => BranchAuditTrial.fromJson(x))); branchAuditTrial = json["branchAuditTrial"] == null ? [] : List<BranchAuditTrial>.from(json["branchAuditTrial"]!.map((x) => BranchAuditTrial.fromJson(x)));
isExpanded = false; isExpanded = false;
isFavorite = false; isFavorite = false;
cityNameN = json["cityNameN"];
} }
} }

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart';
import 'package:mc_common_app/models/advertisment_models/ad_details_model.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/models/chat_models/chat_message_model.dart';
@ -12,8 +14,11 @@ class RequestModel {
ShippingRequestStatusEnum? shippingStatusEnum; ShippingRequestStatusEnum? shippingStatusEnum;
SelfPickupRequestStatusEnum? selfPickupRequestStatusEnum; SelfPickupRequestStatusEnum? selfPickupRequestStatusEnum;
String cityName; String cityName;
String cityNameN;
String vehicleTypeName; String vehicleTypeName;
String vehicleTypeNameN;
String countryName; String countryName;
String countryNameN;
String customerName; String customerName;
String customerID; String customerID;
String address; String address;
@ -47,6 +52,7 @@ class RequestModel {
int? modifiedBy; int? modifiedBy;
String? modifiedOn; String? modifiedOn;
List<ChatMessageModel> chatMessages; List<ChatMessageModel> chatMessages;
String? requestStatusNameN;
RequestModel({ RequestModel({
required this.requestType, required this.requestType,
@ -57,8 +63,11 @@ class RequestModel {
required this.shippingStatusEnum, required this.shippingStatusEnum,
required this.selfPickupRequestStatusEnum, required this.selfPickupRequestStatusEnum,
required this.cityName, required this.cityName,
required this.cityNameN,
required this.vehicleTypeName, required this.vehicleTypeName,
required this.vehicleTypeNameN,
required this.countryName, required this.countryName,
required this.countryNameN,
required this.customerName, required this.customerName,
required this.customerID, required this.customerID,
required this.serviceProviders, required this.serviceProviders,
@ -92,6 +101,7 @@ class RequestModel {
required this.modifiedBy, required this.modifiedBy,
required this.modifiedOn, required this.modifiedOn,
required this.chatMessages, required this.chatMessages,
required this.requestStatusNameN,
}); });
List<GenericImageModel> populateRequestImages(value) { List<GenericImageModel> populateRequestImages(value) {
@ -114,8 +124,11 @@ class RequestModel {
shippingStatusEnum: json['shippingRequestStatus'] != null ? (json['shippingRequestStatus'] as int).toShippingStatusEnum() : ShippingRequestStatusEnum.pending, shippingStatusEnum: json['shippingRequestStatus'] != null ? (json['shippingRequestStatus'] as int).toShippingStatusEnum() : ShippingRequestStatusEnum.pending,
selfPickupRequestStatusEnum: json['selfPickupRequestStatus'] != null ? (json['selfPickupRequestStatus'] as int).toSelfPickupStatusEnum() : SelfPickupRequestStatusEnum.preparingToCollect, selfPickupRequestStatusEnum: json['selfPickupRequestStatus'] != null ? (json['selfPickupRequestStatus'] as int).toSelfPickupStatusEnum() : SelfPickupRequestStatusEnum.preparingToCollect,
cityName: json["cityName"], cityName: json["cityName"],
cityNameN: json["cityNameN"],
vehicleTypeName: json["vehicleTypeName"], vehicleTypeName: json["vehicleTypeName"],
vehicleTypeNameN: json["vehicleTypeNameN"],
countryName: json["countryName"], countryName: json["countryName"],
countryNameN: json["countryNameN"],
customerName: json["customerName"] ?? "", customerName: json["customerName"] ?? "",
address: json["address"] ?? "", address: json["address"] ?? "",
additionalAddressDetails: json['additionalAddress'] ?? "", additionalAddressDetails: json['additionalAddress'] ?? "",
@ -148,6 +161,7 @@ class RequestModel {
createdOn: json["createdOn"], createdOn: json["createdOn"],
modifiedBy: json["modifiedBy"], modifiedBy: json["modifiedBy"],
modifiedOn: json["modifiedOn"], modifiedOn: json["modifiedOn"],
requestStatusNameN: json["requestStatusNameN"],
chatMessages: [], chatMessages: [],
); );
} }

@ -6,8 +6,11 @@ class FAQsModel {
bool? isActive; bool? isActive;
int? channel; int? channel;
bool? isCollapsed; bool? isCollapsed;
String? questionN;
String? answerN;
FAQsModel({this.id, this.question, this.answer, this.sequenceNo, this.isActive, this.channel, this.isCollapsed}); FAQsModel({this.id, this.question, this.answer, this.sequenceNo, this.isActive, this.channel, this.isCollapsed,
this.questionN, this.answerN, });
FAQsModel.fromJson(Map<String, dynamic> json) { FAQsModel.fromJson(Map<String, dynamic> json) {
id = json['id']; id = json['id'];
@ -17,6 +20,8 @@ class FAQsModel {
isActive = json['isActive']; isActive = json['isActive'];
channel = json['channel']; channel = json['channel'];
isCollapsed = false; isCollapsed = false;
questionN = json['questionN'];
answerN = json['answerN'];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -27,6 +32,8 @@ class FAQsModel {
data['sequenceNo'] = sequenceNo; data['sequenceNo'] = sequenceNo;
data['isActive'] = isActive; data['isActive'] = isActive;
data['channel'] = channel; data['channel'] = channel;
data['questionN'] = questionN;
data['answerN'] = answerN;
return data; return data;
} }
} }

@ -57,6 +57,10 @@ abstract class AdsRepo {
Future<List<AdDetailsModel>> getMyAds(); Future<List<AdDetailsModel>> getMyAds();
Future<List<AdDetailsModel>> getFavouriteAds();
Future<GenericRespModel> toggleAdFavorite({required int adsID, required bool isFavorite});
Future<AdsBankDetailsModel?> getAdBankingAccountInfo({required int adId}); Future<AdsBankDetailsModel?> getAdBankingAccountInfo({required int adId});
Future<GenericRespModel> updateAdStatus({required int adId, required AdPostStatus adStatusToUpdate, String? comment}); Future<GenericRespModel> updateAdStatus({required int adId, required AdPostStatus adStatusToUpdate, String? comment});
@ -510,6 +514,38 @@ class AdsRepoImp implements AdsRepo {
return vehicleAdsDetails; return vehicleAdsDetails;
} }
@override
Future<List<AdDetailsModel>> getFavouriteAds() async {
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(
token: appState.getUser.data!.accessToken,
(json) => GenericRespModel.fromJson(json),
ApiConsts.favouriteAdsGet,
);
if (adsGenericModel.messageStatus != 1 || adsGenericModel.data == null) {
return [];
}
List<AdDetailsModel> favouriteAdsDetails = List.generate(adsGenericModel.data.length, (index) => AdDetailsModel.fromJson(adsGenericModel.data[index], false, adsGenericModel.totalItemsCount ?? 1));
return favouriteAdsDetails;
}
@override
Future<GenericRespModel> toggleAdFavorite({required int adsID, required bool isFavorite}) async {
var params = {
"adsID": adsID.toString(),
};
String endpoint = isFavorite ? ApiConsts.unFavouriteAds : ApiConsts.favouriteAdsCreate;
GenericRespModel genericRespModel = await apiClient.postJsonForObject(
token: appState.getUser.data!.accessToken,
(json) => GenericRespModel.fromJson(json),
endpoint,
params,
);
return genericRespModel;
}
@override @override
Future<AdsBankDetailsModel?> getAdBankingAccountInfo({required int adId}) async { Future<AdsBankDetailsModel?> getAdBankingAccountInfo({required int adId}) async {
var params = { var params = {

@ -304,6 +304,8 @@ class AppointmentRepoImp implements AppointmentRepo {
queryParameters: params, queryParameters: params,
ApiConsts.customersAppointmentGetByFilters, ApiConsts.customersAppointmentGetByFilters,
); );
print("genericRespModel.data .......");
print(genericRespModel.data);
List<AppointmentListModel> appointmentList = List.generate(genericRespModel.data.length, (index) => AppointmentListModel.fromJson(genericRespModel.data[index])); List<AppointmentListModel> appointmentList = List.generate(genericRespModel.data.length, (index) => AppointmentListModel.fromJson(genericRespModel.data[index]));
return appointmentList; return appointmentList;
} }

@ -19,7 +19,7 @@ abstract class RequestRepo {
required String brand, required String brand,
required String model, required String model,
required String year, required String year,
required bool isNew, required int conditionType,
required int countryID, required int countryID,
required int cityID, required int cityID,
required String price, required String price,
@ -88,7 +88,7 @@ class RequestRepoImp implements RequestRepo {
required String brand, required String brand,
required String model, required String model,
required String year, required String year,
required bool isNew, required int conditionType,
required int countryID, required int countryID,
required int cityID, required int cityID,
required String price, required String price,
@ -103,7 +103,7 @@ class RequestRepoImp implements RequestRepo {
"brand": brand, "brand": brand,
"model": model, "model": model,
"year": year, "year": year,
"isNew": isNew, "conditionType": conditionType,
"countryID": countryID, "countryID": countryID,
"cityID": cityID, "cityID": cityID,
"price": price, "price": price,
@ -181,9 +181,13 @@ class RequestRepoImp implements RequestRepo {
"model": vehicleModel.toString(), "model": vehicleModel.toString(),
"year": vehicleYearId.toString(), "year": vehicleYearId.toString(),
"cityID": cityId.toString(), "cityID": cityId.toString(),
"isNew": conditionId == 0 ? "" : conditionId == 1,
}; };
// Add conditionType if provided (1=New, 2=Used, 3=All)
if (conditionId != null && conditionId != 0) {
paramsForGetRequests["conditionType"] = conditionId.toString();
}
if (appState.currentAppType == AppType.customer) { if (appState.currentAppType == AppType.customer) {
if (!appState.getIsViewOnly) { if (!appState.getIsViewOnly) {
paramsForGetRequests.addEntries([MapEntry("customerID", appState.getUser.data!.userInfo!.customerId ?? 0)]); paramsForGetRequests.addEntries([MapEntry("customerID", appState.getUser.data!.userInfo!.customerId ?? 0)]);

@ -15,6 +15,7 @@ abstract class PaymentService {
required PaymentTypes paymentType, required PaymentTypes paymentType,
required Function() onSuccess, required Function() onSuccess,
required Function() onFailure, required Function() onFailure,
Function()? onExit,
}); });
Future<void> onBrowserLoadStart({ Future<void> onBrowserLoadStart({
@ -95,11 +96,15 @@ class PaymentServiceImp implements PaymentService {
required PaymentTypes paymentType, required PaymentTypes paymentType,
required Function() onSuccess, required Function() onSuccess,
required Function() onFailure, required Function() onFailure,
Function()? onExit,
}) async { }) async {
String urlRequest = getUrlRequestByPaymentId(id: id, paymentType: paymentType, appointmentIds: appointmentIds); String urlRequest = getUrlRequestByPaymentId(id: id, paymentType: paymentType, appointmentIds: appointmentIds);
log("PaymentUrl: $urlRequest"); log("PaymentUrl: $urlRequest");
myInAppBrowser = MyInAppBrowser(onExitCallback: () { myInAppBrowser = MyInAppBrowser(onExitCallback: () {
log("Browser Exited"); log("Browser Exited");
if (onExit != null) {
onExit();
}
}, onLoadStartCallback: (String url) { }, onLoadStartCallback: (String url) {
log("Browser LoadStart for : $url"); log("Browser LoadStart for : $url");
onBrowserLoadStart(onFailure: onFailure, onSuccess: onSuccess, url: url); onBrowserLoadStart(onFailure: onFailure, onSuccess: onSuccess, url: url);

@ -21,8 +21,13 @@ class DateHelper {
return formatter.format(date); return formatter.format(date);
} }
static String formatAsTime(DateTime date) { // static String formatAsTime(DateTime date) {
final formatter = DateFormat.jm(); // final formatter = DateFormat.jm();
// return formatter.format(date);
// }
static String formatAsTime(DateTime date, {bool isArabic = false,}) {
final formatter = DateFormat.jm(
isArabic ? 'ar' : 'en',);
return formatter.format(date); return formatter.format(date);
} }
@ -36,8 +41,15 @@ class DateHelper {
return formatter.format(date); return formatter.format(date);
} }
static String formatAsDayMonthYear(DateTime? date) { // static String formatAsDayMonthYear(DateTime? date) {
final formatter = DateFormat('d MMM, y'); // final formatter = DateFormat('d MMM, y');
// return date != null ? formatter.format(date) : "N/A";
// }
static String formatAsDayMonthYear(DateTime? date,
{ bool isArabic = false,}) {
final formatter = DateFormat('d MMM, y',
isArabic ? 'ar' : 'en',
);
return date != null ? formatter.format(date) : "N/A"; return date != null ? formatter.format(date) : "N/A";
} }

@ -178,6 +178,7 @@ enum AppointmentStatusEnum {
today, today,
past, past,
upcoming, upcoming,
served,
} }
enum AppointmentPaymentStatusEnum { enum AppointmentPaymentStatusEnum {
@ -191,6 +192,7 @@ enum AppointmentPaymentStatusEnum {
enum RequestsTypeEnum { enum RequestsTypeEnum {
specialCarRequest, specialCarRequest,
serviceRequest, serviceRequest,
other,
} }
enum ChatMessageTypeEnum { enum ChatMessageTypeEnum {

@ -36,8 +36,12 @@ class Utils {
toastification.show( toastification.show(
type: ToastificationType.info, type: ToastificationType.info,
style: ToastificationStyle.minimal, style: ToastificationStyle.minimal,
title: Text(message), title: Text(
autoCloseDuration: const Duration(seconds: 2), message,
maxLines: 5,
overflow: TextOverflow.ellipsis,
),
autoCloseDuration: const Duration(seconds: 3),
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
showProgressBar: false, showProgressBar: false,
); );
@ -305,56 +309,55 @@ class Utils {
static String getNameByRequestOfferStatusEnum(RequestOfferStatusEnum requestOfferStatusEnum) { static String getNameByRequestOfferStatusEnum(RequestOfferStatusEnum requestOfferStatusEnum) {
switch (requestOfferStatusEnum) { switch (requestOfferStatusEnum) {
case RequestOfferStatusEnum.offer: case RequestOfferStatusEnum.offer:
return "Offer"; return LocaleKeys.offer.tr();
case RequestOfferStatusEnum.negotiate: case RequestOfferStatusEnum.negotiate:
return "Negotiate"; return LocaleKeys.negotiate.tr();
case RequestOfferStatusEnum.accepted: case RequestOfferStatusEnum.accepted:
return "Accepted"; return LocaleKeys.accepted.tr();
case RequestOfferStatusEnum.rejected: case RequestOfferStatusEnum.rejected:
return "Rejected"; return LocaleKeys.rejected.tr();
case RequestOfferStatusEnum.cancel: case RequestOfferStatusEnum.cancel:
return "Cancelled"; return LocaleKeys.cancelled.tr();
} }
} }
static String getNameByShippingRequestStatusEnum(ShippingRequestStatusEnum shippingRequestStatusEnum) { static String getNameByShippingRequestStatusEnum(ShippingRequestStatusEnum shippingRequestStatusEnum) {
switch (shippingRequestStatusEnum) { switch (shippingRequestStatusEnum) {
case ShippingRequestStatusEnum.allRequests: case ShippingRequestStatusEnum.allRequests:
return "All Requests"; return LocaleKeys.allRequests.tr();
case ShippingRequestStatusEnum.initiated: case ShippingRequestStatusEnum.initiated:
return "Initiated"; return LocaleKeys.initiated.tr();
case ShippingRequestStatusEnum.inTransit: case ShippingRequestStatusEnum.inTransit:
return "In Transit"; return LocaleKeys.inTransit.tr();
case ShippingRequestStatusEnum.outForDelivery: case ShippingRequestStatusEnum.outForDelivery:
return "Out For Delivery"; return LocaleKeys.outForDelivery.tr();
case ShippingRequestStatusEnum.delivered: case ShippingRequestStatusEnum.delivered:
return "Delivered"; return LocaleKeys.delivered.tr();
case ShippingRequestStatusEnum.pending: case ShippingRequestStatusEnum.pending:
return "Pending"; return LocaleKeys.pending.tr();
} }
} }
static String getNameBySelfPickupRequestStatusEnum(SelfPickupRequestStatusEnum selfPickupRequestStatusEnum) { static String getNameBySelfPickupRequestStatusEnum(SelfPickupRequestStatusEnum selfPickupRequestStatusEnum) {
switch (selfPickupRequestStatusEnum) { switch (selfPickupRequestStatusEnum) {
case SelfPickupRequestStatusEnum.allRequests: case SelfPickupRequestStatusEnum.allRequests:
return "All Requests"; return LocaleKeys.allRequests.tr();
case SelfPickupRequestStatusEnum.pending: case SelfPickupRequestStatusEnum.pending:
return "Pending"; return LocaleKeys.pending.tr();
case SelfPickupRequestStatusEnum.preparingToCollect: case SelfPickupRequestStatusEnum.preparingToCollect:
return "Preparing To Collect"; return LocaleKeys.preparingToCollect.tr();
case SelfPickupRequestStatusEnum.readyToCollect: case SelfPickupRequestStatusEnum.readyToCollect:
return "Ready To Collect"; return LocaleKeys.readyToCollect.tr();
case SelfPickupRequestStatusEnum.collected: case SelfPickupRequestStatusEnum.collected:
return "Collected"; return LocaleKeys.collected.tr();
} }
} }
@ -424,23 +427,43 @@ class Utils {
static String getChipStatusTextByBranchStatus(BranchStatusEnum branchStatusEnum) { static String getChipStatusTextByBranchStatus(BranchStatusEnum branchStatusEnum) {
switch (branchStatusEnum) { switch (branchStatusEnum) {
case BranchStatusEnum.pending: case BranchStatusEnum.pending:
return "Pending Approval"; return LocaleKeys.pendingApproval.tr();
case BranchStatusEnum.review: case BranchStatusEnum.review:
return "In Review"; return LocaleKeys.inReview.tr();
case BranchStatusEnum.rejected: case BranchStatusEnum.rejected:
return "Rejected"; return LocaleKeys.rejected.tr();
case BranchStatusEnum.deactivated: case BranchStatusEnum.deactivated:
return "Deactivated"; return LocaleKeys.deactivated.tr();
case BranchStatusEnum.blocked: case BranchStatusEnum.blocked:
return "Blocked"; return LocaleKeys.blocked.tr();
case BranchStatusEnum.approvedOrActive: case BranchStatusEnum.approvedOrActive:
return "Active"; return LocaleKeys.active.tr();
} }
// static String getChipStatusTextByBranchStatus(BranchStatusEnum branchStatusEnum) {
// switch (branchStatusEnum) {
// case BranchStatusEnum.pending:
// return "Pending Approval";
//
// case BranchStatusEnum.review:
// return "In Review";
//
// case BranchStatusEnum.rejected:
// return "Rejected";
//
// case BranchStatusEnum.deactivated:
// return "Deactivated";
//
// case BranchStatusEnum.blocked:
// return "Blocked";
//
// case BranchStatusEnum.approvedOrActive:
// return "Active";
// }
} }
static statusContainerChip({ static statusContainerChip({

@ -27,12 +27,15 @@ import 'package:mc_common_app/models/general_models/widgets_models.dart';
import 'package:mc_common_app/repositories/ads_repo.dart'; import 'package:mc_common_app/repositories/ads_repo.dart';
import 'package:mc_common_app/repositories/common_repo.dart'; import 'package:mc_common_app/repositories/common_repo.dart';
import 'package:mc_common_app/services/common_services.dart'; import 'package:mc_common_app/services/common_services.dart';
import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/utils/date_helper.dart'; import 'package:mc_common_app/utils/date_helper.dart';
import 'package:mc_common_app/utils/dialogs_and_bottomsheets.dart';
import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/enums.dart';
import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/utils/navigator.dart';
import 'package:mc_common_app/utils/utils.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/base_view_model.dart';
import 'package:mc_common_app/view_models/chat_view_model.dart'; import 'package:mc_common_app/view_models/chat_view_model.dart';
import 'package:mc_common_app/widgets/button/show_fill_button.dart';
import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -86,6 +89,7 @@ class AdVM extends BaseVM {
AdDetailsModel? previousAdDetails; AdDetailsModel? previousAdDetails;
List<AdDetailsModel> myDraftAds = []; List<AdDetailsModel> myDraftAds = [];
List<AdDetailsModel> favouriteAds = [];
List<AdDetailsModel> exploreAds = []; List<AdDetailsModel> exploreAds = [];
List<AdDetailsModel> exploreAdsFilteredList = []; List<AdDetailsModel> exploreAdsFilteredList = [];
List<AdDetailsModel> myAdsFilteredList = []; List<AdDetailsModel> myAdsFilteredList = [];
@ -190,19 +194,20 @@ class AdVM extends BaseVM {
exploreAdsFilterOptions.clear(); exploreAdsFilterOptions.clear();
myAdsFilterOptions.clear(); myAdsFilterOptions.clear();
for (int i = 0; i < myAdsStatusEnums.length; i++) { for (int i = 0; i < myAdsStatusEnums.length; i++) {
myAdsFilterOptions.add(FilterListModel(title: myAdsStatusEnums[i].enumValueStr, isSelected: false, id: myAdsStatusEnums[i].enumValue)); myAdsFilterOptions.add(FilterListModel(title: myAdsStatusEnums[i].enumValueStr, titleN: myAdsStatusEnums[i].enumValueStrN, isSelected: false, id: myAdsStatusEnums[i].enumValue));
} }
myAdsFilterOptions.insert(0, FilterListModel(title: "All Ads", isSelected: true, id: 0)); myAdsFilterOptions.insert(0, FilterListModel(title: "All Ads", titleN: "جميع الاعلانات", isSelected: true, id: 0));
for (int i = 0; i < vehicleBrandsForFilters.length; i++) { for (int i = 0; i < vehicleBrandsForFilters.length; i++) {
exploreAdsFilterOptions.add(FilterListModel( exploreAdsFilterOptions.add(FilterListModel(
id: vehicleBrandsForFilters[i].id!, id: vehicleBrandsForFilters[i].id!,
title: "${vehicleBrandsForFilters[i].vehicleBrandDescription}", title: "${vehicleBrandsForFilters[i].vehicleBrandDescription}",
titleN: "${vehicleBrandsForFilters[i].vehicleBrandDescriptionN}",
iconUrl: vehicleBrandsForFilters[i].imageUrl ?? "", iconUrl: vehicleBrandsForFilters[i].imageUrl ?? "",
isSelected: false, isSelected: false,
)); ));
} }
exploreAdsFilterOptions.insert(0, FilterListModel(title: "All Ads", isSelected: true, id: 0)); exploreAdsFilterOptions.insert(0, FilterListModel(title: "All Ads", titleN: "جميع الاعلانات", isSelected: true, id: 0));
notifyListeners(); notifyListeners();
} }
@ -334,6 +339,25 @@ class AdVM extends BaseVM {
return true; return true;
} }
Future<bool> toggleAdFavorite({required int adsID, required bool isFavorite}) async {
try {
GenericRespModel genericRespModel = await adsRepo.toggleAdFavorite(adsID: adsID, isFavorite: isFavorite);
if (genericRespModel.messageStatus == 1) {
if (genericRespModel.message != null && genericRespModel.message!.isNotEmpty) {
Utils.showToast(genericRespModel.message!);
}
return true;
} else {
Utils.showToast(genericRespModel.message ?? LocaleKeys.somethingWrong.tr());
return false;
}
} catch (e) {
logger.e(e.toString());
Utils.showToast(LocaleKeys.somethingWrong.tr());
return false;
}
}
Future<void> getExploreAds() async { Future<void> getExploreAds() async {
pageIndexForExploreAds = 1; pageIndexForExploreAds = 1;
hasMoreDataForExploreAds = true; hasMoreDataForExploreAds = true;
@ -361,6 +385,18 @@ class AdVM extends BaseVM {
} }
} }
Future<void> getFavouriteAds() async {
try {
setState(ViewState.busy);
favouriteAds = await adsRepo.getFavouriteAds();
notifyListeners();
setState(ViewState.idle);
} catch (e) {
setState(ViewState.idle);
logger.e(e.toString());
}
}
Future<void> fetchMoreDraftAds() async { Future<void> fetchMoreDraftAds() async {
if (isLoadingMore) return; if (isLoadingMore) return;
hasMoreDataForMyDraftsAds = true; hasMoreDataForMyDraftsAds = true;
@ -736,7 +772,7 @@ class AdVM extends BaseVM {
notifyListeners(); notifyListeners();
} }
int isInUsed = 0; int isInUsed = 0; // 0 = No, 1 = Yes
void updateIsInUsed(int value) { void updateIsInUsed(int value) {
isInUsed = value; isInUsed = value;
@ -1243,26 +1279,153 @@ class AdVM extends BaseVM {
void onBackButtonPressed(BuildContext context) { void onBackButtonPressed(BuildContext context) {
switch (currentProgressStep) { switch (currentProgressStep) {
case AdCreationStepsEnum.vehicleDetails: case AdCreationStepsEnum.vehicleDetails:
isAdEditEnabled = false; // Check if user has entered any data
isDraftEditEnabled = false; bool hasData = vehicleTypeId.selectedId != -1 ||
resetValues(); vehicleBrandId.selectedId != -1 ||
pop(context); vehicleModelId.selectedId != -1 ||
vehicleModelYearId.selectedId != -1 ||
vehicleColorId.selectedId != -1 ||
vehicleCategoryId.selectedId != -1 ||
vehicleConditionId.selectedId != -1 ||
vehicleMileageId.selectedId != -1 ||
vehicleTransmissionId.selectedId != -1 ||
vehicleCountryId.selectedId != -1 ||
vehicleCityId.selectedId != -1 ||
vehicleDemandAmount.isNotEmpty ||
vehicleVin.isNotEmpty ||
vehicleTitle.isNotEmpty ||
warrantyDuration.isNotEmpty ||
vehicleDescription.isNotEmpty ||
pickedPostingImages.isNotEmpty;
if (hasData) {
// Show confirmation dialog if user has entered data
showSaveAsDraftConfirmationDialog(
context,
onYes: () async {
Navigator.pop(context);
await saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.vehicleDetails, bypassValidation: true);
currentProgressStep = AdCreationStepsEnum.vehicleDetails;
isAdEditEnabled = false;
isDraftEditEnabled = false;
resetValues();
pop(context);
if (AppState().currentAppType == AppType.customer) {
pop(context);
}
},
onNo: () {
Navigator.pop(context);
isAdEditEnabled = false;
isDraftEditEnabled = false;
resetValues();
pop(context);
},
);
} else {
// No data entered, go back directly
isAdEditEnabled = false;
isDraftEditEnabled = false;
resetValues();
pop(context);
}
break; break;
case AdCreationStepsEnum.damageParts: case AdCreationStepsEnum.damageParts:
currentProgressStep = AdCreationStepsEnum.vehicleDetails; showSaveAsDraftConfirmationDialog(
notifyListeners(); context,
onYes: () async {
Navigator.pop(context);
await saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.damageParts, bypassValidation: true);
currentProgressStep = AdCreationStepsEnum.vehicleDetails;
isAdEditEnabled = false;
isDraftEditEnabled = false;
resetValues();
pop(context);
if (AppState().currentAppType == AppType.customer) {
pop(context);
}
},
onNo: () {
Navigator.pop(context);
currentProgressStep = AdCreationStepsEnum.vehicleDetails;
notifyListeners();
},
);
break; break;
case AdCreationStepsEnum.adDuration: case AdCreationStepsEnum.adDuration:
currentProgressStep = AdCreationStepsEnum.damageParts; showSaveAsDraftConfirmationDialog(
notifyListeners(); context,
onYes: () async {
Navigator.pop(context);
await saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.adDuration, bypassValidation: true);
currentProgressStep = AdCreationStepsEnum.vehicleDetails;
isAdEditEnabled = false;
isDraftEditEnabled = false;
resetValues();
pop(context);
if (AppState().currentAppType == AppType.customer) {
pop(context);
}
},
onNo: () {
Navigator.pop(context);
currentProgressStep = AdCreationStepsEnum.damageParts;
notifyListeners();
},
);
break; break;
case AdCreationStepsEnum.reviewAd: case AdCreationStepsEnum.reviewAd:
currentProgressStep = AdCreationStepsEnum.adDuration; showSaveAsDraftConfirmationDialog(
notifyListeners(); context,
onYes: () async {
Navigator.pop(context);
await saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.reviewAd, bypassValidation: true);
currentProgressStep = AdCreationStepsEnum.vehicleDetails;
isAdEditEnabled = false;
isDraftEditEnabled = false;
resetValues();
pop(context);
if (AppState().currentAppType == AppType.customer) {
pop(context);
}
},
onNo: () {
Navigator.pop(context);
currentProgressStep = AdCreationStepsEnum.adDuration;
notifyListeners();
},
);
break; break;
} }
} }
void showSaveAsDraftConfirmationDialog(BuildContext context, {required VoidCallback onYes, required VoidCallback onNo}) {
actionConfirmationBottomSheet(
context: context,
title: "Save As Draft".toText(fontSize: 28, isBold: true, letterSpacing: -1.44),
subtitle: "Your ad will be saved as draft and you can continue later from where you left off. Do you want to save and go back?",
actionButtonYes: Expanded(
child: ShowFillButton(
maxHeight: 55,
title: LocaleKeys.yes.tr(),
fontSize: 15,
onPressed: onYes,
),
),
actionButtonNo: Expanded(
child: ShowFillButton(
maxHeight: 55,
isFilled: false,
borderColor: MyColors.darkPrimaryColor,
title: LocaleKeys.no.tr(),
txtColor: MyColors.darkPrimaryColor,
fontSize: 15,
onPressed: onNo,
),
),
);
}
Future<int> saveAdVehicleDetailsDraft({required bool isNew, required BuildContext context, required AdCreationStepsEnum stepNoEnum}) async { Future<int> saveAdVehicleDetailsDraft({required bool isNew, required BuildContext context, required AdCreationStepsEnum stepNoEnum}) async {
AppState appState = injector.get<AppState>(); AppState appState = injector.get<AppState>();
@ -1354,6 +1517,99 @@ class AdVM extends BaseVM {
} }
} }
Future<void> saveAdAsDraft({required BuildContext context, required AdCreationStepsEnum stepNoEnum, bool bypassValidation = false}) async {
AppState appState = injector.get<AppState>();
List<int> adsSelectedServices = [];
for (var value in specialServiceCards) {
adsSelectedServices.add(value.serviceSelectedId!.selectedId);
}
Utils.showLoading(context);
try {
Ads ads = Ads(
id: (previousAdDetails != null && previousAdDetails!.id != null) ? previousAdDetails!.id : 0,
adsDurationID: vehicleAdDurationId.selectedId == -1 ? 0 : vehicleAdDurationId.selectedId,
startDate: selectionDurationStartDate.isNotEmpty ? selectionDurationStartDate : null,
countryId: vehicleCountryId.selectedId != -1 ? vehicleCountryId.selectedId : null,
specialServiceIDs: adsSelectedServices,
showContactDetail: isPhoneNumberShown,
isOnWhatsApp: isNumberOnWhatsApp,
);
List<VehiclePostingImages> vehicleImages = [];
for (var image in pickedPostingImages) {
vehicleImages.add(await convertFileToVehiclePostingImages(imageModel: image));
}
List<VehiclePostingDamageParts> vehicleDamageImages = [];
for (var card in vehicleDamageCards) {
if (card.partImages != null && card.partImages!.isNotEmpty) {
for (var image in card.partImages!) {
VehiclePostingDamageParts stringImage = await convertFileToVehiclePostingDamageParts(
imageModel: image,
damagePartId: card.partSelectedId!.selectedId,
commentParam: card.damagePartDescription ?? "",
);
vehicleDamageImages.add(stringImage);
}
}
}
VehiclePosting vehiclePosting = VehiclePosting(
id: (previousAdDetails != null && previousAdDetails!.vehiclePostingID != null) ? previousAdDetails!.vehiclePostingID : null,
userID: appState.getUser.data!.userInfo!.userId,
vehicleType: vehicleTypeId.selectedId != -1 ? vehicleTypeId.selectedId : null,
vehicleModelID: vehicleModelId.selectedId != -1 ? vehicleModelId.selectedId : null,
vehicleModelYearID: vehicleModelYearId.selectedId != -1 ? vehicleModelYearId.selectedId : null,
vehicleColorID: vehicleColorId.selectedId != -1 ? vehicleColorId.selectedId : null,
vehicleCategoryID: vehicleCategoryId.selectedId != -1 ? vehicleCategoryId.selectedId : null,
vehicleConditionID: vehicleConditionId.selectedId != -1 ? vehicleConditionId.selectedId : null,
vehicleMileageID: vehicleMileageId.selectedId != -1 ? vehicleMileageId.selectedId : null,
vehicleTransmissionID: vehicleTransmissionId.selectedId != -1 ? vehicleTransmissionId.selectedId : null,
vehicleSellerTypeID: vehicleSellerTypeId.selectedId == -1 ? 1 : vehicleSellerTypeId.selectedId,
cityID: vehicleCityId.selectedId != -1 ? vehicleCityId.selectedId : null,
price: vehicleDemandAmount.isNotEmpty ? int.tryParse(vehicleDemandAmount) : null,
vehicleVIN: vehicleVin.isNotEmpty ? vehicleVin : null,
vehicleDescription: vehicleDescription.isNotEmpty ? vehicleDescription : null,
vehicleTitle: vehicleTitle.isNotEmpty ? vehicleTitle : null,
vehicleDescriptionN: vehicleDescription.isNotEmpty ? vehicleDescription : null,
isFinanceAvailable: financeAvailableStatus,
warantyYears: warrantyDuration.isNotEmpty ? int.tryParse(warrantyDuration) : null,
demandAmount: vehicleDemandAmount.isNotEmpty ? int.tryParse(vehicleDemandAmount) : null,
vehiclePostingImages: vehicleImages,
vehiclePostingDamageParts: vehicleDamageImages,
phoneNo: (isPhoneNumberShown && adPhoneNumber.isNotEmpty) ? adPhoneNumberDialCode + adPhoneNumber : null,
whatsAppNo: (isPhoneNumberShown && isNumberOnWhatsApp && adPhoneNumber.isNotEmpty) ? adPhoneNumberDialCode + adPhoneNumber : null,
odometer: odometer.isNotEmpty ? int.tryParse(odometer) : 0,
isInUsed: isInUsed,
isAccidentFree: isAccidentFree,
);
AdsCreationPayloadModel adsCreationPayloadModel = AdsCreationPayloadModel(ads: ads, vehiclePosting: vehiclePosting);
GenericRespModel respModel = await adsRepo.createOrUpdateDraftAd(
adsCreationPayloadModel: adsCreationPayloadModel,
isCreateNew: previousAdDetails == null || previousAdDetails!.id == null,
stepNo: stepNoEnum,
);
Utils.hideLoading(context);
if (respModel.messageStatus == 1) {
Utils.showToast('Saved as Draft');
getMyDraftAds();
} else {
Utils.showToast(respModel.message ?? "Failed to save draft");
}
} catch (e) {
Utils.hideLoading(context);
logger.e(e.toString());
Utils.showToast("Error saving draft: ${e.toString()}");
}
}
Future saveAdDamagePartDetailsDraft() async {} Future saveAdDamagePartDetailsDraft() async {}
Future saveAdDurationsDraft() async {} Future saveAdDurationsDraft() async {}
@ -2379,7 +2635,7 @@ class AdVM extends BaseVM {
autoFillSelectedVehicleType(); autoFillSelectedVehicleType();
autoFillSelectedVehicleAdsDuration(); autoFillSelectedVehicleAdsDuration();
if (isForDraft) { if (isForDraft) {
autoFillSelectedVehicleAdsDetails(); autoFillSelectedVehicleAdsDetails(context);
} }
navigateWithName(context, AppRoutes.selectAdTypeView, arguments: [AppState().currentAppType == AppType.provider, isFromExtendAd, previousDetails.id]); navigateWithName(context, AppRoutes.selectAdTypeView, arguments: [AppState().currentAppType == AppType.provider, isFromExtendAd, previousDetails.id]);
} }
@ -2456,7 +2712,7 @@ class AdVM extends BaseVM {
notifyListeners(); notifyListeners();
} }
void autoFillSelectedVehicleAdsDetails() async { void autoFillSelectedVehicleAdsDetails(BuildContext context) async {
if (vehicleBrands.isEmpty) { if (vehicleBrands.isEmpty) {
await getVehicleBrandsByVehicleTypeId(); await getVehicleBrandsByVehicleTypeId();
} }
@ -2465,18 +2721,18 @@ class AdVM extends BaseVM {
if (index != -1) { if (index != -1) {
await updateSelectionVehicleBrandId(SelectionModel(selectedId: vehicleBrands[index].id!, selectedOption: vehicleBrands[index].vehicleBrandDescription ?? "")); await updateSelectionVehicleBrandId(SelectionModel(selectedId: vehicleBrands[index].id!, selectedOption: vehicleBrands[index].vehicleBrandDescription ?? ""));
} }
bool isArabic = AppState().isArabic(context);
updateSelectionVehicleModelId(SelectionModel(selectedId: previousAdDetails!.vehicle!.model!.id!, selectedOption: previousAdDetails!.vehicle!.model!.label ?? "")); updateSelectionVehicleModelId(SelectionModel(selectedId: previousAdDetails!.vehicle!.model!.id!, selectedOption: previousAdDetails!.vehicle!.model!.label ?? ""));
updateSelectionVehicleModelYearId(SelectionModel(selectedId: previousAdDetails!.vehicle!.modelyear!.id!, selectedOption: previousAdDetails!.vehicle!.modelyear!.label ?? "")); updateSelectionVehicleModelYearId(SelectionModel(selectedId: previousAdDetails!.vehicle!.modelyear!.id!, selectedOption: previousAdDetails!.vehicle!.modelyear!.label ?? ""));
updateSelectionVehicleColorId(SelectionModel(selectedId: previousAdDetails!.vehicle!.color!.id!, selectedOption: previousAdDetails!.vehicle!.color!.label ?? "")); updateSelectionVehicleColorId(SelectionModel(selectedId: previousAdDetails!.vehicle!.color!.id!, selectedOption: isArabic ? previousAdDetails!.vehicle!.color!.labelN.toString() : previousAdDetails!.vehicle!.color!.label ?? ""));
updateSelectionVehicleConditionId(SelectionModel(selectedId: previousAdDetails!.vehicle!.condition!.id!, selectedOption: previousAdDetails!.vehicle!.condition!.label ?? "")); updateSelectionVehicleConditionId(SelectionModel(selectedId: previousAdDetails!.vehicle!.condition!.id!, selectedOption: isArabic ? previousAdDetails!.vehicle!.condition!.labelN.toString() :previousAdDetails!.vehicle!.condition!.label ?? ""));
updateSelectionVehicleCategoryId(SelectionModel(selectedId: previousAdDetails!.vehicle!.category!.id!, selectedOption: previousAdDetails!.vehicle!.category!.label ?? "")); updateSelectionVehicleCategoryId(SelectionModel(selectedId: previousAdDetails!.vehicle!.category!.id!, selectedOption: isArabic ? previousAdDetails!.vehicle!.category!.labelN.toString() :previousAdDetails!.vehicle!.category!.label ?? ""));
updateSelectionVehicleMileageId(SelectionModel( updateSelectionVehicleMileageId(SelectionModel(
selectedId: previousAdDetails!.vehicle!.mileage!.id!, selectedId: previousAdDetails!.vehicle!.mileage!.id!,
selectedOption: "${previousAdDetails!.vehicle!.mileage!.mileageStart} - ${previousAdDetails!.vehicle!.mileage!.mileageEnd}", selectedOption: "${previousAdDetails!.vehicle!.mileage!.mileageStart} - ${previousAdDetails!.vehicle!.mileage!.mileageEnd}",
)); ));
updateSelectionVehicleTransmissionId(SelectionModel(selectedId: previousAdDetails!.vehicle!.transmission!.id!, selectedOption: previousAdDetails!.vehicle!.transmission!.label ?? "")); updateSelectionVehicleTransmissionId(SelectionModel(selectedId: previousAdDetails!.vehicle!.transmission!.id!, selectedOption:isArabic ? previousAdDetails!.vehicle!.transmission!.labelN.toString() : previousAdDetails!.vehicle!.transmission!.label ?? ""));
updateSelectionVehicleSellerTypeId(SelectionModel(selectedId: previousAdDetails!.vehicle!.sellertype!.id!, selectedOption: previousAdDetails!.vehicle!.sellertype!.label ?? "")); updateSelectionVehicleSellerTypeId(SelectionModel(selectedId: previousAdDetails!.vehicle!.sellertype!.id!, selectedOption: isArabic ? previousAdDetails!.vehicle!.sellertype!.labelN.toString() :previousAdDetails!.vehicle!.sellertype!.label ?? ""));
int indexCountry = vehicleCountries.indexWhere((element) => element.id == previousAdDetails!.vehicle!.countryID); int indexCountry = vehicleCountries.indexWhere((element) => element.id == previousAdDetails!.vehicle!.countryID);
if (indexCountry != -1) { if (indexCountry != -1) {
updateSelectionVehicleCountryId(SelectionModel(selectedId: vehicleCountries[indexCountry].id!, selectedOption: vehicleCountries[indexCountry].countryName ?? "")); updateSelectionVehicleCountryId(SelectionModel(selectedId: vehicleCountries[indexCountry].id!, selectedOption: vehicleCountries[indexCountry].countryName ?? ""));

File diff suppressed because it is too large Load Diff

@ -33,6 +33,7 @@ class ChatVM extends BaseVM {
ChatVM({required this.chatRepo, required this.requestRepo, required this.commonServices}); ChatVM({required this.chatRepo, required this.requestRepo, required this.commonServices});
HubConnection? hubConnection; HubConnection? hubConnection;
bool _isSubscribed = false; // Track if listeners are subscribed
String chatMessageText = ""; String chatMessageText = "";
@ -281,30 +282,42 @@ class ChatVM extends BaseVM {
} }
int _retryCount = 0; int _retryCount = 0;
final int _maxRetries = 5; // Maximum number of retries final int _maxRetries = 5; // Maximum number of consecutive retries before increasing delay
bool _isReconnecting = false; bool _isReconnecting = false;
Future<void> _reconnectWithBackoff(BuildContext context) async { Future<void> _reconnectWithBackoff() async {
if (_retryCount >= _maxRetries) { // Never stop trying, but increase delays after max retries
logger.e("Maximum retry attempts reached. Stopping reconnection."); int delaySeconds;
return; if (_retryCount < _maxRetries) {
delaySeconds = 2 * (_retryCount + 1); // 2, 4, 6, 8, 10 seconds
} else {
delaySeconds = 30; // After 5 failures, retry every 30 seconds indefinitely
} }
final delay = Duration(seconds: 2 * (_retryCount + 1)); // Exponential backoff logger.i("Retrying connection in $delaySeconds seconds (attempt ${_retryCount + 1})...");
logger.i("Retrying connection in ${delay.inSeconds} seconds..."); await Future.delayed(Duration(seconds: delaySeconds));
await Future.delayed(delay);
_retryCount++; _retryCount++;
await buildHubConnection(context); // Try reconnecting await buildHubConnection(); // Try reconnecting (no context needed)
} }
Future<void> buildHubConnection(BuildContext context) async { // Clear all existing listeners to prevent duplicates
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { void _clearAllListeners() {
if (_isReconnecting) { if (hubConnection != null && _isSubscribed) {
logger.i("Reconnection already in progress"); try {
return; hubConnection!.off(SignalrConsts.receiveMessageRequestOffer);
hubConnection!.off(SignalrConsts.receiveMessageAds);
hubConnection!.off(SignalrConsts.receiveMessageGeneral);
_isSubscribed = false;
logger.i("Cleared all SignalR listeners");
} catch (e) {
logger.e("Error clearing listeners: ${e.toString()}");
} }
}
}
Future<void> buildHubConnection({BuildContext? context}) async {
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
if (_isReconnecting) { if (_isReconnecting) {
logger.i("Reconnection already in progress"); logger.i("Reconnection already in progress");
return; return;
@ -313,34 +326,58 @@ class ChatVM extends BaseVM {
try { try {
_isReconnecting = true; _isReconnecting = true;
// Create new hub connection
hubConnection = await chatRepo.getHubConnection(); hubConnection = await chatRepo.getHubConnection();
await hubConnection!.start();
// Start with timeout
await hubConnection!.start()?.timeout(
const Duration(seconds: 30),
onTimeout: () {
throw Exception("Hub connection timeout");
},
);
// Reset retry count on successful connection // Reset retry count on successful connection
_retryCount = 0; _retryCount = 0;
_isReconnecting = false; _isReconnecting = false;
// Subscribe to messages logger.i("Hub Connected Successfully: ${hubConnection!.connectionId}");
_subscribeToReceiveRequestOfferMessages(context);
_subscribeToReceiveAdMessages(context); // Clear old listeners before subscribing (prevents duplicates)
_subscribeToReceiveGeneralMessages(context); _clearAllListeners();
// Subscribe to messages (only if context is available for first-time connection)
// For reconnections, we'll re-subscribe without context
if (context != null) {
_subscribeToReceiveRequestOfferMessages(context);
_subscribeToReceiveAdMessages(context);
_subscribeToReceiveGeneralMessages(context);
_isSubscribed = true;
}
// Configure handlers // Configure handlers
hubConnection!.onclose((exception) async { hubConnection!.onclose((exception) async {
logger.i("onClose: ${exception.toString()}"); logger.i("onClose: ${exception.toString()}");
await _reconnectWithBackoff(context); _isSubscribed = false;
await _reconnectWithBackoff(); // No context needed
}); });
hubConnection!.onreconnecting((exception) { hubConnection!.onreconnecting((exception) {
logger.i("onReconnecting: ${exception?.toString()}"); logger.i("onReconnecting: ${exception?.toString()}");
_isSubscribed = false;
}); });
hubConnection!.onreconnected((connectionId) { hubConnection!.onreconnected((connectionId) {
logger.i("onReconnected: ${connectionId.toString()}"); logger.i("onReconnected: ${connectionId.toString()}");
// Re-subscribe after automatic reconnect (SignalR's own reconnect, not ours)
// This is safe because we clear listeners in onreconnecting
_retryCount = 0; // Reset on successful reconnect
}); });
} catch (e) { } catch (e) {
logger.e("Error during hub connection setup: ${e.toString()}"); logger.e("Error during hub connection setup: ${e.toString()}");
_isReconnecting = false; // Reset on failure _isReconnecting = false;
// Retry on failure
await _reconnectWithBackoff();
} }
notifyListeners(); notifyListeners();
@ -350,8 +387,18 @@ class ChatVM extends BaseVM {
} }
Future<void> closeHubConnection() async { Future<void> closeHubConnection() async {
if (hubConnection != null && hubConnection!.state == HubConnectionState.connected) { try {
hubConnection!.stop(); _clearAllListeners();
if (hubConnection != null && hubConnection!.state == HubConnectionState.connected) {
await hubConnection!.stop();
logger.i("Hub connection closed");
}
hubConnection = null;
_isSubscribed = false;
_retryCount = 0;
_isReconnecting = false;
} catch (e) {
logger.e("Error closing hub connection: ${e.toString()}");
} }
} }
@ -369,9 +416,19 @@ class ChatVM extends BaseVM {
required List<dynamic> offerImages, required List<dynamic> offerImages,
required BuildContext context, required BuildContext context,
}) async { }) async {
// Ensure connection is established
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
logger.i("Hub Not Connected!!"); logger.i("Hub Not Connected!! Attempting to connect...");
await buildHubConnection(context); await buildHubConnection(context: context);
// Wait a bit for connection to establish
await Future.delayed(const Duration(milliseconds: 500));
// Check again after attempt
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
Utils.showToast("Failed to connect. Please try again.");
return false;
}
} }
if (hubConnection!.state == HubConnectionState.connected) { if (hubConnection!.state == HubConnectionState.connected) {
@ -398,17 +455,20 @@ class ChatVM extends BaseVM {
}; };
logger.i(obj); logger.i(obj);
hubConnection!.invoke( try {
SignalrConsts.sendMessageRequestOffer, await hubConnection!.invoke(
args: <Object>[obj], SignalrConsts.sendMessageRequestOffer,
).catchError((e) { args: <Object>[obj],
logger.i("error in invoking SendMessageRequestOffer: ${e.toString()}"); );
Utils.showToast(e.toString()); return true;
} catch (e) {
logger.e("error in invoking SendMessageRequestOffer: ${e.toString()}");
Utils.showToast("Failed to send offer");
return false; return false;
}); }
} }
return true; return false;
} }
MessageImageModel convertFileToMessageImageModel({required File file}) { MessageImageModel convertFileToMessageImageModel({required File file}) {
@ -467,10 +527,21 @@ class ChatVM extends BaseVM {
}) async { }) async {
if (chatMessageType == ChatMessageTypeEnum.freeText && message.isEmpty) return false; if (chatMessageType == ChatMessageTypeEnum.freeText && message.isEmpty) return false;
// Ensure connection is established
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
logger.i("Hub Not Connected!!"); logger.i("Hub Not Connected!! Attempting to connect...");
await buildHubConnection(context); await buildHubConnection(context: context);
// Wait a bit for connection to establish
await Future.delayed(const Duration(milliseconds: 500));
// Check again after attempt
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
Utils.showToast("Failed to connect. Please try again.");
return false;
}
} }
if (hubConnection!.state == HubConnectionState.connected) { if (hubConnection!.state == HubConnectionState.connected) {
final userId = AppState().getUser.data!.userInfo!.userId.toString(); final userId = AppState().getUser.data!.userInfo!.userId.toString();
final name = AppState().getUser.data!.userInfo!.firstName.toString(); final name = AppState().getUser.data!.userInfo!.firstName.toString();
@ -495,40 +566,42 @@ class ChatVM extends BaseVM {
} }
logger.i(obj); logger.i(obj);
hubConnection!.invoke( try {
SignalrConsts.sendMessageRequestOffer, await hubConnection!.invoke(
args: <Object>[obj], SignalrConsts.sendMessageRequestOffer,
).catchError((e) { args: <Object>[obj],
logger.i("error in invoking SendMessage: ${e.toString()}"); );
Utils.showToast(e.toString());
return false; ChatMessageModel chatMessageModel = ChatMessageModel(
}); messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
chatText: message,
ChatMessageModel chatMessageModel = ChatMessageModel( isMyMessage: true,
messageType: chatMessageType.getIdFromChatMessageTypeEnum(), requestID: requestId,
chatText: message, senderName: name,
isMyMessage: true, senderUserID: userId,
requestID: requestId, chatMessageTypeEnum: chatMessageType,
senderName: name, receiverUserID: receiverId,
senderUserID: userId, messageImages: messageImages,
chatMessageTypeEnum: chatMessageType, );
receiverUserID: receiverId,
messageImages: messageImages, if (AppState().currentAppType == AppType.customer) {
); int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == receiverId);
if (providerIndex != -1) {
if (AppState().currentAppType == AppType.customer) { serviceProviderOffersList[providerIndex].chatMessages!.add(chatMessageModel);
int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == receiverId); }
if (providerIndex != -1) { } else {
serviceProviderOffersList[providerIndex].chatMessages!.add(chatMessageModel); int providerIndex = context.read<RequestsVM>().myFilteredRequests.indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId));
} log("providerIndex2:$providerIndex");
} else { if (providerIndex != -1) {
int providerIndex = context.read<RequestsVM>().myFilteredRequests.indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId)); context.read<RequestsVM>().addChatMessagesInRequestsModel(msg: chatMessageModel, index: providerIndex);
log("providerIndex2:$providerIndex"); }
if (providerIndex != -1) {
context.read<RequestsVM>().addChatMessagesInRequestsModel(msg: chatMessageModel, index: providerIndex);
} }
return true;
} catch (e) {
logger.e("error in invoking SendMessage: ${e.toString()}");
Utils.showToast("Failed to send message");
return false;
} }
return true;
} }
return false; return false;
} }
@ -664,7 +737,7 @@ class ChatVM extends BaseVM {
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
logger.i("Hub Not Connected!!"); logger.i("Hub Not Connected!!");
await buildHubConnection(context); await buildHubConnection(context: context);
} }
if (hubConnection!.state == HubConnectionState.connected) { if (hubConnection!.state == HubConnectionState.connected) {
var obj = <String, dynamic>{ var obj = <String, dynamic>{
@ -764,9 +837,19 @@ class ChatVM extends BaseVM {
}) async { }) async {
if (message.isEmpty) return false; if (message.isEmpty) return false;
// Ensure connection is established
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
logger.i("Hub Not Connected!!"); logger.i("Hub Not Connected!! Attempting to connect...");
await buildHubConnection(context); await buildHubConnection(context: context);
// Wait a bit for connection to establish
await Future.delayed(const Duration(milliseconds: 500));
// Check again after attempt
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
Utils.showToast("Failed to connect. Please try again.");
return false;
}
} }
if (hubConnection!.state == HubConnectionState.connected) { if (hubConnection!.state == HubConnectionState.connected) {
@ -781,32 +864,34 @@ class ChatVM extends BaseVM {
}; };
logger.i("$obj"); logger.i("$obj");
hubConnection!.invoke( try {
SignalrConsts.sendMessageAds, await hubConnection!.invoke(
args: <Object>[obj], SignalrConsts.sendMessageAds,
).catchError((e) { args: <Object>[obj],
logger.i("error in invoking SendMessage: ${e.toString()}"); );
Utils.showToast(e.toString());
ChatMessageModel chatMessageModel = ChatMessageModel(
messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
chatText: message,
isMyMessage: true,
senderName: name,
senderUserID: userId,
chatMessageTypeEnum: ChatMessageTypeEnum.freeText,
receiverUserID: receiverId,
);
currentMessagesForAds.add(chatMessageModel);
notifyListeners();
if (AppState().currentAppType == AppType.customer) {
} else {}
return true;
} catch (e) {
logger.e("error in invoking SendMessage: ${e.toString()}");
Utils.showToast("Failed to send message");
return false; return false;
}); }
ChatMessageModel chatMessageModel = ChatMessageModel(
messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
chatText: message,
isMyMessage: true,
senderName: name,
senderUserID: userId,
chatMessageTypeEnum: ChatMessageTypeEnum.freeText,
receiverUserID: receiverId,
);
currentMessagesForAds.add(chatMessageModel);
notifyListeners();
if (AppState().currentAppType == AppType.customer) {
} else {}
return true;
} }
return false; return false;
} }
@ -843,7 +928,7 @@ class ChatVM extends BaseVM {
Future<void> onNewMessageReceivedForGeneral({required List<ChatMessageModel> messages}) async { Future<void> onNewMessageReceivedForGeneral({required List<ChatMessageModel> messages}) async {
for (var msg in messages) { for (var msg in messages) {
currentMessagesForAds.add(msg); currentMessagesForGeneralChat.add(msg); // FIXED: Use correct list
} }
scrollChatDown(); scrollChatDown();
notifyListeners(); notifyListeners();
@ -872,9 +957,19 @@ class ChatVM extends BaseVM {
}) async { }) async {
if (message.isEmpty) return false; if (message.isEmpty) return false;
// Ensure connection is established
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
logger.i("Hub Not Connected!!"); logger.i("Hub Not Connected!! Attempting to connect...");
await buildHubConnection(context); await buildHubConnection(context: context);
// Wait a bit for connection to establish
await Future.delayed(const Duration(milliseconds: 500));
// Check again after attempt
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
Utils.showToast("Failed to connect. Please try again.");
return false;
}
} }
if (hubConnection!.state == HubConnectionState.connected) { if (hubConnection!.state == HubConnectionState.connected) {
@ -887,31 +982,33 @@ class ChatVM extends BaseVM {
}; };
logger.i("$obj"); logger.i("$obj");
hubConnection!.invoke( try {
SignalrConsts.sendMessageGeneral, await hubConnection!.invoke(
args: <Object>[obj], SignalrConsts.sendMessageGeneral,
).catchError((e) { args: <Object>[obj],
logger.i("error in invoking SendMessage: ${e.toString()}"); );
Utils.showToast(e.toString());
ChatMessageModel chatMessageModel = ChatMessageModel(
messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
chatText: message,
isMyMessage: true,
senderName: name,
senderUserID: userId,
chatMessageTypeEnum: ChatMessageTypeEnum.freeText,
receiverUserID: receiverId,
);
currentMessagesForGeneralChat.add(chatMessageModel);
notifyListeners();
if (AppState().currentAppType == AppType.customer) {
} else {}
return true;
} catch (e) {
logger.e("error in invoking SendMessage: ${e.toString()}");
Utils.showToast("Failed to send message");
return false; return false;
}); }
ChatMessageModel chatMessageModel = ChatMessageModel(
messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
chatText: message,
isMyMessage: true,
senderName: name,
senderUserID: userId,
chatMessageTypeEnum: ChatMessageTypeEnum.freeText,
receiverUserID: receiverId,
);
currentMessagesForGeneralChat.add(chatMessageModel);
notifyListeners();
if (AppState().currentAppType == AppType.customer) {
} else {}
return true;
} }
return false; return false;
} }

@ -1,5 +1,6 @@
import 'dart:developer'; import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:flutter/widgets.dart';
import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/app_state.dart';
import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart';
import 'package:mc_common_app/main.dart'; import 'package:mc_common_app/main.dart';
@ -11,17 +12,59 @@ 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/ad_view_model.dart';
import 'package:mc_common_app/view_models/appointments_view_model.dart'; import 'package:mc_common_app/view_models/appointments_view_model.dart';
import 'package:mc_common_app/view_models/base_view_model.dart'; import 'package:mc_common_app/view_models/base_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:mc_common_app/view_models/chat_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/view_models/requests_view_model.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class DashboardVmCustomer extends BaseVM { class DashboardVmCustomer extends BaseVM with WidgetsBindingObserver {
final CommonAppServices commonServices; final CommonAppServices commonServices;
final UserRepo userRepo; final UserRepo userRepo;
BuildContext? _context; // Store context for lifecycle events
DashboardVmCustomer({required this.commonServices, required this.userRepo}); DashboardVmCustomer({required this.commonServices, required this.userRepo}) {
// Register as lifecycle observer
WidgetsBinding.instance.addObserver(this);
}
// Store context when dashboard is initialized
void setContext(BuildContext context) {
_context = context;
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_context = null;
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
logger.i("App lifecycle state changed to: $state");
if (_context == null) return;
final chatVM = Provider.of<ChatVM>(_context!, listen: false);
switch (state) {
case AppLifecycleState.resumed:
// App came back to foreground - ensure SignalR is connected
logger.i("App resumed - checking SignalR connection");
chatVM.buildHubConnection(context: _context);
break;
case AppLifecycleState.paused:
// App went to background - keep connection alive (as per user requirement)
logger.i("App paused - SignalR staying connected");
break;
case AppLifecycleState.inactive:
case AppLifecycleState.detached:
case AppLifecycleState.hidden:
// Other states - no action needed
break;
}
}
String pickedImage = ""; String pickedImage = "";
@ -81,7 +124,7 @@ class DashboardVmCustomer extends BaseVM {
await requestsVM.getRequests(); await requestsVM.getRequests();
await adVM.getVehicleTypes(); await adVM.getVehicleTypes();
await adVM.getVehicleAdsDuration(); await adVM.getVehicleAdsDuration();
await chatVM.buildHubConnection(context); await chatVM.buildHubConnection(context: context);
// if (appointmentsVM.myAppointments.isEmpty) { // if (appointmentsVM.myAppointments.isEmpty) {
// await appointmentsVM.getMyAppointments(); // await appointmentsVM.getMyAppointments();

@ -1,7 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/widgets.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/app_state.dart';
import 'package:mc_common_app/config/dependency_injection.dart'; import 'package:mc_common_app/config/dependency_injection.dart';
import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/config/routes.dart';
@ -25,11 +25,54 @@ import 'package:mc_common_app/view_models/requests_view_model.dart';
import 'package:mc_common_app/view_models/service_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/view_models/subscriptions_view_model.dart';
class DashboardVMProvider extends BaseVM { class DashboardVMProvider extends BaseVM with WidgetsBindingObserver {
final CommonAppServices commonServices; final CommonAppServices commonServices;
final UserRepo userRepo; final UserRepo userRepo;
BuildContext? _context; // Store context for lifecycle events
DashboardVMProvider({required this.commonServices, required this.userRepo}); DashboardVMProvider({required this.commonServices, required this.userRepo}) {
// Register as lifecycle observer
WidgetsBinding.instance.addObserver(this);
}
// Store context when dashboard is initialized
void setContext(BuildContext context) {
_context = context;
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_context = null;
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
logger.i("App lifecycle state changed to: $state");
if (_context == null) return;
final chatVM = Provider.of<ChatVM>(_context!, listen: false);
switch (state) {
case AppLifecycleState.resumed:
// App came back to foreground - ensure SignalR is connected
logger.i("App resumed - checking SignalR connection");
chatVM.buildHubConnection(context: _context);
break;
case AppLifecycleState.paused:
// App went to background - keep connection alive (as per user requirement)
logger.i("App paused - SignalR staying connected");
break;
case AppLifecycleState.inactive:
case AppLifecycleState.detached:
case AppLifecycleState.hidden:
// Other states - no action needed
break;
}
}
String pickedImage = ""; String pickedImage = "";
@ -102,7 +145,7 @@ class DashboardVMProvider extends BaseVM {
await adVM.getVehicleTypes(); await adVM.getVehicleTypes();
await adVM.getVehicleAdsDuration(); await adVM.getVehicleAdsDuration();
await chatVM.buildHubConnection(context); await chatVM.buildHubConnection(context: context);
} }
void checkUserSubscription(SubscriptionActionTypeEnum actionType, BuildContext context, {required Function() callback}) async { void checkUserSubscription(SubscriptionActionTypeEnum actionType, BuildContext context, {required Function() callback}) async {

@ -76,14 +76,14 @@ class PaymentVM extends ChangeNotifier {
Future<void> verifyPayments( Future<void> verifyPayments(
{required BuildContext context, required int id, List<int>? appointmentIds, required int paymentTypeId, required Function() onVerified, bool isForAppointment = false}) async { {required BuildContext context, required int id, List<int>? appointmentIds, required int paymentTypeId, required Function() onVerified, bool isForAppointment = false}) async {
try { try {
Utils.showLoading(context); // Utils.showLoading(context); // Commented out: causes BuildContext error after screen is popped
bool isPaid = await paymentRepo.verifyPayment( bool isPaid = await paymentRepo.verifyPayment(
paymentTypeId: paymentTypeId, paymentTypeId: paymentTypeId,
id: id, id: id,
isForAppointments: isForAppointment, isForAppointments: isForAppointment,
appointmentIds: appointmentIds, appointmentIds: appointmentIds,
); );
Utils.hideLoading(context); // Utils.hideLoading(context); // Commented out: matching pair with showLoading
// TODO: VERIFY THIS WITH ZAHOOR // TODO: VERIFY THIS WITH ZAHOOR
if (!isPaid) { if (!isPaid) {
@ -116,12 +116,13 @@ class PaymentVM extends ChangeNotifier {
} }
} }
Future<void> placeThePayment({required PaymentTypes paymentTypeEnum, required BuildContext context}) async { Future<void> placeThePayment({required PaymentTypes paymentTypeEnum, required BuildContext context, Function()? onExit}) async {
await paymentService.placePayment( await paymentService.placePayment(
id: getIdTypeByPaymentType(paymentTypeEnum), id: getIdTypeByPaymentType(paymentTypeEnum),
// This will be request ID for request payment || ad ID for Ad Related Payment || OrderProviderSubscriptionID for SubscriptionPayment // This will be request ID for request payment || ad ID for Ad Related Payment || OrderProviderSubscriptionID for SubscriptionPayment
appointmentIds: appointmentIdsForPayment, appointmentIds: appointmentIdsForPayment,
paymentType: paymentTypeEnum, paymentType: paymentTypeEnum,
onExit: onExit,
onFailure: () { onFailure: () {
Utils.showToast(LocaleKeys.paymentFailed.tr()); Utils.showToast(LocaleKeys.paymentFailed.tr());
switch (paymentTypeEnum) { switch (paymentTypeEnum) {
@ -219,26 +220,26 @@ class PaymentVM extends ChangeNotifier {
navigateReplaceWithNameUntilRoute(context, AppRoutes.dashboard); navigateReplaceWithNameUntilRoute(context, AppRoutes.dashboard);
} }
Future<void> onVisaCardSelected(BuildContext context, PaymentTypes paymentType) async { Future<void> onVisaCardSelected(BuildContext context, PaymentTypes paymentType, {Function()? onExit}) async {
currentPaymentType = paymentType; currentPaymentType = paymentType;
switch (currentPaymentType) { switch (currentPaymentType) {
case PaymentTypes.appointment: case PaymentTypes.appointment:
case PaymentTypes.partialAppointment: case PaymentTypes.partialAppointment:
if (appointmentIdsForPayment.isEmpty) return; if (appointmentIdsForPayment.isEmpty) return;
await placeThePayment(context: context, paymentTypeEnum: paymentType); await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit);
break; break;
case PaymentTypes.request: case PaymentTypes.request:
if (requestId == -1) return; if (requestId == -1) return;
await placeThePayment(context: context, paymentTypeEnum: paymentType); await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit);
break; break;
case PaymentTypes.subscription: case PaymentTypes.subscription:
await placeThePayment(context: context, paymentTypeEnum: paymentType); await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit);
break; break;
case PaymentTypes.ads: case PaymentTypes.ads:
case PaymentTypes.adReserve: case PaymentTypes.adReserve:
case PaymentTypes.extendAds: case PaymentTypes.extendAds:
if (currentAdId == -1) return; if (currentAdId == -1) return;
await placeThePayment(context: context, paymentTypeEnum: paymentType); await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit);
break; break;
} }
} }

@ -45,6 +45,9 @@ class RequestsVM extends BaseVM {
List<FilterListModel> requestsTypeFilterOptions = []; List<FilterListModel> requestsTypeFilterOptions = [];
// Spare parts condition filters (All, New, Used)
List<FilterListModel> sparePartsConditionFilterOptions = [];
List<EnumsModel> myRequestsTypeEnum = []; List<EnumsModel> myRequestsTypeEnum = [];
List<EnumsModel> vehicleConditionsEnum = []; List<EnumsModel> vehicleConditionsEnum = [];
@ -157,9 +160,24 @@ class RequestsVM extends BaseVM {
requestCityIdApi = vehicleCityId.selectedId; requestCityIdApi = vehicleCityId.selectedId;
} }
int requestConditionIdApi = 0; int requestConditionIdApi = 0;
if (vehicleConditionId.selectedId != -1) {
log("DEBUG - requestTypeIdApi: $requestTypeIdApi");
log("DEBUG - sparePartsConditionFilterOptions.isNotEmpty: ${sparePartsConditionFilterOptions.isNotEmpty}");
if (sparePartsConditionFilterOptions.isNotEmpty) {
log("DEBUG - sparePartsConditionFilterOptions: ${sparePartsConditionFilterOptions.map((e) => '${e.title}(id:${e.id}, selected:${e.isSelected})').toList()}");
}
// Use condition filter for all request types
if (sparePartsConditionFilterOptions.isNotEmpty) {
// Get selected condition (All=3, New=1, Used=2)
requestConditionIdApi = sparePartsConditionFilterOptions.firstWhere((element) => element.isSelected).id;
log("DEBUG - Using condition filter: $requestConditionIdApi");
} else if (vehicleConditionId.selectedId != -1) {
// Fallback to regular vehicle condition filter if condition filters not initialized
requestConditionIdApi = vehicleConditionId.selectedId; requestConditionIdApi = vehicleConditionId.selectedId;
log("DEBUG - Using vehicle condition filter: $requestConditionIdApi");
} }
int requestVehicleYearIdApi = 0; int requestVehicleYearIdApi = 0;
if (vehicleYearId.selectedId != -1) { if (vehicleYearId.selectedId != -1) {
requestVehicleYearIdApi = vehicleYearId.selectedId; requestVehicleYearIdApi = vehicleYearId.selectedId;
@ -168,6 +186,7 @@ class RequestsVM extends BaseVM {
if (requestStatusId.selectedId != -1) { if (requestStatusId.selectedId != -1) {
requestStatusIdApi = requestStatusId.selectedId; requestStatusIdApi = requestStatusId.selectedId;
} }
log("requestConditionIdApi:${requestConditionIdApi}");
try { try {
myFilteredRequests = await requestRepo.getRequestsBasedOnFilters( myFilteredRequests = await requestRepo.getRequestsBasedOnFilters(
@ -197,7 +216,7 @@ class RequestsVM extends BaseVM {
if (acceptedRequestsTypeFilterOptions.isEmpty) { if (acceptedRequestsTypeFilterOptions.isEmpty) {
for (int i = 0; i < myRequestsTypeEnum.length; i++) { for (int i = 0; i < myRequestsTypeEnum.length; i++) {
acceptedRequestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, isSelected: false, id: myRequestsTypeEnum[i].enumValue)); acceptedRequestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, titleN: myRequestsTypeEnum[i].enumValueStrN, isSelected: false, id: myRequestsTypeEnum[i].enumValue));
} }
acceptedRequestsTypeFilterOptions[0].isSelected = true; acceptedRequestsTypeFilterOptions[0].isSelected = true;
} }
@ -257,17 +276,52 @@ class RequestsVM extends BaseVM {
notifyListeners(); notifyListeners();
} }
applyFilterOnRequestsVM({required RequestsTypeEnum requestsTypeEnum}) async { applyFilterOnRequestsVM({required RequestsTypeEnum requestsTypeEnum, int? conditionId}) async {
if (requestsTypeFilterOptions.isEmpty) return; if (requestsTypeFilterOptions.isEmpty) return;
for (var value in requestsTypeFilterOptions) { for (var value in requestsTypeFilterOptions) {
value.isSelected = false; value.isSelected = false;
} }
requestsTypeFilterOptions[requestsTypeEnum.getIdFromRequestTypeEnum() - 1].isSelected = true; // -1 to match with the index requestsTypeFilterOptions[requestsTypeEnum.getIdFromRequestTypeEnum() - 1].isSelected = true; // -1 to match with the index
// Set condition filter: use provided conditionId or default to "All"
if (sparePartsConditionFilterOptions.isNotEmpty) {
for (var value in sparePartsConditionFilterOptions) {
value.isSelected = false;
}
if (conditionId != null && conditionId != -1) {
// Find and select the matching condition
int index = sparePartsConditionFilterOptions.indexWhere((element) => element.id == conditionId);
if (index != -1) {
sparePartsConditionFilterOptions[index].isSelected = true;
} else {
sparePartsConditionFilterOptions[0].isSelected = true; // Fallback to "All"
}
} else {
sparePartsConditionFilterOptions[0].isSelected = true; // Select "All" by default
}
}
await getRequestsBasedOnFilters(); await getRequestsBasedOnFilters();
isOfferSent = false; isOfferSent = false;
notifyListeners(); notifyListeners();
} }
// Apply spare parts condition filter (All, New, Used)
applySparePartsConditionFilter({required int conditionId}) async {
if (sparePartsConditionFilterOptions.isEmpty) return;
for (var value in sparePartsConditionFilterOptions) {
value.isSelected = false;
}
// Find and select the filter with matching conditionId
int index = sparePartsConditionFilterOptions.indexWhere((element) => element.id == conditionId);
if (index != -1) {
sparePartsConditionFilterOptions[index].isSelected = true;
}
log("index: $index");
await getRequestsBasedOnFilters();
notifyListeners();
}
List<ImageModel> pickedVehicleImages = []; List<ImageModel> pickedVehicleImages = [];
String vehicleImageError = ""; String vehicleImageError = "";
@ -329,11 +383,18 @@ class RequestsVM extends BaseVM {
if (requestsTypeFilterOptions.isEmpty || acceptedRequestsTypeFilterOptions.isEmpty) { if (requestsTypeFilterOptions.isEmpty || acceptedRequestsTypeFilterOptions.isEmpty) {
for (int i = 0; i < myRequestsTypeEnum.length; i++) { for (int i = 0; i < myRequestsTypeEnum.length; i++) {
requestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, isSelected: false, id: myRequestsTypeEnum[i].enumValue)); requestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr,titleN: myRequestsTypeEnum[i].enumValueStrN, isSelected: false, id: myRequestsTypeEnum[i].enumValue));
acceptedRequestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, isSelected: false, id: myRequestsTypeEnum[i].enumValue)); acceptedRequestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, titleN: myRequestsTypeEnum[i].enumValueStrN, isSelected: false, id: myRequestsTypeEnum[i].enumValue));
} }
} }
// Initialize spare parts condition filters (All, New, Used)
if (sparePartsConditionFilterOptions.isEmpty) {
sparePartsConditionFilterOptions.add(FilterListModel(title: "All", titleN: "الكل", isSelected: true, id: 3)); // All
sparePartsConditionFilterOptions.add(FilterListModel(title: "New Spare Part", titleN: "قطعة غيار جديدة", isSelected: false, id: 1)); // New
sparePartsConditionFilterOptions.add(FilterListModel(title: "Used Spare Part",titleN: "قطعة غيار مستعملة", isSelected: false, id: 2)); // Used
}
if (vehicleBrands.isEmpty) { if (vehicleBrands.isEmpty) {
vehicleBrands = await commonRepo.getVehicleBrands(vehicleTypeId: -1); vehicleBrands = await commonRepo.getVehicleBrands(vehicleTypeId: -1);
} }
@ -815,8 +876,8 @@ class RequestsVM extends BaseVM {
brand: brand, brand: brand,
model: model, model: model,
year: vehicleYearId.selectedOption, year: vehicleYearId.selectedOption,
isNew: vehicleConditionId.selectedId == 1, conditionType: vehicleConditionId.selectedId,
// 1 for new, 2 for Used // conditionType: 1 for New, 2 for Used
countryID: vehicleCountryId.selectedId, countryID: vehicleCountryId.selectedId,
cityID: vehicleCityId.selectedId, cityID: vehicleCityId.selectedId,
price: price.isEmpty ? "1.0" : price, price: price.isEmpty ? "1.0" : price,
@ -830,7 +891,13 @@ class RequestsVM extends BaseVM {
log("requestTypeId.selectedId.toRequestTypeEnum(): ${requestTypeId.selectedId.toRequestTypeEnum()}"); log("requestTypeId.selectedId.toRequestTypeEnum(): ${requestTypeId.selectedId.toRequestTypeEnum()}");
Utils.showToast(LocaleKeys.requestSuccessfullyCreated.tr()); Utils.showToast(LocaleKeys.requestSuccessfullyCreated.tr());
Navigator.pop(context); Navigator.pop(context);
await applyFilterOnRequestsVM(requestsTypeEnum: requestTypeId.selectedId.toRequestTypeEnum());
// Apply filter to match the created request (request type + condition in one call)
await applyFilterOnRequestsVM(
requestsTypeEnum: requestTypeId.selectedId.toRequestTypeEnum(),
conditionId: vehicleConditionId.selectedId,
);
resetRequestCreationForm(); resetRequestCreationForm();
} else { } else {
Utils.showToast(respModel.message.toString()); Utils.showToast(respModel.message.toString());

@ -4,6 +4,7 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:http/http.dart';
import 'package:http/src/response.dart'; import 'package:http/src/response.dart';
import 'package:local_auth/local_auth.dart'; import 'package:local_auth/local_auth.dart';
import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/app_state.dart';
@ -748,6 +749,7 @@ class UserVM extends BaseVM {
} else { } else {
context.setLocale(const Locale('ar', 'SA')); context.setLocale(const Locale('ar', 'SA'));
} }
log ("updated status: ${AppState().isArabic(context)}");
// notifyListeners(); // notifyListeners();
} }

@ -25,6 +25,7 @@ class VehicleDetails extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isArabic = AppState().isArabic(context);
return Consumer(builder: (BuildContext context, AdVM adVM, Widget? child) { return Consumer(builder: (BuildContext context, AdVM adVM, Widget? child) {
String roleAppState = ""; String roleAppState = "";
String roleName = ""; String roleName = "";
@ -56,7 +57,7 @@ class VehicleDetails extends StatelessWidget {
builder: (context) { builder: (context) {
List<DropValue> vehicleBrandsDrop = []; List<DropValue> vehicleBrandsDrop = [];
for (var element in adVM.vehicleBrands) { for (var element in adVM.vehicleBrands) {
vehicleBrandsDrop.add(DropValue(element.id?.toInt() ?? 0, element.vehicleBrandDescription ?? "", "")); vehicleBrandsDrop.add(DropValue(element.id?.toInt() ?? 0, isArabic ? element.vehicleBrandDescriptionN ?? "" : element.vehicleBrandDescription ?? "", ""));
} }
return DropdownField( return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleBrandId(SelectionModel(selectedId: value.id, selectedOption: value.value)), (DropValue value) => adVM.updateSelectionVehicleBrandId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
@ -106,12 +107,16 @@ class VehicleDetails extends StatelessWidget {
Builder(builder: (context) { Builder(builder: (context) {
List<DropValue> vehicleModelsDrop = []; List<DropValue> vehicleModelsDrop = [];
for (var element in adVM.vehicleModels) { for (var element in adVM.vehicleModels) {
vehicleModelsDrop.add(DropValue(element.id?.toInt() ?? 0, element.model ?? "", "")); // vehicleModelsDrop.add(DropValue(element.id?.toInt() ?? 0, element.model ?? "", ""));
vehicleModelsDrop.add(DropValue(element.id?.toInt() ?? 0, isArabic ? (element.modelN ?? element.model ?? ""): (element.model ?? ""), "",
),
);
} }
return DropdownField( return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleModelId(SelectionModel(selectedId: value.id, selectedOption: value.value)), (DropValue value) => adVM.updateSelectionVehicleModelId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleModelsDrop, list: vehicleModelsDrop,
dropdownValue: adVM.vehicleModelId.selectedId != -1 ? DropValue(adVM.vehicleModelId.selectedId, adVM.vehicleModelId.selectedOption, "") : null, // dropdownValue: adVM.vehicleModelId.selectedId != -1 ? DropValue(adVM.vehicleModelId.selectedId, adVM.vehicleModelId.selectedOption, "") : null,
dropdownValue: adVM.vehicleModelId.selectedId != -1 ? vehicleModelsDrop.firstWhere((item) => item.id == adVM.vehicleModelId.selectedId,) : null,
hint: LocaleKeys.vehicleModel.tr(), hint: LocaleKeys.vehicleModel.tr(),
errorValue: adVM.vehicleModelId.errorValue, errorValue: adVM.vehicleModelId.errorValue,
); );
@ -135,13 +140,17 @@ class VehicleDetails extends StatelessWidget {
Builder(builder: (context) { Builder(builder: (context) {
List<DropValue> vehicleColorsDrop = []; List<DropValue> vehicleColorsDrop = [];
for (var element in adVM.vehicleColors) { for (var element in adVM.vehicleColors) {
vehicleColorsDrop.add(DropValue(element.id?.toInt() ?? 0, element.color ?? "", "")); // vehicleColorsDrop.add(DropValue(element.id?.toInt() ?? 0, element.color ?? "", ""));
vehicleColorsDrop.add(DropValue(element.id?.toInt() ?? 0, isArabic ? (element.colorN ?? element.color ?? ""): (element.color ?? ""), "",
),
);
} }
return DropdownField( return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleColorId(SelectionModel(selectedId: value.id, selectedOption: value.value)), (DropValue value) => adVM.updateSelectionVehicleColorId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleColorsDrop, list: vehicleColorsDrop,
hint: LocaleKeys.vehicleColor.tr(), hint: LocaleKeys.vehicleColor.tr(),
dropdownValue: adVM.vehicleColorId.selectedId != -1 ? DropValue(adVM.vehicleColorId.selectedId, adVM.vehicleColorId.selectedOption, "") : null, // dropdownValue: adVM.vehicleColorId.selectedId != -1 ? DropValue(adVM.vehicleColorId.selectedId, adVM.vehicleColorId.selectedOption, "") : null,
dropdownValue: adVM.vehicleColorId.selectedId != -1 ? vehicleColorsDrop.firstWhere((item) => item.id == adVM.vehicleColorId.selectedId,) : null,
errorValue: adVM.vehicleColorId.errorValue, errorValue: adVM.vehicleColorId.errorValue,
); );
}), }),
@ -149,12 +158,14 @@ class VehicleDetails extends StatelessWidget {
Builder(builder: (context) { Builder(builder: (context) {
List<DropValue> vehicleConditionsDrop = []; List<DropValue> vehicleConditionsDrop = [];
for (var element in adVM.vehicleConditions) { for (var element in adVM.vehicleConditions) {
vehicleConditionsDrop.add(DropValue(element.id?.toInt() ?? 0, element.condition ?? "", "")); vehicleConditionsDrop.add(DropValue(element.id?.toInt() ?? 0, isArabic ? (element.conditionN ?? element.condition ?? ""): (element.condition ?? ""), ""));
// vehicleConditionsDrop.add(DropValue(element.id?.toInt() ?? 0, element.condition ?? "", ""));
} }
return DropdownField( return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleConditionId(SelectionModel(selectedId: value.id, selectedOption: value.value)), (DropValue value) => adVM.updateSelectionVehicleConditionId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleConditionsDrop, list: vehicleConditionsDrop,
dropdownValue: adVM.vehicleConditionId.selectedId != -1 ? DropValue(adVM.vehicleConditionId.selectedId, adVM.vehicleConditionId.selectedOption, "") : null, dropdownValue: adVM.vehicleConditionId.selectedId != -1 ? vehicleConditionsDrop.firstWhere((item) => item.id == adVM.vehicleConditionId.selectedId,) : null,
// dropdownValue: adVM.vehicleConditionId.selectedId != -1 ? DropValue(adVM.vehicleConditionId.selectedId, adVM.vehicleConditionId.selectedOption, "") : null,
hint: LocaleKeys.vehicleCondition.tr(), hint: LocaleKeys.vehicleCondition.tr(),
errorValue: adVM.vehicleConditionId.errorValue, errorValue: adVM.vehicleConditionId.errorValue,
); );
@ -163,13 +174,15 @@ class VehicleDetails extends StatelessWidget {
Builder(builder: (context) { Builder(builder: (context) {
List<DropValue> vehicleCategoriesDrop = []; List<DropValue> vehicleCategoriesDrop = [];
for (var element in adVM.vehicleCategories) { for (var element in adVM.vehicleCategories) {
vehicleCategoriesDrop.add(DropValue(element.id?.toInt() ?? 0, element.category ?? "", "")); vehicleCategoriesDrop.add(DropValue(element.id?.toInt() ?? 0, isArabic ? (element.categoryN ?? element.category ?? ""): (element.category ?? ""), ""));
// vehicleCategoriesDrop.add(DropValue(element.id?.toInt() ?? 0, element.category ?? "", ""));
} }
return DropdownField( return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleCategoryId(SelectionModel(selectedId: value.id, selectedOption: value.value)), (DropValue value) => adVM.updateSelectionVehicleCategoryId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleCategoriesDrop, list: vehicleCategoriesDrop,
hint: LocaleKeys.vehicleCategory.tr(), hint: LocaleKeys.vehicleCategory.tr(),
dropdownValue: adVM.vehicleCategoryId.selectedId != -1 ? DropValue(adVM.vehicleCategoryId.selectedId, adVM.vehicleCategoryId.selectedOption, "") : null, dropdownValue: adVM.vehicleCategoryId.selectedId != -1 ? vehicleCategoriesDrop.firstWhere((item) => item.id == adVM.vehicleCategoryId.selectedId,) : null,
// dropdownValue: adVM.vehicleCategoryId.selectedId != -1 ? DropValue(adVM.vehicleCategoryId.selectedId, adVM.vehicleCategoryId.selectedOption, "") : null,
errorValue: adVM.vehicleCategoryId.errorValue, errorValue: adVM.vehicleCategoryId.errorValue,
); );
}), }),
@ -191,13 +204,15 @@ class VehicleDetails extends StatelessWidget {
Builder(builder: (context) { Builder(builder: (context) {
List<DropValue> vehicleTransmissionsDrop = []; List<DropValue> vehicleTransmissionsDrop = [];
for (var element in adVM.vehicleTransmissions) { for (var element in adVM.vehicleTransmissions) {
vehicleTransmissionsDrop.add(DropValue(element.id?.toInt() ?? 0, element.transmission ?? "", "")); vehicleTransmissionsDrop.add(DropValue(element.id?.toInt() ?? 0, isArabic ? (element.transmissionN ?? element.transmission ?? ""): (element.transmission ?? ""), ""));
// vehicleTransmissionsDrop.add(DropValue(element.id?.toInt() ?? 0, element.transmission ?? "", ""));
} }
return DropdownField( return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleTransmissionId(SelectionModel(selectedId: value.id, selectedOption: value.value)), (DropValue value) => adVM.updateSelectionVehicleTransmissionId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleTransmissionsDrop, list: vehicleTransmissionsDrop,
hint: LocaleKeys.vehicleTransmission.tr(), hint: LocaleKeys.vehicleTransmission.tr(),
dropdownValue: adVM.vehicleTransmissionId.selectedId != -1 ? DropValue(adVM.vehicleTransmissionId.selectedId, adVM.vehicleTransmissionId.selectedOption, "") : null, dropdownValue: adVM.vehicleTransmissionId.selectedId != -1 ? vehicleTransmissionsDrop.firstWhere((item) => item.id == adVM.vehicleTransmissionId.selectedId,) : null,
//dropdownValue: adVM.vehicleTransmissionId.selectedId != -1 ? DropValue(adVM.vehicleTransmissionId.selectedId, adVM.vehicleTransmissionId.selectedOption, "") : null,
errorValue: adVM.vehicleTransmissionId.errorValue, errorValue: adVM.vehicleTransmissionId.errorValue,
); );
}), }),
@ -206,12 +221,14 @@ class VehicleDetails extends StatelessWidget {
Builder(builder: (context) { Builder(builder: (context) {
List<DropValue> vehicleCountriesDrop = []; List<DropValue> vehicleCountriesDrop = [];
for (var element in adVM.vehicleCountries) { for (var element in adVM.vehicleCountries) {
vehicleCountriesDrop.add(DropValue(element.id?.toInt() ?? 0, element.countryName ?? "", "")); vehicleCountriesDrop.add(DropValue(element.id?.toInt() ?? 0, isArabic ? (element.countryNameN ?? element.countryName ?? ""): (element.countryName ?? ""), ""));
// vehicleCountriesDrop.add(DropValue(element.id?.toInt() ?? 0, element.countryName ?? "", ""));
} }
return DropdownField( return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleCountryId(SelectionModel(selectedOption: value.value, selectedId: value.id)), (DropValue value) => adVM.updateSelectionVehicleCountryId(SelectionModel(selectedOption: value.value, selectedId: value.id)),
list: vehicleCountriesDrop, list: vehicleCountriesDrop,
dropdownValue: adVM.vehicleCountryId.selectedId != -1 ? DropValue(adVM.vehicleCountryId.selectedId, adVM.vehicleCountryId.selectedOption, "") : null, dropdownValue: adVM.vehicleCountryId.selectedId != -1 ? vehicleCountriesDrop.firstWhere((item) => item.id == adVM.vehicleCountryId.selectedId,) : null,
// dropdownValue: adVM.vehicleCountryId.selectedId != -1 ? DropValue(adVM.vehicleCountryId.selectedId, adVM.vehicleCountryId.selectedOption, "") : null,
hint: LocaleKeys.vehicleCountry.tr(), hint: LocaleKeys.vehicleCountry.tr(),
errorValue: adVM.vehicleCountryId.errorValue, errorValue: adVM.vehicleCountryId.errorValue,
); );
@ -227,12 +244,14 @@ class VehicleDetails extends StatelessWidget {
Builder(builder: (context) { Builder(builder: (context) {
List<DropValue> vehicleCitiesDrop = []; List<DropValue> vehicleCitiesDrop = [];
for (var element in adVM.vehicleCities) { for (var element in adVM.vehicleCities) {
vehicleCitiesDrop.add(DropValue(element.id?.toInt() ?? 0, element.cityName ?? "", "")); vehicleCitiesDrop.add(DropValue(element.id?.toInt() ?? 0, isArabic ? (element.cityNameN ?? element.cityName ?? ""): (element.cityName ?? ""), ""));
// vehicleCitiesDrop.add(DropValue(element.id?.toInt() ?? 0, element.cityName ?? "", ""));
} }
return DropdownField( return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleCityId(SelectionModel(selectedId: value.id, selectedOption: value.value)), (DropValue value) => adVM.updateSelectionVehicleCityId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleCitiesDrop, list: vehicleCitiesDrop,
dropdownValue: adVM.vehicleCityId.selectedId != -1 ? DropValue(adVM.vehicleCityId.selectedId, adVM.vehicleCityId.selectedOption, "") : null, dropdownValue: adVM.vehicleCityId.selectedId != -1 ? vehicleCitiesDrop.firstWhere((item) => item.id == adVM.vehicleCityId.selectedId,) : null,
// dropdownValue: adVM.vehicleCityId.selectedId != -1 ? DropValue(adVM.vehicleCityId.selectedId, adVM.vehicleCityId.selectedOption, "") : null,
hint: LocaleKeys.vehicleCity.tr(), hint: LocaleKeys.vehicleCity.tr(),
errorValue: adVM.vehicleCityId.errorValue, errorValue: adVM.vehicleCityId.errorValue,
); );
@ -259,6 +278,22 @@ class VehicleDetails extends StatelessWidget {
hint: LocaleKeys.odometerReading.tr(), hint: LocaleKeys.odometerReading.tr(),
onChanged: (v) => adVM.updateOdometer(v), onChanged: (v) => adVM.updateOdometer(v),
), ),
8.height,
// Can mileage increase dropdown
DropdownField(
(DropValue value) => adVM.updateIsInUsed(value.id),
list: [
DropValue(1, LocaleKeys.yes.tr(), ""),
DropValue(0, LocaleKeys.no.tr(), ""),
],
dropdownValue: DropValue(
adVM.isInUsed,
adVM.isInUsed == 1 ? LocaleKeys.yes.tr() : LocaleKeys.no.tr(),
"",
),
hint: LocaleKeys.canMileageIncrease.tr(),
errorValue: "",
),
], ],
], ],
8.height, 8.height,
@ -304,68 +339,31 @@ class VehicleDetails extends StatelessWidget {
onChanged: (v) => adVM.updateVehicleDescription(v), onChanged: (v) => adVM.updateVehicleDescription(v),
), ),
22.height, 22.height,
Row( // Finance Available toggle
mainAxisAlignment: MainAxisAlignment.spaceBetween, Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded( LocaleKeys.financeAvailable.tr().toText(fontSize: 16),
child: Column( 8.height,
crossAxisAlignment: CrossAxisAlignment.start, Container(
children: [ width: 50,
LocaleKeys.financeAvailable.tr().toText(fontSize: 16), height: 30,
8.height, decoration: BoxDecoration(
Container( color: adVM.financeAvailableStatus ? MyColors.darkPrimaryColor : MyColors.white,
width: 50, borderRadius: BorderRadius.circular(25.0),
height: 30, border: Border.all(color: MyColors.black, width: 1),
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);
},
),
),
),
],
), ),
), child: Transform.scale(
8.width, scale: 0.8,
Expanded( child: CupertinoSwitch(
child: Column( activeColor: MyColors.darkPrimaryColor,
crossAxisAlignment: CrossAxisAlignment.start, trackColor: MyColors.white,
children: [ thumbColor: MyColors.grey98Color,
LocaleKeys.currentlyInUse.tr().toText(fontSize: 16), value: adVM.financeAvailableStatus,
8.height, onChanged: (value) {
Container( adVM.updateFinanceAvailableStatus(value);
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);
},
),
),
),
],
), ),
), ),
], ],

@ -1,5 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/material.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/classes/consts.dart';
import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/int_extensions.dart';
import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart';
@ -23,6 +25,8 @@ import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:share_plus/share_plus.dart';
import 'package:sizer/sizer.dart';
class AdsDetailView extends StatefulWidget { class AdsDetailView extends StatefulWidget {
final AdDetailsModel adDetails; final AdDetailsModel adDetails;
@ -85,18 +89,35 @@ class _AdsDetailViewState extends State<AdsDetailView> {
} }
Widget buildVehicleDetailsWidget() { Widget buildVehicleDetailsWidget() {
bool isArabic = AppState().isArabic(context);
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ImagesCorouselWidget(imagesList: widget.adDetails.vehicle!.image!), Stack(
children: [
ImagesCorouselWidget(imagesList: widget.adDetails.vehicle!.image!),
Positioned(
top: 5,
right: 5,
child: Row(
children: [
ShareAdButton(adDetails: widget.adDetails),
8.width,
FavoriteAdButton(adDetails: widget.adDetails),
],
),
),
],
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Flexible( Flexible(
child: "${widget.adDetails.vehicle!.vehicleTitle} | ${widget.adDetails.vehicle!.color!.label}| ${widget.adDetails.id}".toText(fontSize: 16, maxLines: 2), child: "${widget.adDetails.vehicle!.vehicleTitle} | ${isArabic ? widget.adDetails.vehicle!.color!.labelN : widget.adDetails.vehicle!.color!.label}|"
" ${widget.adDetails.id}".toText(fontSize: 16, maxLines: 2),
), ),
(widget.adDetails.vehicle!.cityName ?? "").toText(fontSize: 10, color: MyColors.lightTextColor), (isArabic ? widget.adDetails.vehicle!.cityNameN ?? "" : widget.adDetails.vehicle!.cityName ?? "").toText(fontSize: 10, color: MyColors.lightTextColor),
], ],
), ),
Row( Row(
@ -105,10 +126,10 @@ class _AdsDetailViewState extends State<AdsDetailView> {
Row( Row(
children: [ children: [
("${LocaleKeys.vehicleModel.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), ("${LocaleKeys.vehicleModel.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor),
"${widget.adDetails.vehicle!.model!.label}".toText(fontSize: 12), "${isArabic? widget.adDetails.vehicle!.model!.labelN :widget.adDetails.vehicle!.model!.label}".toText(fontSize: 12),
], ],
), ),
(widget.adDetails.vehicle!.countryName ?? '').toText(fontSize: 10, color: MyColors.lightTextColor), (isArabic ? widget.adDetails.vehicle!.countryNameN?? '' : widget.adDetails.vehicle!.countryName?? '').toText(fontSize: 10, color: MyColors.lightTextColor),
], ],
), ),
Row( Row(
@ -124,22 +145,24 @@ class _AdsDetailViewState extends State<AdsDetailView> {
Row( Row(
children: [ children: [
("${LocaleKeys.mileage.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), ("${LocaleKeys.mileage.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor),
"${widget.adDetails.vehicle!.mileage!.mileageEnd} Km".toText(fontSize: 12), "${widget.adDetails.vehicle!.mileage!.mileageEnd} ${LocaleKeys.km.tr()}".toText(fontSize: 12),
], ],
), ),
widget.adDetails.createdOn != null ? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo().toText(fontSize: 10, color: MyColors.lightTextColor) : const SizedBox(), widget.adDetails.createdOn != null
? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo(isArabic: isArabic).toText(fontSize: 10,color: MyColors.lightTextColor,): const SizedBox(),
//widget.adDetails.createdOn != null ? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo().toText(fontSize: 10, color: MyColors.lightTextColor) : const SizedBox(),
], ],
), ),
Row( Row(
children: [ children: [
("${LocaleKeys.transmission.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), ("${LocaleKeys.transmission.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor),
"${widget.adDetails.vehicle!.transmission!.label}".toText(fontSize: 12), "${isArabic? widget.adDetails.vehicle!.transmission!.labelN :widget.adDetails.vehicle!.transmission!.label}".toText(fontSize: 12),
], ],
), ),
Row( Row(
children: [ children: [
("${LocaleKeys.odometer.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), ("${LocaleKeys.odometer.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor),
"${widget.adDetails.vehicle!.odometer ?? 0} Km".toText(fontSize: 12), "${widget.adDetails.vehicle!.odometer ?? 0} ${LocaleKeys.km.tr()}".toText(fontSize: 12),
], ],
), ),
Row( Row(
@ -363,12 +386,26 @@ class _AdsDetailViewState extends State<AdsDetailView> {
Widget buildAdStartEndDates() { Widget buildAdStartEndDates() {
String startDate = ""; String startDate = "";
String endDate = ""; String endDate = "";
bool isArabic = AppState().isArabic(context);
final atText = isArabic ? "الساعة" : "at";
if (widget.adDetails.startdate != null) { // if (widget.adDetails.startdate != null) {
startDate = "${DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.startdate!))} at ${DateHelper.formatAsTime(DateTime.parse(widget.adDetails.startdate!))}"; // 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!))}";
// }
if (widget.adDetails.startdate != null) {startDate = "${DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.startdate!),
isArabic: isArabic,)} $atText ${DateHelper.formatAsTime(
DateTime.parse(widget.adDetails.startdate!),
isArabic: isArabic,
)}";
} }
if (widget.adDetails.enddate != null) { if (widget.adDetails.enddate != null) {endDate = "${DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.enddate!),
endDate = "${DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.enddate!))} at ${DateHelper.formatAsTime(DateTime.parse(widget.adDetails.enddate!))}"; isArabic: isArabic,)} $atText ${DateHelper.formatAsTime(
DateTime.parse(widget.adDetails.enddate!),
isArabic: isArabic,
)}";
} }
return Column( return Column(
children: [ children: [
@ -526,3 +563,179 @@ class _AdsDetailViewState extends State<AdsDetailView> {
); );
} }
} }
class FavoriteAdButton extends StatefulWidget {
final AdDetailsModel adDetails;
const FavoriteAdButton({super.key, required this.adDetails});
@override
State<FavoriteAdButton> createState() => _FavoriteAdButtonState();
}
class _FavoriteAdButtonState extends State<FavoriteAdButton> with SingleTickerProviderStateMixin {
bool isLoading = false;
late bool isFavorite;
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
isFavorite = widget.adDetails.isAdFavorite ?? false;
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.3).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
Future<void> toggleFavorite() async {
if (isLoading) return;
setState(() {
isLoading = true;
});
_animationController.forward().then((_) => _animationController.reverse());
bool success = await context.read<AdVM>().toggleAdFavorite(
adsID: widget.adDetails.id!,
isFavorite: isFavorite,
);
if (success) {
setState(() {
isFavorite = !isFavorite;
widget.adDetails.isAdFavorite = isFavorite;
isLoading = false;
});
} else {
setState(() {
isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: toggleFavorite,
child: Container(
width: 8.6.w,
height: 8.6.w,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: const Color(0xff000000).withOpacity(.07),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Center(
child: isLoading
? SizedBox(
width: 4.4.w,
height: 4.4.w,
child: const CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(MyColors.darkPrimaryColor),
),
)
: ScaleTransition(
scale: _scaleAnimation,
child: Icon(
isFavorite ? Icons.favorite : Icons.favorite_border,
color: isFavorite ? MyColors.darkPrimaryColor : MyColors.darkIconColor,
size: 5.5.w,
),
),
),
),
);
}
}
class ShareAdButton extends StatelessWidget {
final AdDetailsModel adDetails;
const ShareAdButton({super.key, required this.adDetails});
void _shareAd() {
final vehicle = adDetails.vehicle!;
// Build share message
String shareMessage = '';
// Add header
shareMessage += '${LocaleKeys.checkOutThisAd.tr()}\n\n';
// Vehicle details
shareMessage += '${vehicle.vehicleTitle ?? ""}\n';
shareMessage += '${LocaleKeys.model.tr()}: ${vehicle.modelyear?.label ?? ""}\n';
shareMessage += '${LocaleKeys.mileage.tr()}: ${vehicle.mileage?.mileageEnd ?? ""}\n';
shareMessage += '${LocaleKeys.price.tr()}: ${vehicle.demandAmount?.toInt()} ${vehicle.currency ?? ""}\n';
if (vehicle.cityName != null && vehicle.cityName!.isNotEmpty) {
shareMessage += '${LocaleKeys.location.tr()}: ${vehicle.cityName}\n';
}
shareMessage += '\n';
// App download links
shareMessage += '${LocaleKeys.downloadApp.tr()}\n';
if (Platform.isAndroid) {
shareMessage += '${LocaleKeys.androidApp.tr()}: https://play.google.com/store/apps/details?id=com.mowater.app\n';
} else if (Platform.isIOS) {
shareMessage += '${LocaleKeys.iosApp.tr()}: https://apps.apple.com/app/mowater/id123456789\n';
} else {
// Include both if platform detection fails
shareMessage += '${LocaleKeys.androidApp.tr()}: https://play.google.com/store/apps/details?id=com.mowater.app\n';
shareMessage += '${LocaleKeys.iosApp.tr()}: https://apps.apple.com/app/mowater/id123456789\n';
}
// Share using share_plus
Share.share(shareMessage);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _shareAd,
child: Container(
width: 8.6.w,
height: 8.6.w,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: const Color(0xff000000).withOpacity(.07),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Center(
child: Icon(
Icons.share,
color: MyColors.darkIconColor,
size: 5.5.w,
),
),
),
);
}
}

@ -125,6 +125,8 @@ class AdDurationSelectionSheet extends StatelessWidget {
isBorderRequired: adDuration.id == adVM.vehicleAdDurationId.selectedId, isBorderRequired: adDuration.id == adVM.vehicleAdDurationId.selectedId,
), ),
).onPress(() { ).onPress(() {
log("isFromExtendAd: $isFromExtendAd");
if (isFromExtendAd) { if (isFromExtendAd) {
adVM.updateVehicleExtendAdDurationId( adVM.updateVehicleExtendAdDurationId(
SelectionModel( SelectionModel(

@ -1,4 +1,5 @@
import 'dart:developer'; import 'dart:developer';
import 'dart:ui';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -17,6 +18,8 @@ import 'package:mc_common_app/view_models/ad_view_model.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../../classes/app_state.dart';
class AdsListWidget extends StatelessWidget { class AdsListWidget extends StatelessWidget {
final List<AdDetailsModel> adsList; final List<AdDetailsModel> adsList;
final ScrollPhysics? scrollPhysics; final ScrollPhysics? scrollPhysics;
@ -108,19 +111,54 @@ class AdsListWidget extends StatelessWidget {
} }
} }
class AdCard extends StatelessWidget { class AdCard extends StatefulWidget {
final AdDetailsModel adDetails; final AdDetailsModel adDetails;
final bool isAdsFragment; final bool isAdsFragment;
final bool shouldShowAdStatus; final bool shouldShowAdStatus;
final bool needBorder;
const AdCard({
super.key,
required this.adDetails,
required this.isAdsFragment,
required this.shouldShowAdStatus,
this.needBorder = true,
});
@override
State<AdCard> createState() => _AdCardState();
}
class _AdCardState extends State<AdCard> {
int _currentImageIndex = 0;
late PageController _pageController;
const AdCard({super.key, required this.adDetails, required this.isAdsFragment, required this.shouldShowAdStatus}); @override
void initState() {
super.initState();
_pageController = PageController();
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isArabic = AppState().isArabic(context);
final bool hasMultipleImages = widget.adDetails.vehicle?.image?.length == null
? false
: (widget.adDetails.vehicle?.image?.length == 0 || widget.adDetails.vehicle?.image?.length == 1)
? false
: true;
final isAdsFragment = widget.isAdsFragment;
return Stack( return Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
if (adDetails.vehicle == null) ...[ if (widget.adDetails.vehicle == null) ...[
Expanded( Expanded(
child: Column( child: Column(
children: [ children: [
@ -131,10 +169,97 @@ class AdCard extends StatelessWidget {
] else ...[ ] else ...[
Row( Row(
children: [ children: [
adDetails.vehicle!.image!.first.imageUrl.buildNetworkImage( Stack(
width: 80, alignment: Alignment.center,
height: 80, children: [
fit: BoxFit.cover, ClipRRect(
child: SizedBox(
width: 100,
height: 100,
child: hasMultipleImages
? PageView.builder(
controller: _pageController,
itemCount: widget.adDetails.vehicle!.image!.length,
onPageChanged: (index) {
setState(() {
_currentImageIndex = index;
});
},
itemBuilder: (context, index) {
return widget.adDetails.vehicle!.image![index].imageUrl.buildNetworkImage(
width: 100,
height: 100,
fit: BoxFit.cover,
);
},
)
: widget.adDetails.vehicle!.image!.first.imageUrl.buildNetworkImage(
width: 100,
height: 100,
fit: BoxFit.cover,
),
),
),
// Image indicator dots
if (hasMultipleImages)
Positioned(
bottom: 6,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.4),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: List.generate(
widget.adDetails.vehicle!.image!.length,
(index) => Container(
width: 6,
height: 6,
margin: const EdgeInsets.symmetric(horizontal: 3),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _currentImageIndex == index ? Colors.white : Colors.white.withOpacity(0.5),
border: Border.all(
color: Colors.black.withOpacity(0.2),
width: 0.5,
),
),
),
),
),
),
),
if (widget.adDetails.adPostStatus == AdPostStatus.sold) ...[
// Blur overlay
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
child: Container(
width: 100,
height: 100,
color: Colors.black.withOpacity(0.3),
),
),
),
// SOLD text
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(4),
),
child: LocaleKeys.sold.tr().toText(
fontSize: 12,
color: Colors.white,
isBold: true,
),
),
],
],
), ),
12.width, 12.width,
Expanded( Expanded(
@ -150,12 +275,37 @@ class AdCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
if (isAdsFragment && adDetails.adPostStatus! == AdPostStatus.sold && adDetails.statuslabel != null && adDetails.statuslabel!.isNotEmpty) ...[ // if (isAdsFragment && adDetails.adPostStatus! == AdPostStatus.sold && adDetails.statuslabel != null && adDetails.statuslabel!.isNotEmpty) ...[
Utils.statusContainerChip(text: adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(adDetails.adPostStatus!)), // Utils.statusContainerChip(text: adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(adDetails.adPostStatus!)),
] else if (isAdsFragment && !context.read<AdVM>().isExploreAdsTapped && adDetails.statuslabel != null && adDetails.statuslabel!.isNotEmpty) ...[ // ] else if (isAdsFragment && !context.read<AdVM>().isExploreAdsTapped && adDetails.statuslabel != null && adDetails.statuslabel!.isNotEmpty) ...[
Utils.statusContainerChip(text: adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(adDetails.adPostStatus!)), // Utils.statusContainerChip(text: adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(adDetails.adPostStatus!)),
// ],
if (isAdsFragment &&
widget.adDetails.adPostStatus! == AdPostStatus.sold &&
widget.adDetails.statuslabel != null &&
widget.adDetails.statuslabel!.isNotEmpty) ...[
Utils.statusContainerChip(
text: isArabic
? (widget.adDetails.statuslabelN ?? "")
: (widget.adDetails.statuslabel ?? ""),
chipColor: Utils.getChipColorByAdStatus(widget.adDetails.adPostStatus!),
),
] else if (isAdsFragment &&
!context.read<AdVM>().isExploreAdsTapped &&
widget.adDetails.statuslabel != null &&
widget.adDetails.statuslabel!.isNotEmpty) ...[
Utils.statusContainerChip(
text: isArabic
? (widget.adDetails.statuslabelN ?? "")
: (widget.adDetails.statuslabel ?? ""),
chipColor: Utils.getChipColorByAdStatus(widget.adDetails.adPostStatus!),
),
],
// Show Accident Free chip for explore ads when isAccidentFree is true
if (isAdsFragment && context.read<AdVM>().isExploreAdsTapped && widget.adDetails.vehicle?.isAccidentFree == true) ...[
Utils.statusContainerChip(text: LocaleKeys.accidentFree.tr(), chipColor: Colors.green),
], ],
(adDetails.vehicle!.vehicleTitle ?? "").toText( (widget.adDetails.vehicle!.vehicleTitle ?? "").toText(
fontSize: 16, fontSize: 16,
letterSpacing: -0.64, letterSpacing: -0.64,
height: 25 / 16, height: 25 / 16,
@ -169,7 +319,7 @@ class AdCard extends StatelessWidget {
height: 18 / 12, height: 18 / 12,
), ),
2.width, 2.width,
(adDetails.vehicle!.modelyear!.label ?? "").toText( (widget.adDetails.vehicle!.modelyear!.label ?? "").toText(
fontSize: 12, fontSize: 12,
letterSpacing: -0.48, letterSpacing: -0.48,
height: 18 / 12, height: 18 / 12,
@ -185,7 +335,7 @@ class AdCard extends StatelessWidget {
height: 18 / 12, height: 18 / 12,
), ),
2.width, 2.width,
(adDetails.vehicle!.mileage!.mileageEnd ?? "").toText( (widget.adDetails.vehicle!.mileage!.mileageEnd ?? "").toText(
fontSize: 12, fontSize: 12,
letterSpacing: -0.48, letterSpacing: -0.48,
height: 18 / 12, height: 18 / 12,
@ -199,8 +349,16 @@ class AdCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
(adDetails.vehicle!.cityName ?? "").toText(color: MyColors.lightTextColor), (isArabic? widget.adDetails.vehicle!.cityNameN ?? "" : widget.adDetails.vehicle!.cityName ?? "").toText(color: MyColors.lightTextColor),
adDetails.createdOn != null ? DateTime.parse(adDetails.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor) : const SizedBox(), // widget.adDetails.createdOn != null ? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor) : const SizedBox(),
widget.adDetails.createdOn != null
? DateFormat(
'd MMM, y',
isArabic ? 'ar' : 'en',
)
.format(DateTime.parse(widget.adDetails.createdOn!))
.toText(color: MyColors.lightTextColor)
: const SizedBox(),
], ],
), ),
], ],
@ -213,9 +371,9 @@ class AdCard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
(adDetails.vehicle!.demandAmount!.toInt().toString()).toText(fontSize: 19, isBold: true, letterSpacing: -1.16, height: 29 / 19), (widget.adDetails.vehicle!.demandAmount!.toInt().toString()).toText(fontSize: 19, isBold: true, letterSpacing: -1.16, height: 29 / 19),
2.width, 2.width,
(adDetails.vehicle!.currency ?? "").toText(color: MyColors.lightTextColor, fontSize: 10, letterSpacing: -0.4, height: 16 / 10).paddingOnly(bottom: 3), (widget.adDetails.vehicle!.currency ?? "").toText(color: MyColors.lightTextColor, fontSize: 10, letterSpacing: -0.4, height: 16 / 10).paddingOnly(bottom: 3),
], ],
), ),
), ),
@ -258,6 +416,7 @@ class AdCard extends StatelessWidget {
).toWhiteContainer( ).toWhiteContainer(
width: double.infinity, width: double.infinity,
allPading: 12, allPading: 12,
isBorderRequired: widget.needBorder,
); );
} }
} }

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/app_state.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/int_extensions.dart';
@ -7,7 +5,6 @@ import 'package:mc_common_app/generated/locale_keys.g.dart';
import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/enums.dart';
import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/utils/navigator.dart';
import 'package:mc_common_app/utils/utils.dart';
import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart';
import 'package:mc_common_app/views/advertisement/ad_creation_steps/ad_duration_container.dart'; import 'package:mc_common_app/views/advertisement/ad_creation_steps/ad_duration_container.dart';
import 'package:mc_common_app/views/advertisement/ad_creation_steps/ad_review_containers.dart'; import 'package:mc_common_app/views/advertisement/ad_creation_steps/ad_review_containers.dart';
@ -76,18 +73,45 @@ class BuildFooterButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isArabic = AppState().isArabic(context);
return Consumer( return Consumer(
builder: (BuildContext context, AdVM adVm, Widget? child) { builder: (BuildContext context, AdVM adVm, Widget? child) {
switch (adVm.currentProgressStep) { switch (adVm.currentProgressStep) {
case AdCreationStepsEnum.vehicleDetails: case AdCreationStepsEnum.vehicleDetails:
return SizedBox( return Row(
width: double.infinity, children: [
child: ShowFillButton( Expanded(
title: LocaleKeys.next.tr(), child: ShowFillButton(
onPressed: () { txtColor: MyColors.black,
adVm.updateCurrentStep(context); maxHeight: 55,
}, // title: "Save As Draft",
), title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.vehicleDetails, bypassValidation: true);
adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues();
pop(context);
if (AppState().currentAppType == AppType.customer) {
pop(context);
}
},
backgroundColor: MyColors.greyButtonColor,
),
),
12.width,
Expanded(
child: ShowFillButton(
maxHeight: 55,
title: LocaleKeys.next.tr(),
onPressed: () {
adVm.updateCurrentStep(context);
},
backgroundColor: MyColors.darkPrimaryColor,
),
),
],
); );
case AdCreationStepsEnum.damageParts: case AdCreationStepsEnum.damageParts:
return Row( return Row(
@ -96,16 +120,18 @@ class BuildFooterButton extends StatelessWidget {
child: ShowFillButton( child: ShowFillButton(
txtColor: MyColors.black, txtColor: MyColors.black,
maxHeight: 55, maxHeight: 55,
title: LocaleKeys.cancel.tr(), // title: "Save As Draft",
onPressed: () { title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.damageParts, bypassValidation: true);
adVm.isDraftEditEnabled = false; adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues(); adVm.resetValues();
pop(context); pop(context);
if (AppState().currentAppType == AppType.customer) { if (AppState().currentAppType == AppType.customer) {
pop(context); pop(context);
} }
Utils.showToast('Saved as Draft');
adVm.getMyDraftAds();
}, },
backgroundColor: MyColors.greyButtonColor, backgroundColor: MyColors.greyButtonColor,
), ),
@ -130,17 +156,18 @@ class BuildFooterButton extends StatelessWidget {
child: ShowFillButton( child: ShowFillButton(
txtColor: MyColors.black, txtColor: MyColors.black,
maxHeight: 55, maxHeight: 55,
title: LocaleKeys.cancel.tr(), // title: "Save As Draft",
onPressed: () { title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.adDuration, bypassValidation: true);
adVm.isDraftEditEnabled = false; adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues(); adVm.resetValues();
pop(context); pop(context);
if (AppState().currentAppType == AppType.customer) { if (AppState().currentAppType == AppType.customer) {
pop(context); pop(context);
} }
Utils.showToast('Saved as Draft');
adVm.getMyDraftAds();
}, },
backgroundColor: MyColors.greyButtonColor, backgroundColor: MyColors.greyButtonColor,
), ),
@ -165,17 +192,18 @@ class BuildFooterButton extends StatelessWidget {
child: ShowFillButton( child: ShowFillButton(
txtColor: MyColors.black, txtColor: MyColors.black,
maxHeight: 55, maxHeight: 55,
title: LocaleKeys.cancel.tr(), //title: "Save As Draft",
onPressed: () { title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.reviewAd, bypassValidation: true);
adVm.isDraftEditEnabled = false; adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues(); adVm.resetValues();
pop(context); pop(context);
if (AppState().currentAppType == AppType.customer) { if (AppState().currentAppType == AppType.customer) {
pop(context); pop(context);
} }
Utils.showToast('Saved as Draft');
adVm.getMyDraftAds();
}, },
backgroundColor: MyColors.greyButtonColor, backgroundColor: MyColors.greyButtonColor,
), ),

@ -0,0 +1,104 @@
import 'dart:async';
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/view_models/ad_view_model.dart';
import 'package:flutter/material.dart';
import 'package:mc_common_app/extensions/int_extensions.dart';
import 'package:mc_common_app/utils/enums.dart';
import 'package:mc_common_app/views/advertisement/components/ads_list_widget.dart';
import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/common_widgets/filters_list.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart';
class FavouriteAdsView extends StatefulWidget {
const FavouriteAdsView({super.key});
@override
State<FavouriteAdsView> createState() => _FavouriteAdsViewState();
}
class _FavouriteAdsViewState extends State<FavouriteAdsView> {
int selectedBrandId = 0;
@override
void initState() {
scheduleMicrotask(() async {
final adVM = context.read<AdVM>();
await adVM.getFavouriteAds();
// Load filter options if not already loaded
if (adVM.exploreAdsFilterOptions.isEmpty) {
await adVM.populateAdsFilterList();
}
});
super.initState();
}
List<AdDetailsModel> getFilteredAds(AdVM adVM) {
if (selectedBrandId == 0) {
return adVM.favouriteAds;
}
return adVM.favouriteAds.where((ad) => ad.vehicle?.model?.vehicleBrandID == selectedBrandId).toList();
}
@override
Widget build(BuildContext context) {
return Consumer(builder: (BuildContext context, AdVM adVM, Widget? child) {
return Scaffold(
appBar: CustomAppBar(title: LocaleKeys.myFavouriteAds.tr()),
body: SizedBox(
width: double.infinity,
height: double.infinity,
child: Column(
children: [
if (adVM.exploreAdsFilterOptions.isNotEmpty) ...[
16.height,
FiltersList(
showImages: true,
filterList: adVM.exploreAdsFilterOptions,
onFilterTapped: (index, selectedFilterId) {
setState(() {
selectedBrandId = selectedFilterId;
});
// Update filter selection in the list
for (var filter in adVM.exploreAdsFilterOptions) {
filter.isSelected = false;
}
adVM.exploreAdsFilterOptions[index].isSelected = true;
},
needLeftPadding: false,
).paddingOnly(left: 21),
],
16.height,
Expanded(
child: RefreshIndicator(
onRefresh: () async {
await adVM.getFavouriteAds();
},
child: adVM.state == ViewState.busy
? const Center(child: CircularProgressIndicator())
: getFilteredAds(adVM).isEmpty
? Center(
child: Text(
LocaleKeys.noFavouriteAds.tr(),
style: const TextStyle(fontSize: 16),
),
)
: AdsListWidget(
isAdsFragment: false,
isDraftAds: false,
shouldShowAdStatus: false,
adsList: getFilteredAds(adVM),
hasMoreData: false,
),
),
),
],
),
),
);
});
}
}

@ -70,16 +70,16 @@ class SelectAdTypeView extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: Consumer(builder: (BuildContext context, AdVM adVM, Widget? child) { child: Consumer(builder: (BuildContext context, AdVM adVM, Widget? child) {
final isArabic = AppState().isArabic(context);
return ListView.separated( return ListView.separated(
itemCount: adVM.vehicleTypes.length, itemCount: adVM.vehicleTypes.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
VehicleTypeModel vehicleTypeModel = adVM.vehicleTypes[index]; VehicleTypeModel vehicleTypeModel = adVM.vehicleTypes[index];
return InkWell( return InkWell(
onTap: () async { onTap: () async {
adVM.updateSelectionVehicleTypeId(SelectionModel(selectedId: vehicleTypeModel.id!, selectedOption: vehicleTypeModel.vehicleTypeName ?? "", errorValue: "")); adVM.updateSelectionVehicleTypeId(SelectionModel(selectedId: vehicleTypeModel.id!, selectedOption: isArabic ? vehicleTypeModel.vehicleTypeNameN ?? "" : vehicleTypeModel.vehicleTypeName ?? "", errorValue: ""));
if (adVM.isAdEditEnabled) { if (adVM.isAdEditEnabled) {
adVM. adVM.autoFillSelectedVehicleAdsDetails(context);
autoFillSelectedVehicleAdsDetails();
} }
if (AppState().currentAppType == AppType.provider) { if (AppState().currentAppType == AppType.provider) {
if (isFromExtendAd && !adVM.isAdEditEnabled) { if (isFromExtendAd && !adVM.isAdEditEnabled) {
@ -114,7 +114,9 @@ class SelectAdTypeView extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
vehicleTypeModel.vehicleTypeName.toString().toText(fontSize: 18), isArabic ? vehicleTypeModel.vehicleTypeNameN.toString().toText(fontSize: 18)
: vehicleTypeModel.vehicleTypeName.toString().toText(fontSize: 18),
// vehicleTypeModel.vehicleTypeName.toString().toText(fontSize: 18),
getVehicleAdTypeIcon(vehicleTypeModel.vehicleTypeEnum!), getVehicleAdTypeIcon(vehicleTypeModel.vehicleTypeEnum!),
], ],
), ),

@ -22,6 +22,8 @@ import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:mc_common_app/widgets/txt_field.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../../classes/app_state.dart';
class AppointmentServicePickBottomSheet extends StatelessWidget { class AppointmentServicePickBottomSheet extends StatelessWidget {
const AppointmentServicePickBottomSheet({super.key}); const AppointmentServicePickBottomSheet({super.key});
@ -70,12 +72,16 @@ class AppointmentServicePickBottomSheet extends StatelessWidget {
8.height, 8.height,
Builder( Builder(
builder: (context) { builder: (context) {
bool isArabic = AppState().isArabic(context);
List<DropValue> serviceCategories = []; List<DropValue> serviceCategories = [];
for (var element in appointmentsVM.branchServices) { for (var element in appointmentsVM.branchServices) {
if (element.categoryId == appointmentsVM.branchSelectedCategoryId.selectedId) { if (element.categoryId == appointmentsVM.branchSelectedCategoryId.selectedId) {
serviceCategories.add(DropValue( serviceCategories.add(DropValue(
element.serviceProviderServiceId ?? 0, element.serviceProviderServiceId ?? 0,
element.serviceDescription!, isArabic
? element.serviceDescriptionN!
: element.serviceDescription!,
// element.serviceDescription!,
"", "",
)); ));
} }

@ -173,7 +173,7 @@ class AppointmentHomeTileWidget extends StatelessWidget {
children: [ children: [
!isForProvider ? MyAssets.miniClock.buildSvg(height: 12) : const SizedBox(), !isForProvider ? MyAssets.miniClock.buildSvg(height: 12) : const SizedBox(),
2.width, 2.width,
"${isForProvider ? "Appt. on:" : ""} ${appointmentListModel!.duration ?? ""} ${appointmentListModel!.appointmentDate!.toFormattedDateWithoutTime()}" "${isForProvider ? LocaleKeys.apptOn.tr() : ""} ${appointmentListModel!.duration ?? ""} ${appointmentListModel!.appointmentDate!.toFormattedDateWithoutTime()}"
.toText(color: MyColors.lightTextColor, fontSize: 12, letterSpacing: -0.48, height: 19 / 12), .toText(color: MyColors.lightTextColor, fontSize: 12, letterSpacing: -0.48, height: 19 / 12),
], ],
), ),
@ -291,7 +291,7 @@ class AppointmentFragmentTileWidget extends StatelessWidget {
(appointmentListModel!.providerName ?? "").toText(color: MyColors.darkTextColor, fontSize: 16, letterSpacing: -0.64, height: 24 / 16), (appointmentListModel!.providerName ?? "").toText(color: MyColors.darkTextColor, fontSize: 16, letterSpacing: -0.64, height: 24 / 16),
Row( Row(
children: [ children: [
('Branch:').toText(color: MyColors.greyColor, fontSize: 12, letterSpacing: -0.48, height: 18 / 12), ("${LocaleKeys.branch.tr()}:").toText(color: MyColors.greyColor, fontSize: 12, letterSpacing: -0.48, height: 18 / 12),
2.width, 2.width,
(appointmentListModel!.branchName ?? "").toText(color: MyColors.darkTextColor, fontSize: 12, letterSpacing: -0.48, height: 18 / 12), (appointmentListModel!.branchName ?? "").toText(color: MyColors.darkTextColor, fontSize: 12, letterSpacing: -0.48, height: 18 / 12),
], ],

@ -20,6 +20,7 @@ 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/app_bar.dart';
import 'package:mc_common_app/widgets/common_widgets/filters_list.dart'; import 'package:mc_common_app/widgets/common_widgets/filters_list.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:mc_common_app/widgets/shimmer/home_shimmer_widgets.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class AdsFragment extends StatelessWidget { class AdsFragment extends StatelessWidget {
@ -147,7 +148,7 @@ class AdsFragment extends StatelessWidget {
} }
}, },
child: adVM.state == ViewState.busy child: adVM.state == ViewState.busy
? const Center(child: CircularProgressIndicator()) ? const AdsListShimmer(itemCount: 5)
: AdsListWidget( : AdsListWidget(
isAdsFragment: true, isAdsFragment: true,
isDraftAds: false, isDraftAds: false,

@ -19,6 +19,7 @@ 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/app_bar.dart';
import 'package:mc_common_app/widgets/common_widgets/filters_list.dart'; import 'package:mc_common_app/widgets/common_widgets/filters_list.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:mc_common_app/widgets/shimmer/home_shimmer_widgets.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -124,12 +125,22 @@ class MyRequestsFragment extends StatelessWidget {
requestsVM.applyFilterOnRequestsVM(requestsTypeEnum: selectedFilterId.toRequestTypeEnum()); requestsVM.applyFilterOnRequestsVM(requestsTypeEnum: selectedFilterId.toRequestTypeEnum());
}, },
), ),
// Show condition filters for all request types
if (requestsVM.sparePartsConditionFilterOptions.isNotEmpty) ...[
8.height,
FiltersList(
filterList: requestsVM.sparePartsConditionFilterOptions,
onFilterTapped: (index, selectedFilterId) {
requestsVM.applySparePartsConditionFilter(conditionId: selectedFilterId);
},
),
],
8.height, 8.height,
Expanded( Expanded(
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () async => await requestsVM.getRequestsBasedOnFilters(), onRefresh: () async => await requestsVM.getRequestsBasedOnFilters(),
child: requestsVM.state == ViewState.busy child: requestsVM.state == ViewState.busy
? const Center(child: CircularProgressIndicator()) ? const RequestsListShimmer(itemCount: 5)
: requestsVM.myFilteredRequests.isEmpty : requestsVM.myFilteredRequests.isEmpty
? Column( ? Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,

@ -1,3 +1,4 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/classes/consts.dart';
import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/int_extensions.dart';
@ -12,73 +13,56 @@ import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
class PaymentMethodsView extends StatelessWidget { class PaymentMethodsView extends StatefulWidget {
final PaymentTypes paymentType; final PaymentTypes paymentType;
final Function? onTempContinue; final Function? onTempContinue;
const PaymentMethodsView({Key? key, required this.paymentType, this.onTempContinue}) : super(key: key); const PaymentMethodsView({Key? key, required this.paymentType, this.onTempContinue}) : super(key: key);
@override
State<PaymentMethodsView> createState() => _PaymentMethodsViewState();
}
class _PaymentMethodsViewState extends State<PaymentMethodsView> {
@override
void initState() {
super.initState();
// Automatically trigger payment flow when screen opens
scheduleMicrotask(() async {
// Show loading for 2 seconds so user can see it
await Future.delayed(const Duration(seconds: 2));
bool isNeedToCallPayment = true;
if (widget.onTempContinue != null) {
isNeedToCallPayment = await widget.onTempContinue!();
}
// Pop the loading screen BEFORE calling payment to avoid context issues
if (mounted) {
Navigator.of(context).pop();
}
if (isNeedToCallPayment && mounted) {
// Call payment which will open webview (context still works for PaymentVM)
await context.read<PaymentVM>().onVisaCardSelected(context, widget.paymentType);
}
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: CustomAppBar( body: Center(
title: LocaleKeys.paymentMethod.tr(),
profileImageUrl: MyAssets.bnCar,
isRemoveBackButton: false,
isDrawerEnabled: false,
),
body: Container(
padding: const EdgeInsets.only(bottom: 10, left: 21, right: 21),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
20.height, const CircularProgressIndicator(
LocaleKeys.selectPaymentMethod.tr().toText(fontSize: 18, isBold: true), valueColor: AlwaysStoppedAnimation<Color>(MyColors.darkPrimaryColor),
15.height,
Consumer(
builder: (BuildContext context, PaymentVM paymentVm, Widget? child) {
return Expanded(
child: ListView(
shrinkWrap: true,
children: [
PaymentMethodContainer(
isSelected: paymentVm.selectedPaymentMethod == PaymentMethods.mada, onTap: () => paymentVm.updateSelectedPaymentMethod(PaymentMethods.mada), cardAsset: MyAssets.madaPng),
PaymentMethodContainer(
isSelected: paymentVm.selectedPaymentMethod == PaymentMethods.visa, onTap: () => paymentVm.updateSelectedPaymentMethod(PaymentMethods.visa), cardAsset: MyAssets.visaPng),
PaymentMethodContainer(
isSelected: paymentVm.selectedPaymentMethod == PaymentMethods.applePay,
onTap: () => paymentVm.updateSelectedPaymentMethod(PaymentMethods.applePay),
cardAsset: MyAssets.applePayPng),
PaymentMethodContainer(
isSelected: paymentVm.selectedPaymentMethod == PaymentMethods.masterCard,
onTap: () => paymentVm.updateSelectedPaymentMethod(PaymentMethods.masterCard),
cardAsset: MyAssets.mastercardPng),
PaymentMethodContainer(
isSelected: paymentVm.selectedPaymentMethod == PaymentMethods.tamara,
onTap: () => paymentVm.updateSelectedPaymentMethod(PaymentMethods.tamara),
cardAsset: MyAssets.tamaraEngPng),
],
),
);
},
), ),
SizedBox( 24.height,
width: double.infinity, LocaleKeys.pleaseWait.tr().toText(
child: ShowFillButton( fontSize: 16,
maxHeight: 55, color: MyColors.darkTextColor,
title: LocaleKeys.continu.tr(),
onPressed: () async {
bool isNeedToCallPayment = true;
if (onTempContinue != null) {
isNeedToCallPayment = await onTempContinue!();
}
if (isNeedToCallPayment) {
context.read<PaymentVM>().onContinuePressed(context, paymentType: paymentType);
}
},
backgroundColor: MyColors.darkPrimaryColor,
),
), ),
], ],
), ),

@ -88,9 +88,13 @@ class CreateRequestPage extends StatelessWidget {
), ),
] else ...[ ] else ...[
Builder(builder: (context) { Builder(builder: (context) {
bool isArabic = AppState().isArabic(context);
List<DropValue> vehicleTypeDrop = []; List<DropValue> vehicleTypeDrop = [];
for (var element in requestsVM.vehicleTypes) { for (var element in requestsVM.vehicleTypes) {
vehicleTypeDrop.add(DropValue(element.id?.toInt() ?? 0, element.vehicleTypeName ?? "", "")); vehicleTypeDrop.add(DropValue(element.id?.toInt() ?? 0,
isArabic
? element.vehicleTypeNameN ?? ""
: element.vehicleTypeName ?? "", ""));
} }
return DropdownField( return DropdownField(
(DropValue value) => requestsVM.updateSelectionVehicleTypeId(SelectionModel(selectedId: value.id, selectedOption: value.value)), (DropValue value) => requestsVM.updateSelectionVehicleTypeId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
@ -155,9 +159,13 @@ class CreateRequestPage extends StatelessWidget {
), ),
8.height, 8.height,
Builder(builder: (context) { Builder(builder: (context) {
bool isArabic = AppState().isArabic(context);
List<DropValue> vehicleCountriesDrop = []; List<DropValue> vehicleCountriesDrop = [];
for (var element in requestsVM.vehicleCountries) { for (var element in requestsVM.vehicleCountries) {
vehicleCountriesDrop.add(DropValue(element.id?.toInt() ?? 0, element.countryName ?? "", "")); vehicleCountriesDrop.add(DropValue(element.id?.toInt() ?? 0,
isArabic
? element.countryNameN ?? ""
: element.countryName ?? "", ""));
} }
return DropdownField( return DropdownField(
(DropValue value) => requestsVM.updateSelectionVehicleCountryId(SelectionModel(selectedOption: value.value, selectedId: value.id)), (DropValue value) => requestsVM.updateSelectionVehicleCountryId(SelectionModel(selectedOption: value.value, selectedId: value.id)),
@ -177,9 +185,12 @@ class CreateRequestPage extends StatelessWidget {
] else ...[ ] else ...[
8.height, 8.height,
Builder(builder: (context) { Builder(builder: (context) {
bool isArabic = AppState().isArabic(context);
List<DropValue> vehicleCitiesDrop = []; List<DropValue> vehicleCitiesDrop = [];
for (var element in requestsVM.vehicleCities) { for (var element in requestsVM.vehicleCities) {
vehicleCitiesDrop.add(DropValue(element.id?.toInt() ?? 0, element.cityName ?? "", "")); vehicleCitiesDrop.add(DropValue(element.id?.toInt() ?? 0, isArabic
? element.cityNameN ?? ""
: element.cityName ?? "", ""));
} }
return DropdownField( return DropdownField(
(DropValue value) => requestsVM.updateSelectionVehicleCityId(SelectionModel(selectedId: value.id, selectedOption: value.value)), (DropValue value) => requestsVM.updateSelectionVehicleCityId(SelectionModel(selectedId: value.id, selectedOption: value.value)),

@ -29,6 +29,7 @@ class RequestDetailPage extends StatelessWidget {
const RequestDetailPage({super.key, required this.requestDetailPageArguments}); const RequestDetailPage({super.key, required this.requestDetailPageArguments});
Widget buildRequestDetailsContainer(BuildContext context) { Widget buildRequestDetailsContainer(BuildContext context) {
bool isArabic = AppState().isArabic(context);
final requestDetail = requestDetailPageArguments.requestModel; final requestDetail = requestDetailPageArguments.requestModel;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -42,7 +43,7 @@ class RequestDetailPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
requestDetail.vehicleTypeName.toText(fontSize: 16, letterSpacing: -0.64), isArabic ? requestDetail.vehicleTypeNameN.toText(fontSize: 16, letterSpacing: -0.64) : requestDetail.vehicleTypeName.toText(fontSize: 16, letterSpacing: -0.64),
showItem("${LocaleKeys.brand.tr()}: ", requestDetail.brand), showItem("${LocaleKeys.brand.tr()}: ", requestDetail.brand),
showItem("${LocaleKeys.model.tr()}: ", requestDetail.model), showItem("${LocaleKeys.model.tr()}: ", requestDetail.model),
showItem("${LocaleKeys.year.tr()}: ", "${requestDetail.year}"), showItem("${LocaleKeys.year.tr()}: ", "${requestDetail.year}"),
@ -53,13 +54,21 @@ class RequestDetailPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
"${requestDetail.city ?? ""} ${requestDetail.countryName}".toText( "${isArabic ? requestDetail.cityNameN : requestDetail.city} "
"${isArabic ? requestDetail.countryNameN : requestDetail.countryName}"
.toText(
color: MyColors.lightTextColor, color: MyColors.lightTextColor,
), ),
// "${requestDetail.city ?? ""} ${requestDetail.countryName}".toText(
// color: MyColors.lightTextColor,
// ),
if (requestDetail.createdOn != null) ...[ if (requestDetail.createdOn != null) ...[
DateTime.parse(requestDetail.createdOn!).getTimeAgo().toText( // DateTime.parse(requestDetail.createdOn!).getTimeAgo().toText(
color: MyColors.lightTextColor, // color: MyColors.lightTextColor,
), // ),
DateTime.parse(requestDetail.createdOn!).getTimeAgo(isArabic: isArabic)
.toText(color: MyColors.lightTextColor,
),
], ],
], ],
), ),

@ -21,6 +21,7 @@ class RequestItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isArabic = AppState().isArabic(context);
String imageUrl = ""; String imageUrl = "";
if (request.requestImages != null && request.requestImages!.isNotEmpty) { if (request.requestImages != null && request.requestImages!.isNotEmpty) {
imageUrl = request.requestImages!.first.imageUrl ?? ""; imageUrl = request.requestImages!.first.imageUrl ?? "";
@ -42,9 +43,20 @@ class RequestItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (shouldShowStatuses) ...[ if (shouldShowStatuses) ...[
// Utils.statusContainerChip(
// text: request.requestStatusName.toLowerCase() == "InProgress".toLowerCase() ? "In Progress" : request.requestStatusName,
// chipColor: Utils.getChipColorByRequestStatus(request.requestStatus)),
Utils.statusContainerChip( Utils.statusContainerChip(
text: request.requestStatusName.toLowerCase() == "InProgress".toLowerCase() ? "In Progress" : request.requestStatusName, text: isArabic
chipColor: Utils.getChipColorByRequestStatus(request.requestStatus)), ? (request.requestStatusNameN?.isNotEmpty == true
? request.requestStatusNameN!
: request.requestStatusName)
: (request.requestStatusName.toLowerCase() == "inprogress"
? "In Progress"
: request.requestStatusName),
chipColor:
Utils.getChipColorByRequestStatus(request.requestStatus),
),
], ],
6.height, 6.height,
"${request.brand} ${request.model} | ${request.id}".toText(fontSize: 16, letterSpacing: -0.64), "${request.brand} ${request.model} | ${request.id}".toText(fontSize: 16, letterSpacing: -0.64),
@ -61,7 +73,7 @@ class RequestItem extends StatelessWidget {
showItem("${LocaleKeys.year.tr()}:", "${request.year}"), showItem("${LocaleKeys.year.tr()}:", "${request.year}"),
], ],
if (request.vehicleTypeName.isNotEmpty) ...[ if (request.vehicleTypeName.isNotEmpty) ...[
showItem("${LocaleKeys.vehicleType.tr()}:", request.vehicleTypeName), showItem("${LocaleKeys.vehicleType.tr()}:", isArabic ? request.vehicleTypeNameN :request.vehicleTypeName),
], ],
showItem("${LocaleKeys.description.tr()}:", request.description), showItem("${LocaleKeys.description.tr()}:", request.description),
], ],
@ -86,9 +98,21 @@ class RequestItem extends StatelessWidget {
), ),
], ],
2.height, 2.height,
request.cityName.toText(color: MyColors.lightTextColor, letterSpacing: -0.4, fontSize: 10), // request.cityName.toText(color: MyColors.lightTextColor, letterSpacing: -0.4, fontSize: 10),
// if (request.createdOn != null) ...[
// DateTime.parse(request.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor, letterSpacing: -0.4, fontSize: 10),
// ],
(isArabic
? (request.cityNameN?.isNotEmpty == true ? request.cityNameN! : request.cityName) : request.cityName).toText(color: MyColors.lightTextColor,
letterSpacing: -0.4,
fontSize: 10,
),
if (request.createdOn != null) ...[ if (request.createdOn != null) ...[
DateTime.parse(request.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor, letterSpacing: -0.4, fontSize: 10), DateTime.parse(request.createdOn!).getTimeAgo(isArabic: isArabic).toText(
color: MyColors.lightTextColor,
letterSpacing: -0.4,
fontSize: 10,
),
], ],
], ],
) )

@ -14,6 +14,8 @@ import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../classes/app_state.dart';
class SettingOptionsFAQs extends StatefulWidget { class SettingOptionsFAQs extends StatefulWidget {
const SettingOptionsFAQs({super.key}); const SettingOptionsFAQs({super.key});
@ -31,6 +33,7 @@ class _SettingOptionsFAQsState extends State<SettingOptionsFAQs> {
await settingsOptionsVM.getAllFaqs(); await settingsOptionsVM.getAllFaqs();
}); });
super.initState(); super.initState();
} }
@override @override
@ -44,6 +47,7 @@ class _SettingOptionsFAQsState extends State<SettingOptionsFAQs> {
), ),
body: Consumer( body: Consumer(
builder: (BuildContext context, SettingOptionsVM settingsOptionsVM, Widget? child) { builder: (BuildContext context, SettingOptionsVM settingsOptionsVM, Widget? child) {
bool isArabic = AppState().isArabic(context);
return Container( return Container(
color: MyColors.backgroundColor, color: MyColors.backgroundColor,
width: double.infinity, width: double.infinity,
@ -69,7 +73,7 @@ class _SettingOptionsFAQsState extends State<SettingOptionsFAQs> {
return ExpansionTile( return ExpansionTile(
tilePadding: EdgeInsets.zero, tilePadding: EdgeInsets.zero,
shape: const Border(), shape: const Border(),
title: (faqModel.question ?? "").toText(fontSize: 16).paddingOnly(left: 8, right: 8), title: (isArabic ? faqModel.questionN ?? "" : faqModel.question ?? "").toText(fontSize: 16).paddingOnly(left: 8, right: 8),
onExpansionChanged: (value) { onExpansionChanged: (value) {
setState(() { setState(() {
settingsOptionsVM.faqsList[index].isCollapsed = value; settingsOptionsVM.faqsList[index].isCollapsed = value;
@ -85,7 +89,7 @@ class _SettingOptionsFAQsState extends State<SettingOptionsFAQs> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
5.height, 5.height,
(faqModel.answer ?? "").toText(color: MyColors.lightTextColor, fontSize: 14, fontWeight: MyFonts.Medium), (isArabic ? faqModel.answerN ?? "" : faqModel.answer ?? "").toText(color: MyColors.lightTextColor, fontSize: 14, fontWeight: MyFonts.Medium),
10.height, 10.height,
], ],
).paddingOnly(left: 8, right: 8) ).paddingOnly(left: 8, right: 8)

@ -83,9 +83,9 @@ class _SettingOptionsMoreState extends State<SettingOptionsMore> {
if (AppState().currentAppType == AppType.customer) ...[ if (AppState().currentAppType == AppType.customer) ...[
CustomSettingOptionsTile( CustomSettingOptionsTile(
leadingWidget: const Icon(Icons.favorite, size: 20), leadingWidget: const Icon(Icons.favorite, size: 20),
titleText: LocaleKeys.favoriteList.tr(), titleText: LocaleKeys.myFavouriteProviders.tr(),
needBorderBelow: true, needBorderBelow: true,
onTap: () => navigateWithName(context, AppRoutes.favoriteListView), onTap: () => navigateWithName(context, AppRoutes.favoriteProvidersView),
), ),
] else ...[ ] else ...[
CustomSettingOptionsTile( CustomSettingOptionsTile(
@ -112,6 +112,12 @@ class _SettingOptionsMoreState extends State<SettingOptionsMore> {
navigateWithName(context, AppRoutes.loginWithPassword, arguments: false); navigateWithName(context, AppRoutes.loginWithPassword, arguments: false);
}), }),
], ],
CustomSettingOptionsTile(
leadingWidget: const Icon(Icons.favorite_border, size: 20),
titleText: LocaleKeys.myFavouriteAds.tr(),
needBorderBelow: true,
onTap: () => navigateWithName(context, AppRoutes.favouriteAdsView),
),
CustomSettingOptionsTile( CustomSettingOptionsTile(
leadingWidget: SvgPicture.asset( leadingWidget: SvgPicture.asset(
MyAssets.icAds, MyAssets.icAds,
@ -128,11 +134,25 @@ class _SettingOptionsMoreState extends State<SettingOptionsMore> {
CustomSettingOptionsTile( CustomSettingOptionsTile(
leadingWidget: const Icon(Icons.settings, size: 20), leadingWidget: const Icon(Icons.settings, size: 20),
titleText: LocaleKeys.settings.tr(), titleText: LocaleKeys.settings.tr(),
needBorderBelow: false, needBorderBelow: true,
onTap: () { onTap: () {
// context.read<SettingOptionsVM>().getAppInvitationHistoryList(); // context.read<SettingOptionsVM>().getAppInvitationHistoryList();
navigateWithName(context, AppRoutes.settingOptionsInviteFriends); navigateWithName(context, AppRoutes.settingOptionsInviteFriends);
}), }),
/////////////////////////////////me add to test
CustomSettingOptionsTile(
leadingWidget: const Icon(Icons.translate, size: 20),
titleText: LocaleKeys.language.tr(),
isForLanguage: true,
onTap: () {
context.read<UserVM>().changeLanguage(context);
Future.delayed(const Duration(seconds: 1), () {
setState(() {});
});
},
),
//////////////////////////////////////////////////////////
], ],
).toWhiteContainer(width: double.infinity, pading: const EdgeInsets.all(12), borderRadius: 0), ).toWhiteContainer(width: double.infinity, pading: const EdgeInsets.all(12), borderRadius: 0),
10.height, 10.height,

@ -7,6 +7,8 @@ import 'package:mc_common_app/generated/locale_keys.g.dart';
import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import '../../../classes/app_state.dart';
class CustomSettingOptionsTile extends StatelessWidget { class CustomSettingOptionsTile extends StatelessWidget {
final Widget leadingWidget; final Widget leadingWidget;
final String titleText; final String titleText;
@ -31,6 +33,7 @@ class CustomSettingOptionsTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isArabic = AppState().isArabic(context);
return Column( return Column(
children: [ children: [
Row( Row(
@ -56,10 +59,20 @@ class CustomSettingOptionsTile extends StatelessWidget {
], ],
), ),
isForLanguage isForLanguage
? LocaleKeys.english.tr().toText(fontSize: 12, isUnderLine: true, color: MyColors.primaryColor) ? (isArabic ? "English": "عربي")
.toText(
fontSize: 12,
isUnderLine: true,
color: MyColors.primaryColor,
)
: showTrailingArrow : showTrailingArrow
? const Icon(Icons.arrow_forward, size: 18) ? const Icon(Icons.arrow_forward, size: 18)
: const SizedBox() : const SizedBox(),
// isForLanguage
// ? LocaleKeys.english.tr().toText(fontSize: 12, isUnderLine: true, color: MyColors.primaryColor)
// : showTrailingArrow
// ? const Icon(Icons.arrow_forward, size: 18)
// : const SizedBox()
], ],
).onPress(onTap), ).onPress(onTap),
subTitle != null ? 3.height : 5.height, subTitle != null ? 3.height : 5.height,

@ -15,11 +15,31 @@ class SplashPage extends StatefulWidget {
} }
class SplashPageState extends State<SplashPage> with WidgetsBindingObserver { class SplashPageState extends State<SplashPage> with WidgetsBindingObserver {
bool showFlashingLights = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
// performTimer(context); startSplashTimer();
}
void startSplashTimer() {
// After 3 seconds, show the flashing lights gif
Future.delayed(const Duration(seconds: 3), () {
if (mounted) {
setState(() {
showFlashingLights = true;
});
}
});
// After 5 seconds total, navigate to next page
Future.delayed(const Duration(seconds: 5), () {
if (mounted) {
navigateToNextDeciderPage();
}
});
} }
@override @override
@ -33,7 +53,7 @@ class SplashPageState extends State<SplashPage> with WidgetsBindingObserver {
super.didChangeAppLifecycleState(state); super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) { if (state == AppLifecycleState.resumed) {
final chatVM = context.read<ChatVM>(); final chatVM = context.read<ChatVM>();
chatVM.buildHubConnection(context); chatVM.buildHubConnection(context: context);
} }
} }
@ -68,12 +88,22 @@ class SplashPageState extends State<SplashPage> with WidgetsBindingObserver {
// Car Image (flex value adjusted to make it more responsive) // Car Image (flex value adjusted to make it more responsive)
Expanded( Expanded(
flex: 10, flex: 10,
child: Image.asset( child: showFlashingLights
MyAssets.bnCar, ? Image.asset(
fit: BoxFit.cover, MyAssets.bnCarFlashingLights,
width: double.infinity, fit: BoxFit.contain,
height: double.infinity, // Use full height in this expanded section width: double.infinity,
), height: double.infinity,
)
: Transform.scale(
scale: 1.72, // Scale down to match gif size (1144/1920 0.6)
child: Image.asset(
MyAssets.bnCar,
fit: BoxFit.contain,
width: double.infinity,
height: double.infinity,
),
),
), ),
// Engine Image // Engine Image
Expanded( Expanded(

@ -77,7 +77,7 @@ class _EditAccountPageState extends State<EditAccountPage> {
children: [ children: [
CustomSettingOptionsTile( CustomSettingOptionsTile(
leadingWidget: const Icon(Icons.quickreply_outlined, size: 20), leadingWidget: const Icon(Icons.quickreply_outlined, size: 20),
titleText: "Requests", titleText: LocaleKeys.requests.tr(),
needBorderBelow: true, needBorderBelow: true,
onTap: () { onTap: () {
context.read<DashboardVmCustomer>().onNavbarTapped(4); context.read<DashboardVmCustomer>().onNavbarTapped(4);
@ -85,19 +85,19 @@ class _EditAccountPageState extends State<EditAccountPage> {
}), }),
CustomSettingOptionsTile( CustomSettingOptionsTile(
leadingWidget: const Icon(Icons.settings, size: 20), leadingWidget: const Icon(Icons.settings, size: 20),
titleText: "Settings", titleText: LocaleKeys.settings.tr(),
needBorderBelow: true, needBorderBelow: true,
onTap: () => navigateWithName(context, AppRoutes.settingOptionsInviteFriends), onTap: () => navigateWithName(context, AppRoutes.settingOptionsInviteFriends),
), ),
CustomSettingOptionsTile( CustomSettingOptionsTile(
leadingWidget: const Icon(Icons.question_mark_outlined, size: 20), leadingWidget: const Icon(Icons.question_mark_outlined, size: 20),
titleText: "Help", titleText: LocaleKeys.help.tr(),
needBorderBelow: true, needBorderBelow: true,
onTap: () => navigateWithName(context, AppRoutes.settingOptionsInviteFriends), onTap: () => navigateWithName(context, AppRoutes.settingOptionsInviteFriends),
), ),
CustomSettingOptionsTile( CustomSettingOptionsTile(
leadingWidget: const Icon(Icons.person, size: 20), leadingWidget: const Icon(Icons.person, size: 20),
titleText: "Account", titleText: LocaleKeys.account.tr(),
needBorderBelow: false, needBorderBelow: false,
onTap: () { onTap: () {
context.read<DashboardVmCustomer>().onNavbarTapped(4); context.read<DashboardVmCustomer>().onNavbarTapped(4);
@ -108,7 +108,7 @@ class _EditAccountPageState extends State<EditAccountPage> {
10.height, 10.height,
CustomSettingOptionsTile( CustomSettingOptionsTile(
leadingWidget: const Icon(Icons.translate, size: 20), leadingWidget: const Icon(Icons.translate, size: 20),
titleText: "Language", titleText: LocaleKeys.language.tr(),
isForLanguage: true, isForLanguage: true,
onTap: () {}, onTap: () {},
).toWhiteContainer(width: double.infinity, pading: const EdgeInsets.all(12), borderRadius: 0), ).toWhiteContainer(width: double.infinity, pading: const EdgeInsets.all(12), borderRadius: 0),
@ -124,8 +124,8 @@ class _EditAccountPageState extends State<EditAccountPage> {
color: MyColors.primaryColor, color: MyColors.primaryColor,
), ),
), ),
titleText: "My Subscriptions", titleText: LocaleKeys.mySubscription.tr(),
subTitle: "Silver", subTitle: LocaleKeys.silver.tr(),
isForLanguage: false, isForLanguage: false,
needBorderBelow: true, needBorderBelow: true,
onTap: () { onTap: () {
@ -141,7 +141,7 @@ class _EditAccountPageState extends State<EditAccountPage> {
// color: MyColors.primaryColor, // color: MyColors.primaryColor,
), ),
), ),
titleText: "Subscriptions", titleText: LocaleKeys.subscriptions.tr(),
subTitle: null, subTitle: null,
isForLanguage: false, isForLanguage: false,
needBorderBelow: false, needBorderBelow: false,
@ -152,7 +152,7 @@ class _EditAccountPageState extends State<EditAccountPage> {
], ],
).toWhiteContainer(width: double.infinity, pading: const EdgeInsets.all(12), borderRadius: 0), ).toWhiteContainer(width: double.infinity, pading: const EdgeInsets.all(12), borderRadius: 0),
10.height, 10.height,
(AppState().currentAppType == AppType.provider) ? const Text("Provider") : const Text("Customer"), (AppState().currentAppType == AppType.provider) ? const Text(LocaleKeys.provider) : const Text(LocaleKeys.customer),
], ],
), ),
), ),

@ -15,6 +15,8 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../classes/app_state.dart';
class ForgetPasswordPage extends StatefulWidget { class ForgetPasswordPage extends StatefulWidget {
const ForgetPasswordPage({Key? key}) : super(key: key); const ForgetPasswordPage({Key? key}) : super(key: key);
@ -133,11 +135,19 @@ class _ForgetPasswordPageState extends State<ForgetPasswordPage> {
} }
Widget getCountry() { Widget getCountry() {
bool isArabic = AppState().isArabic(context);
if (_country != null) { if (_country != null) {
List<DropValue> dropList = []; List<DropValue> dropList = [];
_country!.data?.forEach( _country!.data?.forEach(
(element) { (element) {
dropList.add(DropValue(element.id ?? 0, "${element.countryName ?? ""} ${element.countryCode ?? ""}", element.countryCode ?? "")); // dropList.add(DropValue(element.id ?? 0, "${element.countryName ?? ""} ${element.countryCode ?? ""}", element.countryCode ?? ""));
dropList.add(
DropValue(
element.id ?? 0,
"${isArabic ? (element.countryNameN ?? element.countryName ?? "") : (element.countryName ?? "")} ${element.countryCode ?? ""}",
element.countryCode ?? "",
),
);
}, },
); );
return Padding( return Padding(

@ -107,7 +107,7 @@ class _LoginMethodSelectionPageState extends State<LoginMethodSelectionPage> {
], ],
), ),
150.height, 150.height,
("Login To Another Account") LocaleKeys.loginToAnotherAccount.tr()
.toText( .toText(
fontSize: 20, fontSize: 20,
letterSpacing: -1.44, letterSpacing: -1.44,

@ -179,15 +179,48 @@ class _LoginWithPasswordState extends State<LoginWithPassword> {
); );
} }
// Widget getCountry(BuildContext context) {
// if (_country != null) {
// List<DropValue> dropList = [];
// _country!.data?.forEach((element) {
// dropList.add(DropValue(element.id ?? 0, "${element.countryName ?? ""} ${element.countryCode ?? ""}", element.countryCode ?? ""));
// });
// return DropdownField(
// dropdownValue: tempCountry,
// (DropValue value) {
// countryCode = value.subValue;
// tempCountry = value;
// setState(() {});
// },
// list: dropList,
// hint: LocaleKeys.selectCountryCode.tr(),
// );
// } else {
// return const Center(
// child: CircularProgressIndicator(),
// );
// }
// }
Widget getCountry(BuildContext context) { Widget getCountry(BuildContext context) {
bool isArabic = AppState().isArabic(context);
if (_country != null) { if (_country != null) {
List<DropValue> dropList = []; List<DropValue> dropList = [];
_country!.data?.forEach((element) { _country!.data?.forEach((element) {
dropList.add(DropValue(element.id ?? 0, "${element.countryName ?? ""} ${element.countryCode ?? ""}", element.countryCode ?? "")); dropList.add(
DropValue(
element.id ?? 0,
"${isArabic ? (element.countryNameN ?? element.countryName ?? "") : (element.countryName ?? "")} ${element.countryCode ?? ""}",
element.countryCode ?? "",
),
);
}); });
return DropdownField( return DropdownField(
dropdownValue: tempCountry, dropdownValue: tempCountry,
(DropValue value) { (DropValue value) {
countryCode = value.subValue; countryCode = value.subValue;
tempCountry = value; tempCountry = value;
setState(() {}); setState(() {});

@ -54,14 +54,15 @@ class _UpdateUserCityCountryState extends State<UpdateUserCityCountry> {
padding: const EdgeInsets.only(right: 20, left: 20, top: 12), padding: const EdgeInsets.only(right: 20, left: 20, top: 12),
child: Builder(builder: (context) { child: Builder(builder: (context) {
List<DropValue> userCountryDrop = []; List<DropValue> userCountryDrop = [];
final isArabic = AppState().isArabic(context);
for (var element in uVM.userCountries!.data!) { for (var element in uVM.userCountries!.data!) {
var countryid = country == null ? AppState().getUser.data!.userInfo!.countryId : country!.id; var countryid = country == null ? AppState().getUser.data!.userInfo!.countryId : country!.id;
print("Country ID" + countryid.toString()); print("Country ID" + countryid.toString());
if (countryid == element.id) { if (countryid == element.id) {
print("Country Matched"); print("Country Matched");
country = DropValue(element.id?.toInt() ?? 0, element.countryName ?? "", ""); country = DropValue(element.id?.toInt() ?? 0, isArabic ? element.countryNameN ?? "": element.countryName ?? "", "");
} }
userCountryDrop.add(DropValue(element.id?.toInt() ?? 0, element.countryName ?? "", "")); userCountryDrop.add(DropValue(element.id?.toInt() ?? 0, isArabic ? element.countryNameN ?? "": element.countryName ?? "", ""));
} }
return DropdownField( return DropdownField(
(DropValue value) async { (DropValue value) async {
@ -82,13 +83,14 @@ class _UpdateUserCityCountryState extends State<UpdateUserCityCountry> {
padding: const EdgeInsets.only(right: 20, left: 20, top: 12), padding: const EdgeInsets.only(right: 20, left: 20, top: 12),
child: Builder(builder: (context) { child: Builder(builder: (context) {
List<DropValue> userCityDrop = []; List<DropValue> userCityDrop = [];
final isArabic = AppState().isArabic(context);
for (var element in uVM.userCities!.data!) { for (var element in uVM.userCities!.data!) {
var cid = city == null ? AppState().getUser.data!.userInfo!.cityId : city!.id; var cid = city == null ? AppState().getUser.data!.userInfo!.cityId : city!.id;
print("City ID" + cid.toString()); print("City ID" + cid.toString());
if (cid == element.id) { if (cid == element.id) {
city = DropValue(element.id?.toInt() ?? 0, element.cityName ?? "", ""); city = DropValue(element.id?.toInt() ?? 0, isArabic ? element.cityNameN ?? "": element.cityName ?? "", "");
} }
userCityDrop.add(DropValue(element.id?.toInt() ?? 0, element.cityName ?? "", "")); userCityDrop.add(DropValue(element.id?.toInt() ?? 0, isArabic ? element.cityNameN ?? "": element.cityName ?? "", ""));
} }
return DropdownField( return DropdownField(
(DropValue value) { (DropValue value) {

@ -3,7 +3,6 @@ import 'dart:async';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/app_state.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/int_extensions.dart';
import 'package:mc_common_app/extensions/string_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/generated/locale_keys.g.dart';
@ -51,72 +50,74 @@ class _AddPhoneNumWidgetState extends State<AddPhoneNumWidget> {
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Container( child: Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16), padding: const EdgeInsets.all(20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
"Enter Phone Number".toText(fontSize: 16, isBold: true), LocaleKeys.enterPhoneNumber.tr().toText(fontSize: 20, isBold: true, letterSpacing: -1.44),
8.height, 30.height,
Container( if (_country != null) ...[
decoration: Utils.containerColorRadiusBorderWidth(MyColors.white, 0, MyColors.darkPrimaryColor, 2), DropdownField(
margin: const EdgeInsets.all(0), dropdownValue: countryCodeVal,
padding: const EdgeInsets.only(left: 8, right: 8), (DropValue value) {
width: null, AppState().setUserRegisterCountrySelection = value;
child: Row( countryCode = value.subValue;
children: [ countryCodeVal = value;
if (countryCode.isEmpty) ...[ setState(() {});
LocaleKeys.selectCountryCode.tr().toText(color: borderColor, fontSize: 15, fontWeight: MyFonts.Medium).paddingOnly(top: 12, bottom: 12), },
] else ...[ list: _country!.data?.map((element) {
countryCode.toText(fontSize: 15, fontWeight: MyFonts.Medium).paddingOnly(top: 12, bottom: 12), return DropValue(
] element.id ?? 0,
], "${element.countryName ?? ""} ${element.countryCode ?? ""}",
element.countryCode ?? "",
);
}).toList() ?? [],
hint: LocaleKeys.selectCountryCode.tr(),
), ),
).onPress(() { 10.height,
if (_country != null) { TxtField(
getCountry(context); keyboardType: TextInputType.phone,
} hint: LocaleKeys.enterPhoneNumber.tr(),
}), value: phoneNum,
8.height, isSidePaddingZero: true,
TxtField( onChanged: (v) {
keyboardType: TextInputType.phone, // Remove country code if user enters it
hint: "Phone without country code ", String cleanedPhone = v.trim();
value: phoneNum,
isSidePaddingZero: true, // If country code is selected and phone starts with it, remove it
onChanged: (v) { if (countryCode.isNotEmpty && cleanedPhone.startsWith(countryCode)) {
// Remove country code if user enters it cleanedPhone = cleanedPhone.substring(countryCode.length).trim();
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 // Also handle cases where user adds + before country code
if (countryCode.isNotEmpty && cleanedPhone.startsWith('+$countryCode')) { if (countryCode.isNotEmpty && cleanedPhone.startsWith('+$countryCode')) {
cleanedPhone = cleanedPhone.substring(countryCode.length + 1).trim(); cleanedPhone = cleanedPhone.substring(countryCode.length + 1).trim();
} }
// Handle case where user just types + followed by digits matching country code // Handle case where user just types + followed by digits matching country code
if (countryCode.isNotEmpty && cleanedPhone.startsWith('+')) { if (countryCode.isNotEmpty && cleanedPhone.startsWith('+')) {
String codeWithoutPlus = countryCode.replaceAll('+', ''); String codeWithoutPlus = countryCode.replaceAll('+', '');
if (cleanedPhone.substring(1).startsWith(codeWithoutPlus)) { if (cleanedPhone.substring(1).startsWith(codeWithoutPlus)) {
cleanedPhone = cleanedPhone.substring(codeWithoutPlus.length + 1).trim(); cleanedPhone = cleanedPhone.substring(codeWithoutPlus.length + 1).trim();
}
} }
}
setState(() { setState(() {
phoneNum = cleanedPhone; phoneNum = cleanedPhone;
}); });
}, },
), ),
16.height, ] else ...[
const Center(child: CircularProgressIndicator()),
],
30.height,
Row( Row(
children: [ children: [
Expanded( Expanded(
child: ShowFillButton( child: ShowFillButton(
title: "Cancel", title: LocaleKeys.cancel.tr(),
backgroundColor: MyColors.greyButtonColor, backgroundColor: MyColors.greyButtonColor,
txtColor: Colors.black, txtColor: Colors.black,
onPressed: () { onPressed: () {
@ -147,22 +148,6 @@ class _AddPhoneNumWidgetState extends State<AddPhoneNumWidget> {
); );
} }
void getCountry(BuildContext context) {
List<DropValue> dropList = [];
_country!.data?.forEach((element) {
dropList.add(DropValue(element.id ?? 0, "${element.countryName ?? ""} ${element.countryCode ?? ""}", element.countryCode ?? ""));
});
return showCustomBottomPicker(
items: dropList,
onItemSelected: (value) {
AppState().setUserRegisterCountrySelection = value;
countryCode = value.subValue;
countryCodeVal = value;
setState(() {});
},
context: context);
}
bool validation() { bool validation() {
bool isValid = true; bool isValid = true;
if (countryCode.isEmpty) { if (countryCode.isEmpty) {
@ -177,73 +162,3 @@ class _AddPhoneNumWidgetState extends State<AddPhoneNumWidget> {
return isValid; return isValid;
} }
} }
class CustomBottomPicker extends StatelessWidget {
final List<DropValue> items;
final void Function(DropValue selectedItem) onItemSelected;
const CustomBottomPicker({
super.key,
required this.items,
required this.onItemSelected,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
color: Colors.black.withOpacity(0.4),
child: DraggableScrollableSheet(
initialChildSize: 0.2,
maxChildSize: 0.4,
minChildSize: 0.2,
expand: true,
builder: (context, scrollController) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: ListView.builder(
controller: scrollController,
itemCount: items.length,
shrinkWrap: true,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].value),
onTap: () {
onItemSelected(items[index]);
Navigator.of(context).pop();
},
);
},
),
);
},
),
),
);
}
}
// Function to show the bottom picker
void showCustomBottomPicker({
required BuildContext context,
required List<DropValue> items,
required void Function(DropValue selectedItem) onItemSelected,
}) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: false,
backgroundColor: Colors.transparent,
builder: (context) {
return CustomBottomPicker(
items: items,
onItemSelected: onItemSelected,
);
},
);
}

@ -95,24 +95,47 @@ class BranchDetailCard extends StatelessWidget {
], ],
), ),
8.height, 8.height,
// if (services != null)
// Column(
// children: services!
// .take(2)
// .map(
// (e) => Row(
// children: [
// //TODO: Needs to add icon in the future when added from the provider
// // MyAssets.maintenanceIcon.buildSvg(),
// // 8.width,
// e.serviceDescription.toString().toText(fontSize: 12)
// ],
// ),
// )
// .toList(),
// ),
if (services != null) if (services != null)
Column( Column(
children: services! children: services!
.take(2) .take(2)
.map( .map(
(e) => Row( (e) {
children: [ bool isArabic = AppState().isArabic(context);
//TODO: Needs to add icon in the future when added from the provider String serviceText = isArabic
// MyAssets.maintenanceIcon.buildSvg(), ? e.serviceDescriptionN.toString()
// 8.width, : e.serviceDescription.toString();
e.serviceDescription.toString().toText(fontSize: 12)
], return Row(
), children: [
) //TODO: Needs to add icon in the future when added from the provider
// MyAssets.maintenanceIcon.buildSvg(),
// 8.width,
serviceText.toText(fontSize: 12)
],
);
},
)
.toList(), .toList(),
), ),
if (services != null && services!.length > 2) if (services != null && services!.length > 2)
("+${services!.length - 2} more").toText( ("+${services!.length - 2} ${LocaleKeys.more.tr()}").toText(
color: MyColors.primaryColor, color: MyColors.primaryColor,
isUnderLine: true, isUnderLine: true,
isBold: true, isBold: true,

@ -1,11 +1,14 @@
import 'dart:developer'; import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart';
import 'package:mc_common_app/models/general_models/widgets_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/theme/colors.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import '../../classes/app_state.dart';
class FiltersList extends StatelessWidget { class FiltersList extends StatelessWidget {
final List<FilterListModel> filterList; final List<FilterListModel> filterList;
final Function(int, int) onFilterTapped; final Function(int, int) onFilterTapped;
@ -24,6 +27,7 @@ class FiltersList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isArabic = AppState().isArabic(context);
return SizedBox( return SizedBox(
height: 37, height: 37,
width: double.infinity, width: double.infinity,
@ -32,6 +36,8 @@ class FiltersList extends StatelessWidget {
itemCount: filterList.length, itemCount: filterList.length,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
print("==================================================");
print(filterList[index].title);
return InkWell( return InkWell(
onTap: () { onTap: () {
onFilterTapped(index, filterList[index].id); onFilterTapped(index, filterList[index].id);
@ -62,11 +68,22 @@ class FiltersList extends StatelessWidget {
], ],
], ],
// Text displayed in the chip // Text displayed in the chip
filterList[index].title.toText( (isArabic
fontSize: 13, ? (filterList[index].titleN.isNotEmpty ? filterList[index].titleN : filterList[index].title) : filterList[index].title).toText(
letterSpacing: -0.14, fontSize: 13,
color: filterList[index].isSelected ? MyColors.white : null, letterSpacing: -0.14,
), color: filterList[index].isSelected ? MyColors.white : null,
)
// (isArabic
// ? (filterList[index].titleN ?? filterList[index].title)
// : filterList[index].title)
// .tr()
// .toText(
// // filterList[index].title.tr().toText(
// fontSize: 13,
// letterSpacing: -0.14,
// color: filterList[index].isSelected ? MyColors.white : null,
// ),
], ],
), ),
), ),

@ -46,44 +46,44 @@ class ProviderDetailCard extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
title.toText(fontSize: 16, isBold: true), title.toText(fontSize: 16, isBold: true),
Row( Row(
children: [ children: [
("${LocaleKeys.totalBranches.tr()}:").toText(color: MyColors.lightTextColor, fontSize: 12), ("${LocaleKeys.totalBranches.tr()}:").toText(color: MyColors.lightTextColor, fontSize: 12),
4.width, 4.width,
totalBranches.toText(fontSize: 12, isBold: true), totalBranches.toText(fontSize: 12, isBold: true),
], ],
), ),
if (description.isNotEmpty) ...[ if (description.isNotEmpty) ...[
Row( Row(
children: [ children: [
("${LocaleKeys.description.tr()}:").toText(color: MyColors.lightTextColor, fontSize: 12), ("${LocaleKeys.description.tr()}:").toText(color: MyColors.lightTextColor, fontSize: 12),
4.width, 4.width,
description.toText(fontSize: 12, isBold: true), description.toText(fontSize: 12, isBold: true),
], ],
), ),
], ],
Row( Row(
children: [ children: [
(isFromFavoriteList ? LocaleKeys.addToFavoritesOn.tr() : LocaleKeys.memberSince.tr()).toText(color: MyColors.lightTextColor, fontSize: 12), (isFromFavoriteList ? LocaleKeys.addToFavoritesOn.tr() : LocaleKeys.memberSince.tr()).toText(color: MyColors.lightTextColor, fontSize: 12),
4.width, 4.width,
startedSince.toText(fontSize: 12, isBold: true), startedSince.toText(fontSize: 12, isBold: true),
], ],
), ),
], ],
), ),
), ),
], ],
), ),
], ],
), ),
), ),

@ -75,12 +75,7 @@ class _DropdownFieldState extends State<DropdownField> {
// Show disabled text field when not selectable // Show disabled text field when not selectable
if (!widget.isSelectAble) if (!widget.isSelectAble)
Container( Container(
decoration: Utils.containerColorRadiusBorderWidth( decoration: Utils.containerColorRadiusBorderWidth(MyColors.greyShadowColor, 0, MyColors.greyACColor, 2),
MyColors.greyShadowColor,
0,
MyColors.greyACColor,
2
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
width: double.infinity, width: double.infinity,
child: (dropdownValue?.value ?? "").toText( child: (dropdownValue?.value ?? "").toText(
@ -90,16 +85,9 @@ class _DropdownFieldState extends State<DropdownField> {
), ),
) )
else else
// Show normal dropdown when selectable // Show normal dropdown when selectable
Container( Container(
decoration: widget.showAppointmentPickerVariant decoration: widget.showAppointmentPickerVariant ? null : Utils.containerColorRadiusBorderWidth(MyColors.white, 0, MyColors.darkPrimaryColor, 2),
? null
: Utils.containerColorRadiusBorderWidth(
MyColors.white,
0,
MyColors.darkPrimaryColor,
2
),
margin: const EdgeInsets.all(0), margin: const EdgeInsets.all(0),
padding: const EdgeInsets.only(left: 8, right: 8), padding: const EdgeInsets.only(left: 8, right: 8),
width: widget.showAppointmentPickerVariant ? 170 : null, width: widget.showAppointmentPickerVariant ? 170 : null,
@ -127,15 +115,11 @@ class _DropdownFieldState extends State<DropdownField> {
}, },
onTap: widget.onTap, onTap: widget.onTap,
items: (widget.list ?? defaultV).map<DropdownMenuItem<DropValue>>( items: (widget.list ?? defaultV).map<DropdownMenuItem<DropValue>>(
(DropValue value) { (DropValue value) {
return DropdownMenuItem<DropValue>( return DropdownMenuItem<DropValue>(
value: value, value: value,
enabled: value.isEnabled ?? true, enabled: value.isEnabled ?? true,
child: value.value.toText( child: value.value.toText(fontSize: 15, color: value.isEnabled == false ? MyColors.darkTextColor : null, fontWeight: MyFonts.Medium),
fontSize: 15,
color: value.isEnabled == false ? MyColors.darkTextColor : null,
fontWeight: MyFonts.Medium
),
); );
}, },
).toList(), ).toList(),

@ -371,7 +371,10 @@ extension BuildSVG on String? {
return SizedBox( return SizedBox(
height: height, height: height,
width: width, width: width,
child: Image.asset(MyAssets.brokenImage).paddingAll(10), child: Image.asset(
MyAssets.brokenImage,
fit: BoxFit.fill,
),
); );
} }
@ -382,7 +385,11 @@ extension BuildSVG on String? {
height: height, height: height,
width: width, width: width,
decoration: BoxDecoration(border: Border.all(color: MyColors.roundedCrossBgColor)), decoration: BoxDecoration(border: Border.all(color: MyColors.roundedCrossBgColor)),
child: const Icon(Icons.image_not_supported_outlined, color: MyColors.roundedCrossBgColor), child: Icon(
Icons.image_not_supported_outlined,
color: MyColors.roundedCrossBgColor,
size: width,
),
); );
}, },
loadingBuilder: (BuildContext context, Widget? child, ImageChunkEvent? imageChunk) { loadingBuilder: (BuildContext context, Widget? child, ImageChunkEvent? imageChunk) {

@ -0,0 +1,228 @@
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
/// Shimmer for horizontal appointment slider
class AppointmentSliderShimmer extends StatelessWidget {
const AppointmentSliderShimmer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
height: 180,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 3,
padding: const EdgeInsets.symmetric(horizontal: 21),
itemBuilder: (context, index) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
width: MediaQuery.of(context).size.width * 0.85,
margin: EdgeInsets.only(right: index < 2 ? 12 : 0),
decoration: const BoxDecoration(
color: Colors.white,
),
),
);
},
),
);
}
}
/// Shimmer for appointments list (vertical list of appointment cards)
class AppointmentsListShimmer extends StatelessWidget {
final int itemCount;
const AppointmentsListShimmer({super.key, this.itemCount = 5});
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 21),
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemCount,
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemBuilder: (context, index) {
return Container(
height: 140,
decoration: const BoxDecoration(
color: Colors.white,
),
);
},
),
),
);
}
}
/// Shimmer for branches list (vertical list of branch cards)
class BranchesListShimmer extends StatelessWidget {
final int itemCount;
const BranchesListShimmer({Key? key, this.itemCount = 5}) : super(key: key);
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Padding(
padding: const EdgeInsets.all(12),
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemCount,
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemBuilder: (context, index) {
return Container(
height: 160,
decoration: const BoxDecoration(
color: Colors.white,
),
);
},
),
),
);
}
}
/// Shimmer for requests list (vertical list of request cards)
class RequestsListShimmer extends StatelessWidget {
final int itemCount;
const RequestsListShimmer({Key? key, this.itemCount = 5}) : super(key: key);
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 21),
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemCount,
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemBuilder: (context, index) {
return Container(
height: 120,
decoration: const BoxDecoration(
color: Colors.white,
),
);
},
),
),
);
}
}
/// Shimmer for services grid (3 columns)
class ServicesGridShimmer extends StatelessWidget {
const ServicesGridShimmer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 21),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.9,
),
itemCount: 6,
itemBuilder: (context, index) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
),
);
},
),
),
);
}
}
/// Shimmer for ads list
class AdsListShimmer extends StatelessWidget {
final int itemCount;
const AdsListShimmer({Key? key, this.itemCount = 3}) : super(key: key);
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 21),
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemCount,
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemBuilder: (context, index) {
return Container(
height: 140,
decoration: const BoxDecoration(
color: Colors.white,
),
);
},
),
),
);
}
}
/// Shimmer for provider branches grid
class BranchesGridShimmer extends StatelessWidget {
const BranchesGridShimmer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 21),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.75,
),
itemCount: 4,
itemBuilder: (context, index) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
),
);
},
),
),
);
}
}

File diff suppressed because it is too large Load Diff

@ -43,6 +43,7 @@ dependencies:
change_app_package_name: ^1.3.0 change_app_package_name: ^1.3.0
share_plus: any share_plus: any
toastification: ^2.3.0 toastification: ^2.3.0
geolocator_android: 4.5.5
# Firebase Packages # Firebase Packages

Loading…
Cancel
Save