diff --git a/assets/images/bn_car_flashing_lights.gif b/assets/images/bn_car_flashing_lights.gif new file mode 100644 index 0000000..9a076f5 Binary files /dev/null and b/assets/images/bn_car_flashing_lights.gif differ diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index eb03dca..a7bf966 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -254,6 +254,9 @@ "sendOTP": "إرسال رمز OTP", "noUserAddedYet": "لم يتم إضافة مستخدم بعد", "assignDealerUser": "تعيين مستخدم تاجر", + "confirmAssignDealerUser": "هل أنت متأكد من رغبتك في تعيين مستخدم التاجر هذا للفرع؟", + "removeDealerUser": "إزالة مستخدم تاجر", + "confirmRemoveDealerUser": "هل أنت متأكد من رغبتك في إزالة مستخدم التاجر هذا من الفرع؟", "createNewUser": "إنشاء مستخدم جديد", "dealerUser": "مستخدم تاجر", "setorEditBranchSchedule": "تعيين أو تحرير جدول الفروع", @@ -417,8 +420,12 @@ "accidentFree": "خالية من الحوادث", "brandNew": "جديدة", "used": "مستعملة", + "all": "الكل", + "newSparePart": "قطع غيار جديدة", + "usedSparePart": "قطع غيار مستعملة", "hasYourCarHadAccident": "هل تعرضت سيارتك لأي حادث؟", "odometerReading": "قراءة عداد المسافات (كم)", + "canMileageIncrease": "هل يمكن زيادة المسافة المقطوعة عند الفحص؟", "currentlyInUse": "قيد الاستخدام حاليًا", "demand": "الطلب", "adDurationExpired": "انتهت مدة عرض الإعلان الخاص بك", @@ -590,6 +597,9 @@ "myNearbyBranches": "فروعي القريبة", "myRecentBranches": "فروعي الأخيرة", "favoriteList": "قائمة المفضلة", + "myFavouriteProviders": "مزودي الخدمة المفضلين", + "myFavouriteAds": "إعلاناتي المفضلة", + "noFavouriteAds": "لا توجد إعلانات مفضلة", "noReviewsBranch": "هذا الفرع لا يوجد لديه تعليقات.", "readLess": "اقرأ أقل", "readMoreReviews": "اقرأ المزيد من التقييمات", @@ -885,6 +895,12 @@ "month": "شهر", "currentPackage": "الحزمة الحالية", "forVar": "لمدة", - "branch": "الفرع" + "branch": "الفرع", + "shareAd": "مشاركة الإعلان", + "checkOutThisAd": "تفقد هذه السيارة الرائعة!", + "downloadApp": "تحميل تطبيق موّاتر:", + "androidApp": "أندرويد", + "iosApp": "آيفون", + "pleaseWait": "يرجى الانتظار..." } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 07b3fbd..0afc648 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -254,6 +254,9 @@ "sendOTP": "Send OTP", "noUserAddedYet": "No User Added Yet", "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", "dealerUser": "Dealer User", "setorEditBranchSchedule": "Set or Edit Branch Schedule", @@ -418,8 +421,12 @@ "accidentFree": "Accident Free", "brandNew": "Brand New", "used": "Used", + "all": "All", + "newSparePart": "New Spare Part", + "usedSparePart": "Used Spare Part", "hasYourCarHadAccident": "Has your car had any accident?", "odometerReading": "Odometer Reading (Km)", + "canMileageIncrease": "Can the mileage be increased when checking?", "currentlyInUse": "Currently in Use", "demand": "Demand", "adDurationExpired": "Your Ad Duration time is over", @@ -591,6 +598,9 @@ "myNearbyBranches": "My Nearby Branches", "myRecentBranches": "My Recent Branches", "favoriteList": "Favorite List", + "myFavouriteProviders": "My Favourite Providers", + "myFavouriteAds": "My Favourite Ads", + "noFavouriteAds": "No Favourite Ads Found", "noReviewsBranch": "This branch has no reviews.", "readLess": "Read Less", "readMoreReviews": "Read More Reviews", @@ -883,5 +893,11 @@ "month": "Month", "currentPackage": "Current Package", "forVar": "for", - "branch": "branch" + "branch": "branch", + "shareAd": "Share Ad", + "checkOutThisAd": "Check out this amazing vehicle!", + "downloadApp": "Download MoWater App:", + "androidApp": "Android", + "iosApp": "iOS", + "pleaseWait": "Please wait..." } \ No newline at end of file diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -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: diff --git a/lib/api/api_client.dart b/lib/api/api_client.dart index 6a3083d..4a640b0 100644 --- a/lib/api/api_client.dart +++ b/lib/api/api_client.dart @@ -138,7 +138,7 @@ class ApiClientImp implements ApiClient { logger.i(url); logger.d("------Payload------"); logger.i(jsonDecode(requestBody)); - logger.d("------Response------"); + // logger.d("------Response------"); logger.i(jsonDecode(response.body)); } if (response.statusCode >= 200 && response.statusCode < 500) { diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 62570e3..600ebb9 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -127,6 +127,9 @@ class ApiConsts { static String vehicleAdsSingleStepUpdate = "${baseUrlServices}api/Advertisement/AdsSingleStep_Update"; static String vehicleAdsSingleStepUpdateExtend = "${baseUrlServices}api/Advertisement/AdsSingleStep_Update_Extend"; static String vehicleAdsGet = "${baseUrlServices}api/Advertisement/Ads_Get"; + static String 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 reserveAdsBankDetailsGet = "${baseUrlServices}api/Advertisement/MCBankAccountAd_Get"; static String adsUpdateStatus = "${baseUrlServices}api/Advertisement/Ads_UpdateStatus"; @@ -181,9 +184,8 @@ class ApiConsts { static String requestOfferUpdateDeliveryOptionByCustomer = "${baseUrlServices}api/RequestManagement/RequestOffer_UpdateDeliveryByCustomer"; static String requestUpdateAdditionalAddress = "${baseUrlServices}api/RequestManagement/Request_UpdateAdditionalAddress"; - //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 selfPickupRequestStatusGet = "${baseUrlServices}api/RequestManagement/SelfPickUpRequestStatus_Get"; static String selfPickupRequestStatusUpdate = "${baseUrlServices}api/RequestManagement/SelfPickUpRequestStatus_Update"; @@ -290,6 +292,7 @@ class MyAssets { //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"; //SVG @@ -468,3 +471,4 @@ class GuestConsts { }, ); } + diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 4fca131..56307b8 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -143,7 +143,8 @@ class AppRoutes { static const String branchDetailView = "/branchDetailPage"; static const String branchSearchFilterPage = "/branchSearchFilterPage"; static const String providerProfileView = "/providerProfilePage"; - static const String favoriteListView = "/favoriteListView"; + static const String favoriteProvidersView = "/favoriteProvidersView"; + static const String favouriteAdsView = "/favouriteAdsView"; //Requests static const String requestsDetailPage = "/requestsDetailPage"; diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 2462395..d5d4066 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -290,6 +290,8 @@ extension AppointmentStatusToInt on AppointmentStatusEnum { case AppointmentStatusEnum.upcoming: return 11; + case AppointmentStatusEnum.served: + return 12; case AppointmentStatusEnum.allAppointments: return 0; @@ -386,6 +388,8 @@ extension AppointmentEnum on int { return AppointmentStatusEnum.past; } else if (this == 11) { return AppointmentStatusEnum.upcoming; + } else if (this == 12) { + return AppointmentStatusEnum.served; } else { return AppointmentStatusEnum.allAppointments; } @@ -440,6 +444,8 @@ extension RequestTypeTypEnum on int { return RequestsTypeEnum.specialCarRequest; } else if (this == 2) { return RequestsTypeEnum.serviceRequest; + } else if (this == 3) { + return RequestsTypeEnum.other; } return RequestsTypeEnum.specialCarRequest; } @@ -454,6 +460,9 @@ extension RequestTypeStatusToInt on RequestsTypeEnum { case RequestsTypeEnum.serviceRequest: return 2; + case RequestsTypeEnum.other: + return 3; + default: return 0; } diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 420938e..3f62126 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -270,6 +270,9 @@ class CodegenLoader extends AssetLoader{ "sendOTP": "إرسال رمز OTP", "noUserAddedYet": "لم يتم إضافة مستخدم بعد", "assignDealerUser": "تعيين مستخدم تاجر", + "confirmAssignDealerUser": "هل أنت متأكد من رغبتك في تعيين مستخدم التاجر هذا للفرع؟", + "removeDealerUser": "إزالة مستخدم تاجر", + "confirmRemoveDealerUser": "هل أنت متأكد من رغبتك في إزالة مستخدم التاجر هذا من الفرع؟", "createNewUser": "إنشاء مستخدم جديد", "dealerUser": "مستخدم تاجر", "setorEditBranchSchedule": "تعيين أو تحرير جدول الفروع", @@ -433,6 +436,9 @@ class CodegenLoader extends AssetLoader{ "accidentFree": "خالية من الحوادث", "brandNew": "جديدة", "used": "مستعملة", + "all": "الكل", + "newSparePart": "قطع غيار جديدة", + "usedSparePart": "قطع غيار مستعملة", "hasYourCarHadAccident": "هل تعرضت سيارتك لأي حادث؟", "odometerReading": "قراءة عداد المسافات (كم)", "currentlyInUse": "قيد الاستخدام حاليًا", @@ -606,6 +612,9 @@ class CodegenLoader extends AssetLoader{ "myNearbyBranches": "فروعي القريبة", "myRecentBranches": "فروعي الأخيرة", "favoriteList": "قائمة المفضلة", + "myFavouriteProviders": "مزودي الخدمة المفضلين", + "myFavouriteAds": "إعلاناتي المفضلة", + "noFavouriteAds": "لا توجد إعلانات مفضلة", "noReviewsBranch": "هذا الفرع لا يوجد لديه تعليقات.", "readLess": "اقرأ أقل", "readMoreReviews": "اقرأ المزيد من التقييمات", @@ -833,7 +842,7 @@ class CodegenLoader extends AssetLoader{ "specialRequestsChats": "دردشات الطلبات الخاصة", "selectDeliveryOption": "اختر خيار التوصيل", "deliveryOptions": "خيارات التوصيل", - "selfPickup": "الاستلام الذاتي", + "selfPickup": "استلام ذاتي", "updateYourLocationInfo": "قم بتحديث معلومات موقعك", "initiateSelfPickup": "بدء الاستلام الذاتي", "selfPickupStatus": "حالة الاستلام الذاتي", @@ -841,43 +850,43 @@ class CodegenLoader extends AssetLoader{ "scheduleDeletedSuccessfully": "تم حذف الجدول بنجاح", "addValidAddress": "رجى إضافة عنوان صالح", "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": "جميع الاعلانات", + "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": "قيد الدفع", @@ -901,7 +910,13 @@ class CodegenLoader extends AssetLoader{ "month": "شهر", "currentPackage": "الحزمة الحالية", "forVar": "لمدة", - "branch": "الفرع" + "branch": "الفرع", + "shareAd": "مشاركة الإعلان", + "checkOutThisAd": "تفقد هذه السيارة الرائعة!", + "downloadApp": "تحميل تطبيق موّاتر:", + "androidApp": "أندرويد", + "iosApp": "آيفون", + "pleaseWait": "يرجى الانتظار..." }; static const Map _en_US = { "firstTimeLogIn": "First Time Log In", @@ -1159,6 +1174,9 @@ static const Map _en_US = { "sendOTP": "Send OTP", "noUserAddedYet": "No User Added Yet", "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", "dealerUser": "Dealer User", "setorEditBranchSchedule": "Set or Edit Branch Schedule", @@ -1323,6 +1341,9 @@ static const Map _en_US = { "accidentFree": "Accident Free", "brandNew": "Brand New", "used": "Used", + "all": "All", + "newSparePart": "New Spare Part", + "usedSparePart": "Used Spare Part", "hasYourCarHadAccident": "Has your car had any accident?", "odometerReading": "Odometer Reading (Km)", "currentlyInUse": "Currently in Use", @@ -1496,6 +1517,9 @@ static const Map _en_US = { "myNearbyBranches": "My Nearby Branches", "myRecentBranches": "My Recent Branches", "favoriteList": "Favorite List", + "myFavouriteProviders": "My Favourite Providers", + "myFavouriteAds": "My Favourite Ads", + "noFavouriteAds": "No Favourite Ads Found", "noReviewsBranch": "This branch has no reviews.", "readLess": "Read Less", "readMoreReviews": "Read More Reviews", @@ -1764,12 +1788,12 @@ static const Map _en_US = { "km": "km", "items": "items", "no_items": "No items", - "All Ads":"All Ads", - "reserveCancel": " Reserve Cancel", + "allAds": "All Ads", + "reserveCancel": "Reserve Cancel", "pendingForReview": "Pending For Review", "pendingForPayment": "Pending For Payment", "rejected_n": "Rejected", - "canceled": "Canceled", + "canceled": "Cancelled", "pendingForPost": "Pending For Post", "active_n": "Active", "expired_n": "Expired", @@ -1785,11 +1809,16 @@ static const Map _en_US = { "pleaseAddValidItemDescription": "Please add valid item description", "pleaseAddValidItemPrice": "Please add valid item price", "remaining": "remaining", - "month": "Month", + "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> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US}; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 3ed6164..eae7b40 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -236,6 +236,9 @@ abstract class LocaleKeys { static const sendOTP = 'sendOTP'; static const noUserAddedYet = 'noUserAddedYet'; static const assignDealerUser = 'assignDealerUser'; + static const confirmAssignDealerUser = 'confirmAssignDealerUser'; + static const removeDealerUser = 'removeDealerUser'; + static const confirmRemoveDealerUser = 'confirmRemoveDealerUser'; static const createNewUser = 'createNewUser'; static const dealerUser = 'dealerUser'; static const setorEditBranchSchedule = 'setorEditBranchSchedule'; @@ -398,6 +401,9 @@ abstract class LocaleKeys { static const accidentFree = 'accidentFree'; static const brandNew = 'brandNew'; static const used = 'used'; + static const all = 'all'; + static const newSparePart = 'newSparePart'; + static const usedSparePart = 'usedSparePart'; static const hasYourCarHadAccident = 'hasYourCarHadAccident'; static const odometerReading = 'odometerReading'; static const currentlyInUse = 'currentlyInUse'; @@ -571,6 +577,9 @@ abstract class LocaleKeys { static const myNearbyBranches = 'myNearbyBranches'; static const myRecentBranches = 'myRecentBranches'; static const favoriteList = 'favoriteList'; + static const myFavouriteProviders = 'myFavouriteProviders'; + static const myFavouriteAds = 'myFavouriteAds'; + static const noFavouriteAds = 'noFavouriteAds'; static const noReviewsBranch = 'noReviewsBranch'; static const readLess = 'readLess'; static const readMoreReviews = 'readMoreReviews'; @@ -837,6 +846,7 @@ abstract class LocaleKeys { 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'; @@ -846,7 +856,7 @@ abstract class LocaleKeys { static const pendingForReview = 'pendingForReview'; static const pendingForPayment = 'pendingForPayment'; static const rejected_n = 'rejected_n'; - static const cancelled_n = 'cancelled_n'; + static const canceled = 'canceled'; static const pendingForPost = 'pendingForPost'; static const active_n = 'active_n'; static const expired_n = 'expired_n'; @@ -866,20 +876,11 @@ abstract class LocaleKeys { 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'; } diff --git a/lib/models/advertisment_models/ad_details_model.dart b/lib/models/advertisment_models/ad_details_model.dart index 665fe68..c63b3e0 100644 --- a/lib/models/advertisment_models/ad_details_model.dart +++ b/lib/models/advertisment_models/ad_details_model.dart @@ -46,6 +46,7 @@ class AdDetailsModel { AdReserveStatus? adReserveStatus; bool? isMyAd; bool? isReservedByMe; + bool? isAdFavorite; String? phoneNo; String? whatsAppNo; String? adOwnerName; @@ -95,6 +96,7 @@ class AdDetailsModel { this.adReserveStatus, this.isMyAd, this.isReservedByMe, + this.isAdFavorite, this.phoneNo, this.whatsAppNo, this.adOwnerName, @@ -164,6 +166,7 @@ class AdDetailsModel { createdByRoleEnum = (json['createdByRole'] as int).toCreatedByRoleEnum(); isMyAd = isMyAds; isReservedByMe = false; + isAdFavorite = json['isFavourite'] ?? false; totalItemsCount = totalItems; } } diff --git a/lib/models/general_models/enums_model.dart b/lib/models/general_models/enums_model.dart index e46372f..5413d41 100644 --- a/lib/models/general_models/enums_model.dart +++ b/lib/models/general_models/enums_model.dart @@ -21,7 +21,7 @@ class EnumsModel { id: json["id"], enumTypeId: json["enumTypeID"], enumValueStr: json["enumValueStr"], - enumValueStrN: json["enumValueStrN"], + enumValueStrN: json["enumValueStrN"] ?? json["enumValueStr"], enumValueStrDes: json["enumValueStrDes"] ?? json["enumValueStr"], enumValue: json["enumValue"], isActive: json["isActive"], diff --git a/lib/models/general_models/widgets_models.dart b/lib/models/general_models/widgets_models.dart index 0463729..e71290c 100644 --- a/lib/models/general_models/widgets_models.dart +++ b/lib/models/general_models/widgets_models.dart @@ -1,12 +1,14 @@ class FilterListModel { String title; - String? titleN; + String titleN; + String description; + String descriptionN; String iconUrl; int id; bool isSelected; + bool isCustom; - FilterListModel( - {required this.id, required this.isSelected, this.iconUrl = "", required this.title, this.titleN}); + FilterListModel({required this.id, required this.isSelected, this.isCustom = false, this.iconUrl = "", required this.title, this.titleN = "", this.description = "", this.descriptionN = "",}); } class SelectionModel { diff --git a/lib/repositories/ads_repo.dart b/lib/repositories/ads_repo.dart index e4455a5..4d0a8ea 100644 --- a/lib/repositories/ads_repo.dart +++ b/lib/repositories/ads_repo.dart @@ -57,6 +57,10 @@ abstract class AdsRepo { Future> getMyAds(); + Future> getFavouriteAds(); + + Future toggleAdFavorite({required int adsID, required bool isFavorite}); + Future getAdBankingAccountInfo({required int adId}); Future updateAdStatus({required int adId, required AdPostStatus adStatusToUpdate, String? comment}); @@ -510,6 +514,38 @@ class AdsRepoImp implements AdsRepo { return vehicleAdsDetails; } + @override + Future> 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 favouriteAdsDetails = List.generate(adsGenericModel.data.length, (index) => AdDetailsModel.fromJson(adsGenericModel.data[index], false, adsGenericModel.totalItemsCount ?? 1)); + return favouriteAdsDetails; + } + + @override + Future 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 Future getAdBankingAccountInfo({required int adId}) async { var params = { @@ -560,8 +596,7 @@ class AdsRepoImp implements AdsRepo { String token = appState.getUser.data!.accessToken ?? ""; GenericRespModel adsGenericModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), - ApiConsts. - adsExtendDurationCreate, + ApiConsts.adsExtendDurationCreate, postParams, token: token, ); diff --git a/lib/repositories/request_repo.dart b/lib/repositories/request_repo.dart index 021369c..19d86b4 100644 --- a/lib/repositories/request_repo.dart +++ b/lib/repositories/request_repo.dart @@ -19,7 +19,7 @@ abstract class RequestRepo { required String brand, required String model, required String year, - required bool isNew, + required int conditionType, required int countryID, required int cityID, required String price, @@ -88,7 +88,7 @@ class RequestRepoImp implements RequestRepo { required String brand, required String model, required String year, - required bool isNew, + required int conditionType, required int countryID, required int cityID, required String price, @@ -103,7 +103,7 @@ class RequestRepoImp implements RequestRepo { "brand": brand, "model": model, "year": year, - "isNew": isNew, + "conditionType": conditionType, "countryID": countryID, "cityID": cityID, "price": price, @@ -181,9 +181,13 @@ class RequestRepoImp implements RequestRepo { "model": vehicleModel.toString(), "year": vehicleYearId.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.getIsViewOnly) { paramsForGetRequests.addEntries([MapEntry("customerID", appState.getUser.data!.userInfo!.customerId ?? 0)]); diff --git a/lib/services/payments_service.dart b/lib/services/payments_service.dart index 95ecb61..b6caaa9 100644 --- a/lib/services/payments_service.dart +++ b/lib/services/payments_service.dart @@ -15,6 +15,7 @@ abstract class PaymentService { required PaymentTypes paymentType, required Function() onSuccess, required Function() onFailure, + Function()? onExit, }); Future onBrowserLoadStart({ @@ -95,11 +96,15 @@ class PaymentServiceImp implements PaymentService { required PaymentTypes paymentType, required Function() onSuccess, required Function() onFailure, + Function()? onExit, }) async { String urlRequest = getUrlRequestByPaymentId(id: id, paymentType: paymentType, appointmentIds: appointmentIds); log("PaymentUrl: $urlRequest"); myInAppBrowser = MyInAppBrowser(onExitCallback: () { log("Browser Exited"); + if (onExit != null) { + onExit(); + } }, onLoadStartCallback: (String url) { log("Browser LoadStart for : $url"); onBrowserLoadStart(onFailure: onFailure, onSuccess: onSuccess, url: url); diff --git a/lib/utils/enums.dart b/lib/utils/enums.dart index 6806c5a..28d93c7 100644 --- a/lib/utils/enums.dart +++ b/lib/utils/enums.dart @@ -178,6 +178,7 @@ enum AppointmentStatusEnum { today, past, upcoming, + served, } enum AppointmentPaymentStatusEnum { @@ -191,6 +192,7 @@ enum AppointmentPaymentStatusEnum { enum RequestsTypeEnum { specialCarRequest, serviceRequest, + other, } enum ChatMessageTypeEnum { diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index ca9d93f..ba15a61 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -36,8 +36,12 @@ class Utils { toastification.show( type: ToastificationType.info, style: ToastificationStyle.minimal, - title: Text(message), - autoCloseDuration: const Duration(seconds: 2), + title: Text( + message, + maxLines: 5, + overflow: TextOverflow.ellipsis, + ), + autoCloseDuration: const Duration(seconds: 3), alignment: Alignment.bottomCenter, showProgressBar: false, ); @@ -440,27 +444,27 @@ class Utils { case BranchStatusEnum.approvedOrActive: 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 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({ required String text, diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index c3f32af..dd332e5 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -89,6 +89,7 @@ class AdVM extends BaseVM { AdDetailsModel? previousAdDetails; List myDraftAds = []; + List favouriteAds = []; List exploreAds = []; List exploreAdsFilteredList = []; List myAdsFilteredList = []; @@ -338,6 +339,25 @@ class AdVM extends BaseVM { return true; } + Future 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 getExploreAds() async { pageIndexForExploreAds = 1; hasMoreDataForExploreAds = true; @@ -365,6 +385,18 @@ class AdVM extends BaseVM { } } + Future getFavouriteAds() async { + try { + setState(ViewState.busy); + favouriteAds = await adsRepo.getFavouriteAds(); + notifyListeners(); + setState(ViewState.idle); + } catch (e) { + setState(ViewState.idle); + logger.e(e.toString()); + } + } + Future fetchMoreDraftAds() async { if (isLoadingMore) return; hasMoreDataForMyDraftsAds = true; @@ -740,7 +772,7 @@ class AdVM extends BaseVM { notifyListeners(); } - int isInUsed = 0; + int isInUsed = 0; // 0 = No, 1 = Yes void updateIsInUsed(int value) { isInUsed = value; diff --git a/lib/view_models/appointments_view_model.dart b/lib/view_models/appointments_view_model.dart index fd88b0a..9e846a9 100644 --- a/lib/view_models/appointments_view_model.dart +++ b/lib/view_models/appointments_view_model.dart @@ -52,8 +52,7 @@ class AppointmentsVM extends BaseVM { final BranchRepo branchRepo; final AppointmentRepo appointmentRepo; - AppointmentsVM( - {required this.commonServices, required this.appointmentRepo, required this.commonRepo, required this.branchRepo}); + AppointmentsVM({required this.commonServices, required this.appointmentRepo, required this.commonRepo, required this.branchRepo}); bool isUpcomingEnabled = true; bool isFetchingLists = false; @@ -80,8 +79,7 @@ class AppointmentsVM extends BaseVM { List allSelectedItemsInAppointments = []; bool ifItemAlreadySelected(int id) { - int indexFound = allSelectedItemsInAppointments.indexWhere(( - element) => element.id == id); + int indexFound = allSelectedItemsInAppointments.indexWhere((element) => element.id == id); log("already selected at: $indexFound"); if (indexFound != -1) { return true; @@ -109,8 +107,7 @@ class AppointmentsVM extends BaseVM { } } - Future onPayNowPressedForAppointment( - {required BuildContext context, required int appointmentID}) async { + Future onPayNowPressedForAppointment({required BuildContext context, required int appointmentID}) async { context.read().updateAppointmentIdsForPayment( ids: [appointmentID]); navigateWithName(context, AppRoutes.paymentMethodsView, @@ -168,8 +165,7 @@ class AppointmentsVM extends BaseVM { } } - Future onConfirmAppointmentPressed( - {required BuildContext context, required appointmentId}) async { + Future onConfirmAppointmentPressed({required BuildContext context, required appointmentId}) async { context.read().updateAppointmentIdsForPayment( ids: [appointmentId]); navigateWithName(context, AppRoutes.paymentMethodsView, @@ -224,8 +220,7 @@ class AppointmentsVM extends BaseVM { } } - Future onCancelAppointmentPressed( - {required BuildContext context, required AppointmentListModel appointmentListModel}) async { + Future onCancelAppointmentPressed({required BuildContext context, required AppointmentListModel appointmentListModel}) async { Utils.showLoading(context); try { GenericRespModel genericRespModel = await appointmentRepo @@ -378,20 +373,19 @@ class AppointmentsVM extends BaseVM { for (int i = 0; i < myAppointmentsEnum.length; i++) { appointmentsFilterOptions.add(FilterListModel( title: myAppointmentsEnum[i].enumValueStr, - titleN: myAppointmentsEnum[i].enumValueStrN, + titleN: myAppointmentsEnum[i].enumValueStrN, isSelected: false, id: myAppointmentsEnum[i].enumValue)); } appointmentsFilterOptions.insert( - 0, FilterListModel(title: "All Appointments",titleN: "جميع الفروع", isSelected: true, id: 0)); + 0, FilterListModel(title: "All Appointments", titleN: "جميع الفروع", isSelected: true, id: 0)); notifyListeners(); } bool isAppointmentLoading = false; - applyFilterOnAppointmentsVMForProviders( - {int? branchID, required AppointmentStatusEnum appointmentStatusEnum, bool isNeedCustomerFilter = false, bool shouldPopulateUpcoming = false}) async { + applyFilterOnAppointmentsVMForProviders({int? branchID, required AppointmentStatusEnum appointmentStatusEnum, bool isNeedCustomerFilter = false, bool shouldPopulateUpcoming = false}) async { if (appointmentsFilterOptions.isEmpty) return; for (var value in appointmentsFilterOptions) { value.isSelected = false; @@ -430,8 +424,7 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - applyFilterOnAppointmentsVMForCustomers( - {required AppointmentStatusEnum appointmentStatusEnum, bool shouldPopulateUpcoming = false}) async { + applyFilterOnAppointmentsVMForCustomers({required AppointmentStatusEnum appointmentStatusEnum, bool shouldPopulateUpcoming = false}) async { log("appointmentsFilterOptions: ${appointmentsFilterOptions.isEmpty}"); if (appointmentsFilterOptions.isEmpty) return; for (var value in appointmentsFilterOptions) { @@ -460,8 +453,7 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - List findAppointmentsBasedOnCustomers( - List appointments) { + List findAppointmentsBasedOnCustomers(List appointments) { Set uniqueCustomerIDs = {}; for (var item in appointments) { uniqueCustomerIDs.add(item.customerID ?? 0); @@ -511,8 +503,7 @@ class AppointmentsVM extends BaseVM { } } - Future updateAppointmentStatus( - {required int appointmentId, required AppointmentStatusEnum appointmentStatusEnum, bool isNeedToRebuild = false}) async { + Future updateAppointmentStatus({required int appointmentId, required AppointmentStatusEnum appointmentStatusEnum, bool isNeedToRebuild = false}) async { if (isNeedToRebuild) setState(ViewState.busy); try { GenericRespModel genericRespModel = await appointmentRepo @@ -599,8 +590,7 @@ class AppointmentsVM extends BaseVM { .length; } - updateSelectedAppointmentDate( - {required int dateIndex, required int scheduleIndex}) { + updateSelectedAppointmentDate({required int dateIndex, required int scheduleIndex}) { for (var element in serviceAppointmentScheduleList[scheduleIndex] .customTimeDateSlotList!) { element.date!.isSelected = false; @@ -622,8 +612,7 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - updateSelectedAppointmentSlotByDate( - {required int scheduleIndex, required int slotIndex}) { + updateSelectedAppointmentSlotByDate({required int scheduleIndex, required int slotIndex}) { for (var element in serviceAppointmentScheduleList[scheduleIndex] .customTimeDateSlotList!) { for (var element in element.availableSlots!) { @@ -652,8 +641,7 @@ class AppointmentsVM extends BaseVM { int selectedSubServicesCounter = 0; void onItemUpdateOrSelected(int index, bool selected, int itemId) { - int serviceIndex = servicesInCurrentAppointment.indexWhere(( - element) => element.serviceId == currentServiceSelection!.serviceId!); + int serviceIndex = servicesInCurrentAppointment.indexWhere((element) => element.serviceId == currentServiceSelection!.serviceId!); serviceItemsFromApi[index].isUpdateOrSelected = selected; serviceItemsFromApi[index].isHomeSelected = isHomeTapped; @@ -983,8 +971,7 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - Future onRescheduleAppointmentPressed( - {required BuildContext context, required AppointmentListModel appointmentListModel}) async { + Future onRescheduleAppointmentPressed({required BuildContext context, required AppointmentListModel appointmentListModel}) async { Utils.showLoading(context); List serviceItemIdsForHome = []; @@ -1024,8 +1011,7 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - Future onRescheduleAppointmentConfirmPressed( - {required BuildContext context, required int appointmentId, required int selectedSlotId}) async { + Future onRescheduleAppointmentConfirmPressed({required BuildContext context, required int appointmentId, required int selectedSlotId}) async { Utils.showLoading(context); try { GenericRespModel genericRespModel = await appointmentRepo @@ -1066,8 +1052,7 @@ class AppointmentsVM extends BaseVM { } } - Future buildComplaintBottomSheet( - {required int customerID, required int complainType, required String customerName, required BuildContext context}) { + Future buildComplaintBottomSheet({required int customerID, required int complainType, required String customerName, required BuildContext context}) { return showModalBottomSheet( context: context, isScrollControlled: true, @@ -1129,8 +1114,7 @@ class AppointmentsVM extends BaseVM { ); } - Future reportComplaintAgainstCustomer( - {required int customerID, required String comment, required int complaintType, required BuildContext context}) async { + Future reportComplaintAgainstCustomer({required int customerID, required String comment, required int complaintType, required BuildContext context}) async { Utils.showLoading(context); try { GenericRespModel genericRespModel = await commonRepo @@ -1148,8 +1132,7 @@ class AppointmentsVM extends BaseVM { } } - Future getProvidersContactInfo( - {required int providerId, required BuildContext context}) async { + Future getProvidersContactInfo({required int providerId, required BuildContext context}) async { Utils.showLoading(context); try { ProviderContactInfoModel contactInfoModel = await commonRepo @@ -1179,7 +1162,8 @@ class AppointmentsVM extends BaseVM { Services services = await branchRepo.fetchServicesByCategoryId( serviceCategoryId: -1); services.data?.forEach((element) { - branchesCategoriesFilterOptions.add(FilterListModel(id: element.id ?? 0, + branchesCategoriesFilterOptions.add(FilterListModel( + id: element.id ?? 0, isSelected: false, iconUrl: element.serviceIconUrl ?? "", title: element.description ?? "", @@ -1191,14 +1175,12 @@ class AppointmentsVM extends BaseVM { setState(ViewState.idle); } - Future getAllNearBranches( - {bool isNeedToRebuild = false, bool isFromRefresh = false}) async { + Future getAllNearBranches({bool isNeedToRebuild = false, bool isFromRefresh = false}) async { nearbyBranches.clear(); if (isNeedToRebuild) setState(ViewState.busy); if (isFromRefresh) { - var selectedFilter = branchesCategoriesFilterOptions.firstWhere(( - element) => element.isSelected); + var selectedFilter = branchesCategoriesFilterOptions.firstWhere((element) => element.isSelected); log("selectedFilter: ${selectedFilter.id}"); if (selectedFilter.id == 0) { @@ -1305,8 +1287,7 @@ class AppointmentsVM extends BaseVM { // Provider Filter List branchFilterProviderSearchHistory = []; - void removeBranchFilterProviderSearchHistory( - {bool isClear = false, required int index}) { + void removeBranchFilterProviderSearchHistory({bool isClear = false, required int index}) { if (isClear) { branchFilterProviderSearchHistory.clear(); notifyListeners(); @@ -1349,8 +1330,7 @@ class AppointmentsVM extends BaseVM { // Category Filter List branchFilterCategorySearchHistory = []; - void removeBranchFilterCategorySearchHistory( - {bool isClear = false, required int index}) { + void removeBranchFilterCategorySearchHistory({bool isClear = false, required int index}) { if (isClear) { branchFilterCategorySearchHistory.clear(); notifyListeners(); @@ -1393,8 +1373,7 @@ class AppointmentsVM extends BaseVM { // Services Filter List branchFilterServicesSearchHistory = []; - void removeBranchFilterServicesSearchHistory( - {bool isClear = false, required int index}) { + void removeBranchFilterServicesSearchHistory({bool isClear = false, required int index}) { if (isClear) { branchFilterServicesSearchHistory.clear(); notifyListeners(); @@ -1448,8 +1427,7 @@ class AppointmentsVM extends BaseVM { return false; } - ifAlreadyExistForStringValue( - {required List list, required String value}) { + ifAlreadyExistForStringValue({required List list, required String value}) { int index = list.indexWhere((element) { return element == value; }); @@ -1627,8 +1605,7 @@ class AppointmentsVM extends BaseVM { setState(ViewState.idle); } - Future getBranchesBasedOnCategoryFilters( - {required int serviceId}) async { + Future getBranchesBasedOnCategoryFilters({required int serviceId}) async { setState(ViewState.busy); if (serviceId == 0) { await getAllNearBranches(); @@ -1647,8 +1624,7 @@ class AppointmentsVM extends BaseVM { List branchesDropList = []; - Future fetchAllBranchesBySelectedProviderId( - {required List providersIdsList}) async { + Future fetchAllBranchesBySelectedProviderId({required List providersIdsList}) async { branchesDropList.clear(); setOnlyState(ViewState.busy); List providers = await branchRepo.getBranchesByFilters( @@ -1682,8 +1658,7 @@ class AppointmentsVM extends BaseVM { // Provider Filter For Appointments List appointmentFilterProviderSearchHistory = []; - void removeAppointmentFilterProviderSearchHistory( - {bool isClear = false, required int index}) { + void removeAppointmentFilterProviderSearchHistory({bool isClear = false, required int index}) { if (isClear) { appointmentFilterProviderSearchHistory.clear(); notifyListeners(); @@ -1740,8 +1715,7 @@ class AppointmentsVM extends BaseVM { List appointmentFilterBranchSearchHistory = []; - void removeAppointmentFilterBranchSearchHistory( - {bool isClear = false, required int index}) { + void removeAppointmentFilterBranchSearchHistory({bool isClear = false, required int index}) { if (isClear) { appointmentFilterBranchSearchHistory.clear(); notifyListeners(); @@ -1784,8 +1758,7 @@ class AppointmentsVM extends BaseVM { List appointmentFilterCategorySearchHistory = []; - void removeAppointmentFilterCategorySearchHistory( - {bool isClear = false, required int index}) { + void removeAppointmentFilterCategorySearchHistory({bool isClear = false, required int index}) { if (isClear) { appointmentFilterCategorySearchHistory.clear(); notifyListeners(); @@ -1826,8 +1799,7 @@ class AppointmentsVM extends BaseVM { List appointmentFilterServicesSearchHistory = []; - void removeAppointmentFilterServicesSearchHistory( - {bool isClear = false, required int index}) { + void removeAppointmentFilterServicesSearchHistory({bool isClear = false, required int index}) { if (isClear) { appointmentFilterServicesSearchHistory.clear(); notifyListeners(); @@ -1868,8 +1840,7 @@ class AppointmentsVM extends BaseVM { List appointmentFilterItemsSearchHistory = []; - void removeAppointmentFilterItemsSearchHistory( - {bool isClear = false, required int index}) { + void removeAppointmentFilterItemsSearchHistory({bool isClear = false, required int index}) { if (isClear) { appointmentFilterItemsSearchHistory.clear(); notifyListeners(); @@ -1918,8 +1889,7 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - void removeAppointmentFilterCustomerNameSearchHistory( - {bool isClear = false, required int index}) { + void removeAppointmentFilterCustomerNameSearchHistory({bool isClear = false, required int index}) { if (isClear) { appointmentFilterCustomerNameSearchHistory.clear(); notifyListeners(); @@ -1957,8 +1927,7 @@ class AppointmentsVM extends BaseVM { List appointmentFilterMobilePhoneSearchHistory = []; - void removeAppointmentFilterMobilePhoneSearchHistory( - {bool isClear = false, required int index}) { + void removeAppointmentFilterMobilePhoneSearchHistory({bool isClear = false, required int index}) { if (isClear) { appointmentFilterMobilePhoneSearchHistory.clear(); notifyListeners(); @@ -1989,8 +1958,7 @@ class AppointmentsVM extends BaseVM { List appointmentFilterServiceDeliverySearchHistory = []; - void removeAppointmentFilterServiceDeliverySearchHistory( - {bool isClear = false, required int index}) { + void removeAppointmentFilterServiceDeliverySearchHistory({bool isClear = false, required int index}) { if (isClear) { appointmentFilterServiceDeliverySearchHistory.clear(); notifyListeners(); @@ -2004,8 +1972,7 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - void addAppointmentFilterServiceDeliverySearchHistory( - {required DropValue value}) { + void addAppointmentFilterServiceDeliverySearchHistory({required DropValue value}) { if (appointmentFilterServiceDeliverySearchHistory.isEmpty) { updateAppointmentFiltersCounter(appointmentFiltersCounter + 1); } @@ -2020,8 +1987,7 @@ class AppointmentsVM extends BaseVM { void updateAppointmentFilterSelectedServiceDeliveryId(SelectionModel id, {bool isForSearch = false}) async { if (isForSearch) { - DropValue itemsDrop = serviceDeliveryTypesDropList.firstWhere(( - element) => element.id == id.selectedId); + DropValue itemsDrop = serviceDeliveryTypesDropList.firstWhere((element) => element.id == id.selectedId); if (!ifAlreadyExist(list: appointmentFilterServiceDeliverySearchHistory, value: itemsDrop)) { addAppointmentFilterServiceDeliverySearchHistory(value: itemsDrop); @@ -2069,8 +2035,7 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - Future> getAppointmentsBasedOnFiltersForCustomer( - {required AppointmentStatusEnum appointmentStatusEnum}) async { + Future> getAppointmentsBasedOnFiltersForCustomer({required AppointmentStatusEnum appointmentStatusEnum}) async { try { setState(ViewState.busy); List providersIdsList = []; @@ -2126,8 +2091,7 @@ class AppointmentsVM extends BaseVM { String selectedDateForAppointments = ""; - Future> getAppointmentsBasedOnFiltersForProviders( - {required int branchID, AppointmentStatusEnum? appointmentStatus}) async { + Future> getAppointmentsBasedOnFiltersForProviders({required int branchID, AppointmentStatusEnum? appointmentStatus}) async { try { setState(ViewState.busy); List servicesIdsList = []; @@ -2183,36 +2147,29 @@ class AppointmentsVM extends BaseVM { } } - Future addProviderToFavorite( - {required int serviceProviderID, required BuildContext context}) async { - Utils.showLoading(context); + Future addProviderToFavorite({required int serviceProviderID, BuildContext? context}) async { try { - GenericRespModel genericRespModel = await branchRepo - .addProviderToFavourite(providerID: serviceProviderID); - Utils.showToast(genericRespModel.message.toString()); - Utils.hideLoading(context); + GenericRespModel genericRespModel = await branchRepo.addProviderToFavourite(providerID: serviceProviderID); + if (genericRespModel.message != null && genericRespModel.message!.isNotEmpty) { + Utils.showToast(genericRespModel.message!); + } return genericRespModel.messageStatus == 1; } catch (e, s) { logger.e(s); - Utils.hideLoading(context); Utils.showToast(e.toString()); return false; } } - Future removeProviderFromFavorite( - {required int serviceProviderID, required BuildContext context}) async { - Utils.showLoading(context); - + Future removeProviderFromFavorite({required int serviceProviderID, BuildContext? context}) async { try { - GenericRespModel genericRespModel = await branchRepo - .removeProviderFromFavourite(providerID: serviceProviderID); - Utils.hideLoading(context); - Utils.showToast(genericRespModel.message.toString()); + GenericRespModel genericRespModel = await branchRepo.removeProviderFromFavourite(providerID: serviceProviderID); + if (genericRespModel.message != null && genericRespModel.message!.isNotEmpty) { + Utils.showToast(genericRespModel.message!); + } return genericRespModel.messageStatus == 1; } catch (e, s) { logger.e(s); - Utils.hideLoading(context); Utils.showToast(e.toString()); return false; } @@ -2233,6 +2190,25 @@ class AppointmentsVM extends BaseVM { } } + Future removeProviderFromFavourite({required int providerID}) async { + try { + GenericRespModel genericRespModel = await branchRepo.removeProviderFromFavourite(providerID: providerID); + 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; + } + } + List currentBranchReviews = []; bool isReadMoreEnabled = false; @@ -2246,8 +2222,7 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - Future getReviewsByBranchId( - {required int serviceProviderBranchID}) async { + Future getReviewsByBranchId({required int serviceProviderBranchID}) async { setState(ViewState.busy); try { currentBranchReviews = await branchRepo.getBranchRatings( @@ -2262,8 +2237,7 @@ class AppointmentsVM extends BaseVM { // Chat Related - Future getGeneralChatsListForProvider( - {required BuildContext context}) async { + Future getGeneralChatsListForProvider({required BuildContext context}) async { try { // Utils.showLoading(context); // List buyersChatList = await appointmentRepo.getChatBuyersForAds(adsID: adsID); @@ -2278,8 +2252,7 @@ class AppointmentsVM extends BaseVM { } } - void onGeneralChatMessagesButtonPressed( - {required BuildContext context}) async { + void onGeneralChatMessagesButtonPressed({required BuildContext context}) async { final myUserID = AppState().getUser.data!.userInfo!.userId; // if (adDetailsModel.isMyAd != null && adDetailsModel.isMyAd!) { // await getGeneralChatsListForProvider(context: context); diff --git a/lib/view_models/chat_view_model.dart b/lib/view_models/chat_view_model.dart index fd09446..c075154 100644 --- a/lib/view_models/chat_view_model.dart +++ b/lib/view_models/chat_view_model.dart @@ -33,6 +33,7 @@ class ChatVM extends BaseVM { ChatVM({required this.chatRepo, required this.requestRepo, required this.commonServices}); HubConnection? hubConnection; + bool _isSubscribed = false; // Track if listeners are subscribed String chatMessageText = ""; @@ -281,30 +282,42 @@ class ChatVM extends BaseVM { } 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; - Future _reconnectWithBackoff(BuildContext context) async { - if (_retryCount >= _maxRetries) { - logger.e("Maximum retry attempts reached. Stopping reconnection."); - return; + Future _reconnectWithBackoff() async { + // Never stop trying, but increase delays after max retries + int delaySeconds; + 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 ${delay.inSeconds} seconds..."); - await Future.delayed(delay); + logger.i("Retrying connection in $delaySeconds seconds (attempt ${_retryCount + 1})..."); + await Future.delayed(Duration(seconds: delaySeconds)); _retryCount++; - await buildHubConnection(context); // Try reconnecting + await buildHubConnection(); // Try reconnecting (no context needed) } - Future buildHubConnection(BuildContext context) async { - if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { - if (_isReconnecting) { - logger.i("Reconnection already in progress"); - return; + // Clear all existing listeners to prevent duplicates + void _clearAllListeners() { + if (hubConnection != null && _isSubscribed) { + try { + 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 buildHubConnection({BuildContext? context}) async { + if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { if (_isReconnecting) { logger.i("Reconnection already in progress"); return; @@ -313,34 +326,58 @@ class ChatVM extends BaseVM { try { _isReconnecting = true; + // Create new hub connection 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 _retryCount = 0; _isReconnecting = false; - // Subscribe to messages - _subscribeToReceiveRequestOfferMessages(context); - _subscribeToReceiveAdMessages(context); - _subscribeToReceiveGeneralMessages(context); + logger.i("Hub Connected Successfully: ${hubConnection!.connectionId}"); + + // Clear old listeners before subscribing (prevents duplicates) + _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 hubConnection!.onclose((exception) async { logger.i("onClose: ${exception.toString()}"); - await _reconnectWithBackoff(context); + _isSubscribed = false; + await _reconnectWithBackoff(); // No context needed }); hubConnection!.onreconnecting((exception) { logger.i("onReconnecting: ${exception?.toString()}"); + _isSubscribed = false; }); hubConnection!.onreconnected((connectionId) { 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) { logger.e("Error during hub connection setup: ${e.toString()}"); - _isReconnecting = false; // Reset on failure + _isReconnecting = false; + // Retry on failure + await _reconnectWithBackoff(); } notifyListeners(); @@ -350,8 +387,18 @@ class ChatVM extends BaseVM { } Future closeHubConnection() async { - if (hubConnection != null && hubConnection!.state == HubConnectionState.connected) { - hubConnection!.stop(); + try { + _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 offerImages, required BuildContext context, }) async { + // Ensure connection is established if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { - logger.i("Hub Not Connected!!"); - await buildHubConnection(context); + logger.i("Hub Not Connected!! Attempting to connect..."); + 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) { @@ -398,17 +455,20 @@ class ChatVM extends BaseVM { }; logger.i(obj); - hubConnection!.invoke( - SignalrConsts.sendMessageRequestOffer, - args: [obj], - ).catchError((e) { - logger.i("error in invoking SendMessageRequestOffer: ${e.toString()}"); - Utils.showToast(e.toString()); + try { + await hubConnection!.invoke( + SignalrConsts.sendMessageRequestOffer, + args: [obj], + ); + return true; + } catch (e) { + logger.e("error in invoking SendMessageRequestOffer: ${e.toString()}"); + Utils.showToast("Failed to send offer"); return false; - }); + } } - return true; + return false; } MessageImageModel convertFileToMessageImageModel({required File file}) { @@ -467,10 +527,21 @@ class ChatVM extends BaseVM { }) async { if (chatMessageType == ChatMessageTypeEnum.freeText && message.isEmpty) return false; + // Ensure connection is established if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { - logger.i("Hub Not Connected!!"); - await buildHubConnection(context); + logger.i("Hub Not Connected!! Attempting to connect..."); + 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) { final userId = AppState().getUser.data!.userInfo!.userId.toString(); final name = AppState().getUser.data!.userInfo!.firstName.toString(); @@ -495,40 +566,42 @@ class ChatVM extends BaseVM { } logger.i(obj); - hubConnection!.invoke( - SignalrConsts.sendMessageRequestOffer, - args: [obj], - ).catchError((e) { - logger.i("error in invoking SendMessage: ${e.toString()}"); - Utils.showToast(e.toString()); - return false; - }); - - ChatMessageModel chatMessageModel = ChatMessageModel( - messageType: chatMessageType.getIdFromChatMessageTypeEnum(), - chatText: message, - isMyMessage: true, - requestID: requestId, - senderName: name, - senderUserID: userId, - chatMessageTypeEnum: chatMessageType, - receiverUserID: receiverId, - messageImages: messageImages, - ); - - if (AppState().currentAppType == AppType.customer) { - int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == receiverId); - if (providerIndex != -1) { - serviceProviderOffersList[providerIndex].chatMessages!.add(chatMessageModel); - } - } else { - int providerIndex = context.read().myFilteredRequests.indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId)); - log("providerIndex2:$providerIndex"); - if (providerIndex != -1) { - context.read().addChatMessagesInRequestsModel(msg: chatMessageModel, index: providerIndex); + try { + await hubConnection!.invoke( + SignalrConsts.sendMessageRequestOffer, + args: [obj], + ); + + ChatMessageModel chatMessageModel = ChatMessageModel( + messageType: chatMessageType.getIdFromChatMessageTypeEnum(), + chatText: message, + isMyMessage: true, + requestID: requestId, + senderName: name, + senderUserID: userId, + chatMessageTypeEnum: chatMessageType, + receiverUserID: receiverId, + messageImages: messageImages, + ); + + if (AppState().currentAppType == AppType.customer) { + int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == receiverId); + if (providerIndex != -1) { + serviceProviderOffersList[providerIndex].chatMessages!.add(chatMessageModel); + } + } else { + int providerIndex = context.read().myFilteredRequests.indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId)); + log("providerIndex2:$providerIndex"); + if (providerIndex != -1) { + context.read().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; } @@ -664,7 +737,7 @@ class ChatVM extends BaseVM { if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { logger.i("Hub Not Connected!!"); - await buildHubConnection(context); + await buildHubConnection(context: context); } if (hubConnection!.state == HubConnectionState.connected) { var obj = { @@ -764,9 +837,19 @@ class ChatVM extends BaseVM { }) async { if (message.isEmpty) return false; + // Ensure connection is established if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { - logger.i("Hub Not Connected!!"); - await buildHubConnection(context); + logger.i("Hub Not Connected!! Attempting to connect..."); + 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) { @@ -781,32 +864,34 @@ class ChatVM extends BaseVM { }; logger.i("$obj"); - hubConnection!.invoke( - SignalrConsts.sendMessageAds, - args: [obj], - ).catchError((e) { - logger.i("error in invoking SendMessage: ${e.toString()}"); - Utils.showToast(e.toString()); + try { + await hubConnection!.invoke( + SignalrConsts.sendMessageAds, + args: [obj], + ); + + 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; - }); - - 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; } @@ -843,7 +928,7 @@ class ChatVM extends BaseVM { Future onNewMessageReceivedForGeneral({required List messages}) async { for (var msg in messages) { - currentMessagesForAds.add(msg); + currentMessagesForGeneralChat.add(msg); // ✅ FIXED: Use correct list } scrollChatDown(); notifyListeners(); @@ -872,9 +957,19 @@ class ChatVM extends BaseVM { }) async { if (message.isEmpty) return false; + // Ensure connection is established if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) { - logger.i("Hub Not Connected!!"); - await buildHubConnection(context); + logger.i("Hub Not Connected!! Attempting to connect..."); + 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) { @@ -887,31 +982,33 @@ class ChatVM extends BaseVM { }; logger.i("$obj"); - hubConnection!.invoke( - SignalrConsts.sendMessageGeneral, - args: [obj], - ).catchError((e) { - logger.i("error in invoking SendMessage: ${e.toString()}"); - Utils.showToast(e.toString()); + try { + await hubConnection!.invoke( + SignalrConsts.sendMessageGeneral, + args: [obj], + ); + + 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; - }); - - 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; } diff --git a/lib/view_models/dashboard_view_model_customer.dart b/lib/view_models/dashboard_view_model_customer.dart index 247496e..abdf321 100644 --- a/lib/view_models/dashboard_view_model_customer.dart +++ b/lib/view_models/dashboard_view_model_customer.dart @@ -1,5 +1,6 @@ import 'dart:developer'; import 'dart:io'; +import 'package:flutter/widgets.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/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/appointments_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:mc_common_app/view_models/chat_view_model.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; import 'package:provider/provider.dart'; -class DashboardVmCustomer extends BaseVM { +class DashboardVmCustomer extends BaseVM with WidgetsBindingObserver { final CommonAppServices commonServices; 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(_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 = ""; @@ -81,7 +124,7 @@ class DashboardVmCustomer extends BaseVM { await requestsVM.getRequests(); await adVM.getVehicleTypes(); await adVM.getVehicleAdsDuration(); - await chatVM.buildHubConnection(context); + await chatVM.buildHubConnection(context: context); // if (appointmentsVM.myAppointments.isEmpty) { // await appointmentsVM.getMyAppointments(); diff --git a/lib/view_models/dashboard_view_model_provider.dart b/lib/view_models/dashboard_view_model_provider.dart index fbb112e..1d02098 100644 --- a/lib/view_models/dashboard_view_model_provider.dart +++ b/lib/view_models/dashboard_view_model_provider.dart @@ -1,7 +1,7 @@ import 'dart:io'; +import 'package:flutter/widgets.dart'; import 'package:google_maps_flutter/google_maps_flutter.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/config/dependency_injection.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/subscriptions_view_model.dart'; -class DashboardVMProvider extends BaseVM { +class DashboardVMProvider extends BaseVM with WidgetsBindingObserver { final CommonAppServices commonServices; 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(_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 = ""; @@ -102,7 +145,7 @@ class DashboardVMProvider extends BaseVM { await adVM.getVehicleTypes(); await adVM.getVehicleAdsDuration(); - await chatVM.buildHubConnection(context); + await chatVM.buildHubConnection(context: context); } void checkUserSubscription(SubscriptionActionTypeEnum actionType, BuildContext context, {required Function() callback}) async { diff --git a/lib/view_models/payment_view_model.dart b/lib/view_models/payment_view_model.dart index bcb4030..41b0717 100644 --- a/lib/view_models/payment_view_model.dart +++ b/lib/view_models/payment_view_model.dart @@ -76,14 +76,14 @@ class PaymentVM extends ChangeNotifier { Future verifyPayments( {required BuildContext context, required int id, List? appointmentIds, required int paymentTypeId, required Function() onVerified, bool isForAppointment = false}) async { try { - Utils.showLoading(context); + // Utils.showLoading(context); // Commented out: causes BuildContext error after screen is popped bool isPaid = await paymentRepo.verifyPayment( paymentTypeId: paymentTypeId, id: id, isForAppointments: isForAppointment, appointmentIds: appointmentIds, ); - Utils.hideLoading(context); + // Utils.hideLoading(context); // Commented out: matching pair with showLoading // TODO: VERIFY THIS WITH ZAHOOR if (!isPaid) { @@ -116,12 +116,13 @@ class PaymentVM extends ChangeNotifier { } } - Future placeThePayment({required PaymentTypes paymentTypeEnum, required BuildContext context}) async { + Future placeThePayment({required PaymentTypes paymentTypeEnum, required BuildContext context, Function()? onExit}) async { await paymentService.placePayment( id: getIdTypeByPaymentType(paymentTypeEnum), // This will be request ID for request payment || ad ID for Ad Related Payment || OrderProviderSubscriptionID for SubscriptionPayment appointmentIds: appointmentIdsForPayment, paymentType: paymentTypeEnum, + onExit: onExit, onFailure: () { Utils.showToast(LocaleKeys.paymentFailed.tr()); switch (paymentTypeEnum) { @@ -219,26 +220,26 @@ class PaymentVM extends ChangeNotifier { navigateReplaceWithNameUntilRoute(context, AppRoutes.dashboard); } - Future onVisaCardSelected(BuildContext context, PaymentTypes paymentType) async { + Future onVisaCardSelected(BuildContext context, PaymentTypes paymentType, {Function()? onExit}) async { currentPaymentType = paymentType; switch (currentPaymentType) { case PaymentTypes.appointment: case PaymentTypes.partialAppointment: if (appointmentIdsForPayment.isEmpty) return; - await placeThePayment(context: context, paymentTypeEnum: paymentType); + await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit); break; case PaymentTypes.request: if (requestId == -1) return; - await placeThePayment(context: context, paymentTypeEnum: paymentType); + await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit); break; case PaymentTypes.subscription: - await placeThePayment(context: context, paymentTypeEnum: paymentType); + await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit); break; case PaymentTypes.ads: case PaymentTypes.adReserve: case PaymentTypes.extendAds: if (currentAdId == -1) return; - await placeThePayment(context: context, paymentTypeEnum: paymentType); + await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit); break; } } diff --git a/lib/view_models/requests_view_model.dart b/lib/view_models/requests_view_model.dart index 94dc060..a1639d2 100644 --- a/lib/view_models/requests_view_model.dart +++ b/lib/view_models/requests_view_model.dart @@ -45,6 +45,9 @@ class RequestsVM extends BaseVM { List requestsTypeFilterOptions = []; + // Spare parts condition filters (All, New, Used) + List sparePartsConditionFilterOptions = []; + List myRequestsTypeEnum = []; List vehicleConditionsEnum = []; @@ -157,9 +160,24 @@ class RequestsVM extends BaseVM { requestCityIdApi = vehicleCityId.selectedId; } 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; + log("DEBUG - Using vehicle condition filter: $requestConditionIdApi"); } + int requestVehicleYearIdApi = 0; if (vehicleYearId.selectedId != -1) { requestVehicleYearIdApi = vehicleYearId.selectedId; @@ -168,6 +186,7 @@ class RequestsVM extends BaseVM { if (requestStatusId.selectedId != -1) { requestStatusIdApi = requestStatusId.selectedId; } + log("requestConditionIdApi:${requestConditionIdApi}"); try { myFilteredRequests = await requestRepo.getRequestsBasedOnFilters( @@ -257,17 +276,52 @@ class RequestsVM extends BaseVM { notifyListeners(); } - applyFilterOnRequestsVM({required RequestsTypeEnum requestsTypeEnum}) async { + applyFilterOnRequestsVM({required RequestsTypeEnum requestsTypeEnum, int? conditionId}) async { if (requestsTypeFilterOptions.isEmpty) return; for (var value in requestsTypeFilterOptions) { value.isSelected = false; } 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(); isOfferSent = false; 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 pickedVehicleImages = []; String vehicleImageError = ""; @@ -334,6 +388,13 @@ class RequestsVM extends BaseVM { } } + // Initialize spare parts condition filters (All, New, Used) + if (sparePartsConditionFilterOptions.isEmpty) { + sparePartsConditionFilterOptions.add(FilterListModel(title: "All", isSelected: true, id: 3)); // All + sparePartsConditionFilterOptions.add(FilterListModel(title: "New Spare Part", isSelected: false, id: 1)); // New + sparePartsConditionFilterOptions.add(FilterListModel(title: "Used Spare Part", isSelected: false, id: 2)); // Used + } + if (vehicleBrands.isEmpty) { vehicleBrands = await commonRepo.getVehicleBrands(vehicleTypeId: -1); } @@ -815,8 +876,8 @@ class RequestsVM extends BaseVM { brand: brand, model: model, year: vehicleYearId.selectedOption, - isNew: vehicleConditionId.selectedId == 1, - // 1 for new, 2 for Used + conditionType: vehicleConditionId.selectedId, + // conditionType: 1 for New, 2 for Used countryID: vehicleCountryId.selectedId, cityID: vehicleCityId.selectedId, price: price.isEmpty ? "1.0" : price, @@ -830,7 +891,13 @@ class RequestsVM extends BaseVM { log("requestTypeId.selectedId.toRequestTypeEnum(): ${requestTypeId.selectedId.toRequestTypeEnum()}"); Utils.showToast(LocaleKeys.requestSuccessfullyCreated.tr()); 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(); } else { Utils.showToast(respModel.message.toString()); diff --git a/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart b/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart index 2b5bf2b..7461c5b 100644 --- a/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart +++ b/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart @@ -259,6 +259,22 @@ class VehicleDetails extends StatelessWidget { hint: LocaleKeys.odometerReading.tr(), 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, @@ -304,68 +320,31 @@ class VehicleDetails extends StatelessWidget { onChanged: (v) => adVM.updateVehicleDescription(v), ), 22.height, - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + // Finance Available toggle + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.financeAvailable.tr().toText(fontSize: 16), - 8.height, - Container( - width: 50, - height: 30, - decoration: BoxDecoration( - color: adVM.financeAvailableStatus ? MyColors.darkPrimaryColor : MyColors.white, - borderRadius: BorderRadius.circular(25.0), - border: Border.all(color: MyColors.black, width: 1), - ), - child: Transform.scale( - scale: 0.8, - child: CupertinoSwitch( - activeColor: MyColors.darkPrimaryColor, - trackColor: MyColors.white, - thumbColor: MyColors.grey98Color, - value: adVM.financeAvailableStatus, - onChanged: (value) { - adVM.updateFinanceAvailableStatus(value); - }, - ), - ), - ), - ], + LocaleKeys.financeAvailable.tr().toText(fontSize: 16), + 8.height, + Container( + width: 50, + height: 30, + decoration: BoxDecoration( + color: adVM.financeAvailableStatus ? MyColors.darkPrimaryColor : MyColors.white, + borderRadius: BorderRadius.circular(25.0), + border: Border.all(color: MyColors.black, width: 1), ), - ), - 8.width, - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.currentlyInUse.tr().toText(fontSize: 16), - 8.height, - Container( - width: 50, - height: 30, - decoration: BoxDecoration( - color: adVM.isInUsed == 1 ? MyColors.darkPrimaryColor : MyColors.white, - borderRadius: BorderRadius.circular(25.0), - border: Border.all(color: MyColors.black, width: 1), - ), - child: Transform.scale( - scale: 0.8, - child: CupertinoSwitch( - activeColor: MyColors.darkPrimaryColor, - trackColor: MyColors.white, - thumbColor: MyColors.grey98Color, - value: adVM.isInUsed == 1, - onChanged: (value) { - adVM.updateIsInUsed(value ? 1 : 0); - }, - ), - ), - ), - ], + 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); + }, + ), ), ), ], diff --git a/lib/views/advertisement/ads_detail_view/ads_detail_view.dart b/lib/views/advertisement/ads_detail_view/ads_detail_view.dart index 77a02fa..c7039e4 100644 --- a/lib/views/advertisement/ads_detail_view/ads_detail_view.dart +++ b/lib/views/advertisement/ads_detail_view/ads_detail_view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -24,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:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:sizer/sizer.dart'; class AdsDetailView extends StatefulWidget { final AdDetailsModel adDetails; @@ -91,7 +94,22 @@ class _AdsDetailViewState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, 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( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -545,3 +563,179 @@ class _AdsDetailViewState extends State { ); } } + +class FavoriteAdButton extends StatefulWidget { + final AdDetailsModel adDetails; + + const FavoriteAdButton({super.key, required this.adDetails}); + + @override + State createState() => _FavoriteAdButtonState(); +} + +class _FavoriteAdButtonState extends State with SingleTickerProviderStateMixin { + bool isLoading = false; + late bool isFavorite; + late AnimationController _animationController; + late Animation _scaleAnimation; + + @override + void initState() { + super.initState(); + isFavorite = widget.adDetails.isAdFavorite ?? false; + + _animationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + + _scaleAnimation = Tween(begin: 1.0, end: 1.3).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), + ); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + Future toggleFavorite() async { + if (isLoading) return; + + setState(() { + isLoading = true; + }); + + _animationController.forward().then((_) => _animationController.reverse()); + + bool success = await context.read().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(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, + ), + ), + ), + ); + } +} diff --git a/lib/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart b/lib/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart index 26b26bf..394d147 100644 --- a/lib/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart +++ b/lib/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart @@ -125,6 +125,8 @@ class AdDurationSelectionSheet extends StatelessWidget { isBorderRequired: adDuration.id == adVM.vehicleAdDurationId.selectedId, ), ).onPress(() { + + log("isFromExtendAd: $isFromExtendAd"); if (isFromExtendAd) { adVM.updateVehicleExtendAdDurationId( SelectionModel( diff --git a/lib/views/advertisement/components/ads_list_widget.dart b/lib/views/advertisement/components/ads_list_widget.dart index a38905d..3f20576 100644 --- a/lib/views/advertisement/components/ads_list_widget.dart +++ b/lib/views/advertisement/components/ads_list_widget.dart @@ -1,4 +1,5 @@ import 'dart:developer'; +import 'dart:ui'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -110,20 +111,54 @@ class AdsListWidget extends StatelessWidget { } } -class AdCard extends StatelessWidget { +class AdCard extends StatefulWidget { final AdDetailsModel adDetails; final bool isAdsFragment; final bool shouldShowAdStatus; + final bool needBorder; - const AdCard({super.key, required this.adDetails, required this.isAdsFragment, required this.shouldShowAdStatus}); + const AdCard({ + super.key, + required this.adDetails, + required this.isAdsFragment, + required this.shouldShowAdStatus, + this.needBorder = true, + }); + + @override + State createState() => _AdCardState(); +} + +class _AdCardState extends State { + int _currentImageIndex = 0; + late PageController _pageController; + + @override + void initState() { + super.initState(); + _pageController = PageController(); + } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } @override 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( alignment: Alignment.center, children: [ - if (adDetails.vehicle == null) ...[ + if (widget.adDetails.vehicle == null) ...[ Expanded( child: Column( children: [ @@ -134,10 +169,97 @@ class AdCard extends StatelessWidget { ] else ...[ Row( children: [ - adDetails.vehicle!.image!.first.imageUrl.buildNetworkImage( - width: 80, - height: 80, - fit: BoxFit.cover, + Stack( + alignment: Alignment.center, + children: [ + 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, Expanded( @@ -179,7 +301,11 @@ class AdCard extends StatelessWidget { chipColor: Utils.getChipColorByAdStatus(adDetails.adPostStatus!), ), ], - (adDetails.vehicle!.vehicleTitle ?? "").toText( + // Show Accident Free chip for explore ads when isAccidentFree is true + if (isAdsFragment && context.read().isExploreAdsTapped && widget.adDetails.vehicle?.isAccidentFree == true) ...[ + Utils.statusContainerChip(text: LocaleKeys.accidentFree.tr(), chipColor: Colors.green), + ], + (widget.adDetails.vehicle!.vehicleTitle ?? "").toText( fontSize: 16, letterSpacing: -0.64, height: 25 / 16, @@ -193,7 +319,7 @@ class AdCard extends StatelessWidget { height: 18 / 12, ), 2.width, - (adDetails.vehicle!.modelyear!.label ?? "").toText( + (widget.adDetails.vehicle!.modelyear!.label ?? "").toText( fontSize: 12, letterSpacing: -0.48, height: 18 / 12, @@ -209,7 +335,7 @@ class AdCard extends StatelessWidget { height: 18 / 12, ), 2.width, - (adDetails.vehicle!.mileage!.mileageEnd ?? "").toText( + (widget.adDetails.vehicle!.mileage!.mileageEnd ?? "").toText( fontSize: 12, letterSpacing: -0.48, height: 18 / 12, @@ -223,10 +349,8 @@ class AdCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.start, children: [ - (adDetails.vehicle!.cityName ?? "").toText(color: MyColors.lightTextColor), - // adDetails.createdOn != null ? DateTime.parse(adDetails.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor) : const SizedBox(), - adDetails.createdOn != null - ? DateTime.parse(adDetails.createdOn!).getTimeAgo(isArabic: isArabic).toText(color: MyColors.lightTextColor) : const SizedBox(), + (widget.adDetails.vehicle!.cityName ?? "").toText(color: MyColors.lightTextColor), + widget.adDetails.createdOn != null ? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor) : const SizedBox(), ], ), ], @@ -239,9 +363,9 @@ class AdCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, 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, - (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), ], ), ), @@ -284,6 +408,7 @@ class AdCard extends StatelessWidget { ).toWhiteContainer( width: double.infinity, allPading: 12, + isBorderRequired: widget.needBorder, ); } } diff --git a/lib/views/advertisement/favourite_ads_view.dart b/lib/views/advertisement/favourite_ads_view.dart new file mode 100644 index 0000000..fa8eee0 --- /dev/null +++ b/lib/views/advertisement/favourite_ads_view.dart @@ -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 createState() => _FavouriteAdsViewState(); +} + +class _FavouriteAdsViewState extends State { + int selectedBrandId = 0; + + @override + void initState() { + scheduleMicrotask(() async { + final adVM = context.read(); + await adVM.getFavouriteAds(); + // Load filter options if not already loaded + if (adVM.exploreAdsFilterOptions.isEmpty) { + await adVM.populateAdsFilterList(); + } + }); + super.initState(); + } + + List 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, + ), + ), + ), + ], + ), + ), + ); + }); + } +} diff --git a/lib/views/common_fragments/ads_fragment.dart b/lib/views/common_fragments/ads_fragment.dart index 71329e0..6e01f16 100644 --- a/lib/views/common_fragments/ads_fragment.dart +++ b/lib/views/common_fragments/ads_fragment.dart @@ -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/filters_list.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'; class AdsFragment extends StatelessWidget { @@ -147,7 +148,7 @@ class AdsFragment extends StatelessWidget { } }, child: adVM.state == ViewState.busy - ? const Center(child: CircularProgressIndicator()) + ? const AdsListShimmer(itemCount: 5) : AdsListWidget( isAdsFragment: true, isDraftAds: false, diff --git a/lib/views/common_fragments/requests_fragment.dart b/lib/views/common_fragments/requests_fragment.dart index 33d4e9c..823bb2d 100644 --- a/lib/views/common_fragments/requests_fragment.dart +++ b/lib/views/common_fragments/requests_fragment.dart @@ -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/filters_list.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:easy_localization/easy_localization.dart'; @@ -124,12 +125,22 @@ class MyRequestsFragment extends StatelessWidget { 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, Expanded( child: RefreshIndicator( onRefresh: () async => await requestsVM.getRequestsBasedOnFilters(), child: requestsVM.state == ViewState.busy - ? const Center(child: CircularProgressIndicator()) + ? const RequestsListShimmer(itemCount: 5) : requestsVM.myFilteredRequests.isEmpty ? Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/views/payments/payment_methods_view.dart b/lib/views/payments/payment_methods_view.dart index d7b72db..870e69f 100644 --- a/lib/views/payments/payment_methods_view.dart +++ b/lib/views/payments/payment_methods_view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.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:easy_localization/easy_localization.dart'; -class PaymentMethodsView extends StatelessWidget { +class PaymentMethodsView extends StatefulWidget { final PaymentTypes paymentType; final Function? onTempContinue; const PaymentMethodsView({Key? key, required this.paymentType, this.onTempContinue}) : super(key: key); + @override + State createState() => _PaymentMethodsViewState(); +} + +class _PaymentMethodsViewState extends State { + @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().onVisaCardSelected(context, widget.paymentType); + } + }); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: CustomAppBar( - title: LocaleKeys.paymentMethod.tr(), - profileImageUrl: MyAssets.bnCar, - isRemoveBackButton: false, - isDrawerEnabled: false, - ), - body: Container( - padding: const EdgeInsets.only(bottom: 10, left: 21, right: 21), + body: Center( child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ - 20.height, - LocaleKeys.selectPaymentMethod.tr().toText(fontSize: 18, isBold: true), - 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), - ], - ), - ); - }, + const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(MyColors.darkPrimaryColor), ), - SizedBox( - width: double.infinity, - child: ShowFillButton( - maxHeight: 55, - title: LocaleKeys.continu.tr(), - onPressed: () async { - bool isNeedToCallPayment = true; - if (onTempContinue != null) { - isNeedToCallPayment = await onTempContinue!(); - } - if (isNeedToCallPayment) { - context.read().onContinuePressed(context, paymentType: paymentType); - } - }, - backgroundColor: MyColors.darkPrimaryColor, - ), + 24.height, + LocaleKeys.pleaseWait.tr().toText( + fontSize: 16, + color: MyColors.darkTextColor, ), ], ), diff --git a/lib/views/setting_options/setting_options_more.dart b/lib/views/setting_options/setting_options_more.dart index d741835..62483dd 100644 --- a/lib/views/setting_options/setting_options_more.dart +++ b/lib/views/setting_options/setting_options_more.dart @@ -83,9 +83,9 @@ class _SettingOptionsMoreState extends State { if (AppState().currentAppType == AppType.customer) ...[ CustomSettingOptionsTile( leadingWidget: const Icon(Icons.favorite, size: 20), - titleText: LocaleKeys.favoriteList.tr(), + titleText: LocaleKeys.myFavouriteProviders.tr(), needBorderBelow: true, - onTap: () => navigateWithName(context, AppRoutes.favoriteListView), + onTap: () => navigateWithName(context, AppRoutes.favoriteProvidersView), ), ] else ...[ CustomSettingOptionsTile( @@ -112,6 +112,12 @@ class _SettingOptionsMoreState extends State { 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( leadingWidget: SvgPicture.asset( MyAssets.icAds, diff --git a/lib/views/splash/splash_page.dart b/lib/views/splash/splash_page.dart index e99dd8e..3208edf 100644 --- a/lib/views/splash/splash_page.dart +++ b/lib/views/splash/splash_page.dart @@ -15,11 +15,31 @@ class SplashPage extends StatefulWidget { } class SplashPageState extends State with WidgetsBindingObserver { + bool showFlashingLights = false; + @override void initState() { super.initState(); 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 @@ -33,7 +53,7 @@ class SplashPageState extends State with WidgetsBindingObserver { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.resumed) { final chatVM = context.read(); - chatVM.buildHubConnection(context); + chatVM.buildHubConnection(context: context); } } @@ -68,12 +88,22 @@ class SplashPageState extends State with WidgetsBindingObserver { // Car Image (flex value adjusted to make it more responsive) Expanded( flex: 10, - child: Image.asset( - MyAssets.bnCar, - fit: BoxFit.cover, - width: double.infinity, - height: double.infinity, // Use full height in this expanded section - ), + child: showFlashingLights + ? Image.asset( + MyAssets.bnCarFlashingLights, + fit: BoxFit.contain, + 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 Expanded( diff --git a/lib/widgets/common_widgets/add_phone_num_wiget.dart b/lib/widgets/common_widgets/add_phone_num_wiget.dart index 8736f25..3762f80 100644 --- a/lib/widgets/common_widgets/add_phone_num_wiget.dart +++ b/lib/widgets/common_widgets/add_phone_num_wiget.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; -import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; @@ -51,67 +50,69 @@ class _AddPhoneNumWidgetState extends State { padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: Container( width: double.infinity, - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16), + padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - LocaleKeys.enterPhoneNumber.tr().toText(fontSize: 16, isBold: true), - 8.height, - Container( - decoration: Utils.containerColorRadiusBorderWidth(MyColors.white, 0, MyColors.darkPrimaryColor, 2), - margin: const EdgeInsets.all(0), - padding: const EdgeInsets.only(left: 8, right: 8), - width: null, - child: Row( - children: [ - if (countryCode.isEmpty) ...[ - LocaleKeys.selectCountryCode.tr().toText(color: borderColor, fontSize: 15, fontWeight: MyFonts.Medium).paddingOnly(top: 12, bottom: 12), - ] else ...[ - countryCode.toText(fontSize: 15, fontWeight: MyFonts.Medium).paddingOnly(top: 12, bottom: 12), - ] - ], + LocaleKeys.enterPhoneNumber.tr().toText(fontSize: 20, isBold: true, letterSpacing: -1.44), + 30.height, + if (_country != null) ...[ + DropdownField( + dropdownValue: countryCodeVal, + (DropValue value) { + AppState().setUserRegisterCountrySelection = value; + countryCode = value.subValue; + countryCodeVal = value; + setState(() {}); + }, + list: _country!.data?.map((element) { + return DropValue( + element.id ?? 0, + "${element.countryName ?? ""} ${element.countryCode ?? ""}", + element.countryCode ?? "", + ); + }).toList() ?? [], + hint: LocaleKeys.selectCountryCode.tr(), ), - ).onPress(() { - if (_country != null) { - getCountry(context); - } - }), - 8.height, - TxtField( - keyboardType: TextInputType.phone, - hint: LocaleKeys.phoneWithoutCountryCode.tr(), - value: phoneNum, - isSidePaddingZero: true, - onChanged: (v) { - // Remove country code if user enters it - String cleanedPhone = v.trim(); - - // If country code is selected and phone starts with it, remove it - if (countryCode.isNotEmpty && cleanedPhone.startsWith(countryCode)) { - cleanedPhone = cleanedPhone.substring(countryCode.length).trim(); - } + 10.height, + TxtField( + keyboardType: TextInputType.phone, + hint: LocaleKeys.enterPhoneNumber.tr(), + value: phoneNum, + isSidePaddingZero: true, + onChanged: (v) { + // Remove country code if user enters it + String cleanedPhone = v.trim(); + + // If country code is selected and phone starts with it, remove it + if (countryCode.isNotEmpty && cleanedPhone.startsWith(countryCode)) { + cleanedPhone = cleanedPhone.substring(countryCode.length).trim(); + } - // Also handle cases where user adds + before country code - if (countryCode.isNotEmpty && cleanedPhone.startsWith('+$countryCode')) { - cleanedPhone = cleanedPhone.substring(countryCode.length + 1).trim(); - } + // Also handle cases where user adds + before country code + if (countryCode.isNotEmpty && cleanedPhone.startsWith('+$countryCode')) { + cleanedPhone = cleanedPhone.substring(countryCode.length + 1).trim(); + } - // Handle case where user just types + followed by digits matching country code - if (countryCode.isNotEmpty && cleanedPhone.startsWith('+')) { - String codeWithoutPlus = countryCode.replaceAll('+', ''); - if (cleanedPhone.substring(1).startsWith(codeWithoutPlus)) { - cleanedPhone = cleanedPhone.substring(codeWithoutPlus.length + 1).trim(); + // Handle case where user just types + followed by digits matching country code + if (countryCode.isNotEmpty && cleanedPhone.startsWith('+')) { + String codeWithoutPlus = countryCode.replaceAll('+', ''); + if (cleanedPhone.substring(1).startsWith(codeWithoutPlus)) { + cleanedPhone = cleanedPhone.substring(codeWithoutPlus.length + 1).trim(); + } } - } - setState(() { - phoneNum = cleanedPhone; - }); - }, - ), - 16.height, + setState(() { + phoneNum = cleanedPhone; + }); + }, + ), + ] else ...[ + const Center(child: CircularProgressIndicator()), + ], + 30.height, Row( children: [ Expanded( @@ -147,22 +148,6 @@ class _AddPhoneNumWidgetState extends State { ); } - void getCountry(BuildContext context) { - List 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 isValid = true; if (countryCode.isEmpty) { @@ -177,73 +162,3 @@ class _AddPhoneNumWidgetState extends State { return isValid; } } - -class CustomBottomPicker extends StatelessWidget { - final List 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 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, - ); - }, - ); -} diff --git a/lib/widgets/common_widgets/provider_details_card.dart b/lib/widgets/common_widgets/provider_details_card.dart index 1167613..c0a2dd2 100644 --- a/lib/widgets/common_widgets/provider_details_card.dart +++ b/lib/widgets/common_widgets/provider_details_card.dart @@ -46,44 +46,44 @@ class ProviderDetailCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - title.toText(fontSize: 16, isBold: true), - Row( - children: [ - ("${LocaleKeys.totalBranches.tr()}:").toText(color: MyColors.lightTextColor, fontSize: 12), - 4.width, - totalBranches.toText(fontSize: 12, isBold: true), - ], - ), - if (description.isNotEmpty) ...[ - Row( - children: [ - ("${LocaleKeys.description.tr()}:").toText(color: MyColors.lightTextColor, fontSize: 12), - 4.width, - description.toText(fontSize: 12, isBold: true), - ], - ), - ], - Row( - children: [ - (isFromFavoriteList ? LocaleKeys.addToFavoritesOn.tr() : LocaleKeys.memberSince.tr()).toText(color: MyColors.lightTextColor, fontSize: 12), - 4.width, - startedSince.toText(fontSize: 12, isBold: true), - ], - ), - ], - ), - ), - ], - ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + title.toText(fontSize: 16, isBold: true), + Row( + children: [ + ("${LocaleKeys.totalBranches.tr()}:").toText(color: MyColors.lightTextColor, fontSize: 12), + 4.width, + totalBranches.toText(fontSize: 12, isBold: true), + ], + ), + if (description.isNotEmpty) ...[ + Row( + children: [ + ("${LocaleKeys.description.tr()}:").toText(color: MyColors.lightTextColor, fontSize: 12), + 4.width, + description.toText(fontSize: 12, isBold: true), + ], + ), + ], + Row( + children: [ + (isFromFavoriteList ? LocaleKeys.addToFavoritesOn.tr() : LocaleKeys.memberSince.tr()).toText(color: MyColors.lightTextColor, fontSize: 12), + 4.width, + startedSince.toText(fontSize: 12, isBold: true), + ], + ), + ], + ), + ), + ], + ), ], ), ), diff --git a/lib/widgets/dropdown/dropdow_field.dart b/lib/widgets/dropdown/dropdow_field.dart index ad27687..4a7b0be 100644 --- a/lib/widgets/dropdown/dropdow_field.dart +++ b/lib/widgets/dropdown/dropdow_field.dart @@ -75,12 +75,7 @@ class _DropdownFieldState extends State { // Show disabled text field when not selectable if (!widget.isSelectAble) Container( - decoration: Utils.containerColorRadiusBorderWidth( - MyColors.greyShadowColor, - 0, - MyColors.greyACColor, - 2 - ), + decoration: Utils.containerColorRadiusBorderWidth(MyColors.greyShadowColor, 0, MyColors.greyACColor, 2), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), width: double.infinity, child: (dropdownValue?.value ?? "").toText( @@ -90,16 +85,9 @@ class _DropdownFieldState extends State { ), ) else - // Show normal dropdown when selectable + // Show normal dropdown when selectable Container( - decoration: widget.showAppointmentPickerVariant - ? null - : Utils.containerColorRadiusBorderWidth( - MyColors.white, - 0, - MyColors.darkPrimaryColor, - 2 - ), + decoration: widget.showAppointmentPickerVariant ? null : Utils.containerColorRadiusBorderWidth(MyColors.white, 0, MyColors.darkPrimaryColor, 2), margin: const EdgeInsets.all(0), padding: const EdgeInsets.only(left: 8, right: 8), width: widget.showAppointmentPickerVariant ? 170 : null, @@ -127,15 +115,11 @@ class _DropdownFieldState extends State { }, onTap: widget.onTap, items: (widget.list ?? defaultV).map>( - (DropValue value) { + (DropValue value) { return DropdownMenuItem( value: value, enabled: value.isEnabled ?? true, - child: value.value.toText( - fontSize: 15, - color: value.isEnabled == false ? MyColors.darkTextColor : null, - fontWeight: MyFonts.Medium - ), + child: value.value.toText(fontSize: 15, color: value.isEnabled == false ? MyColors.darkTextColor : null, fontWeight: MyFonts.Medium), ); }, ).toList(), diff --git a/lib/widgets/extensions/extensions_widget.dart b/lib/widgets/extensions/extensions_widget.dart index e7fe767..e869e20 100644 --- a/lib/widgets/extensions/extensions_widget.dart +++ b/lib/widgets/extensions/extensions_widget.dart @@ -371,7 +371,10 @@ extension BuildSVG on String? { return SizedBox( height: height, 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, width: width, 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) { diff --git a/lib/widgets/shimmer/home_shimmer_widgets.dart b/lib/widgets/shimmer/home_shimmer_widgets.dart new file mode 100644 index 0000000..ef8b273 --- /dev/null +++ b/lib/widgets/shimmer/home_shimmer_widgets.dart @@ -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, + ), + ); + }, + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..516fca5 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1487 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "9371d13b8ee442e3bfc08a24e3a1b3742c839abbfaf5eef11b79c4b862c89bf7" + url: "https://pub.dev" + source: hosted + version: "1.3.41" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + auto_size_text: + dependency: "direct main" + description: + name: auto_size_text + sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + badges: + dependency: "direct main" + description: + name: badges + sha256: a7b6bbd60dce418df0db3058b53f9d083c22cdb5132a052145dc267494df0b84 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819" + url: "https://pub.dev" + source: hosted + version: "3.4.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + carousel_slider: + dependency: "direct main" + description: + name: carousel_slider + sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + change_app_package_name: + dependency: "direct main" + description: + name: change_app_package_name + sha256: "8e43b754fe960426904d77ed4c62fa8c9834deaf6e293ae40963fa447482c4c5" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + country_code_picker: + dependency: "direct main" + description: + name: country_code_picker + sha256: f0411f4833b6f98e8b7215f4fa3813bcc88e50f13925f70a170dbd36e3e447f5 + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 + url: "https://pub.dev" + source: hosted + version: "10.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + diacritic: + dependency: transitive + description: + name: diacritic + sha256: "12981945ec38931748836cd76f2b38773118d0baef3c68404bdfde9566147876" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + dropdown_button2: + dependency: "direct main" + description: + name: dropdown_button2 + sha256: b0fe8d49a030315e9eef6c7ac84ca964250155a6224d491c1365061bc974a9e1 + url: "https://pub.dev" + source: hosted + version: "2.3.9" + easy_localization: + dependency: "direct main" + description: + name: easy_localization + sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed" + url: "https://pub.dev" + source: hosted + version: "3.0.8" + easy_logger: + dependency: transitive + description: + name: easy_logger + sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 + url: "https://pub.dev" + source: hosted + version: "0.0.2" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "825aec673606875c33cd8d3c4083f1a3c3999015a84178b317b7ef396b7384f3" + url: "https://pub.dev" + source: hosted + version: "8.0.7" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "06537da27db981947fa535bb91ca120b4e9cb59cb87278dbdde718558cafc9ff" + url: "https://pub.dev" + source: hosted + version: "3.4.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "8bcfad6d7033f5ea951d15b867622a824b13812178bfec0c779b9d81de011bbb" + url: "https://pub.dev" + source: hosted + version: "5.4.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88" + url: "https://pub.dev" + source: hosted + version: "2.17.5" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "29941ba5a3204d80656c0e52103369aa9a53edfd9ceae05a2bb3376f24fda453" + url: "https://pub.dev" + source: hosted + version: "15.1.0" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "26c5370d3a79b15c8032724a68a4741e28f63e1f1a45699c4f0a8ae740aadd72" + url: "https://pub.dev" + source: hosted + version: "4.5.43" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "58276cd5d9e22a9320ef9e5bc358628920f770f93c91221f8b638e8346ed5df4" + url: "https://pub.dev" + source: hosted + version: "3.8.13" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_inappwebview: + dependency: "direct main" + description: + name: flutter_inappwebview + sha256: "3e9a443a18ecef966fb930c3a76ca5ab6a7aafc0c7b5e14a4a850cf107b09959" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: d8c680abfb6fec71609a700199635d38a744df0febd5544c5a020bd73de8ee07 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 + url: "https://pub.dev" + source: hosted + version: "18.0.1" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" + url: "https://pub.dev" + source: hosted + version: "8.0.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "1c2b787f99bdca1f3718543f81d38aa1b124817dfeb9fb196201bea85b6134bf" + url: "https://pub.dev" + source: hosted + version: "2.0.26" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + geocoding: + dependency: "direct main" + description: + name: geocoding + sha256: "606be036287842d779d7ec4e2f6c9435fc29bbbd3c6da6589710f981d8852895" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + geocoding_android: + dependency: transitive + description: + name: geocoding_android + sha256: ba810da90d6633cbb82bbab630e5b4a3b7d23503263c00ae7f1ef0316dcae5b9 + url: "https://pub.dev" + source: hosted + version: "4.0.1" + geocoding_ios: + dependency: transitive + description: + name: geocoding_ios + sha256: "18ab1c8369e2b0dcb3a8ccc907319334f35ee8cf4cfef4d9c8e23b13c65cb825" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + geocoding_platform_interface: + dependency: transitive + description: + name: geocoding_platform_interface + sha256: "8c2c8226e5c276594c2e18bfe88b19110ed770aeb7c1ab50ede570be8b92229b" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: f4efb8d3c4cdcad2e226af9661eb1a0dd38c71a9494b22526f9da80ab79520e5 + url: "https://pub.dev" + source: hosted + version: "10.1.1" + geolocator_android: + dependency: "direct main" + description: + name: geolocator_android + sha256: "80cd39a94100957431a6dd37cebd686f6d446bbc9a884d90c9a07b34fd28b923" + url: "https://pub.dev" + source: hosted + version: "4.5.5" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 + url: "https://pub.dev" + source: hosted + version: "2.3.13" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" + url: "https://pub.dev" + source: hosted + version: "4.2.6" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: "102e7da05b48ca6bf0a5bda0010f886b171d1a08059f01bfe02addd0175ebece" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" + url: "https://pub.dev" + source: hosted + version: "0.2.5" + google_maps: + dependency: transitive + description: + name: google_maps + sha256: "5d410c32112d7c6eb7858d359275b2aa04778eed3e36c745aeae905fb2fa6468" + url: "https://pub.dev" + source: hosted + version: "8.2.0" + google_maps_flutter: + dependency: "direct main" + description: + name: google_maps_flutter + sha256: "621125e35e81ca39ef600e45243d2be93167e61def72bc7207b0c4a635c58506" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + google_maps_flutter_android: + dependency: transitive + description: + name: google_maps_flutter_android + sha256: "721ffae2240e957c04b0de19ffd4b68580adb57a8224496b7fb55fad23aec98a" + url: "https://pub.dev" + source: hosted + version: "2.14.13" + google_maps_flutter_ios: + dependency: transitive + description: + name: google_maps_flutter_ios + sha256: c7433645c4c9b61c587938cb06072f3dad601239e596b090c0f8f206c1f2ade7 + url: "https://pub.dev" + source: hosted + version: "2.15.2" + google_maps_flutter_platform_interface: + dependency: transitive + description: + name: google_maps_flutter_platform_interface + sha256: "970c8f766c02909c7be282dea923c971f83a88adaf07f8871d0aacebc3b07bb2" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + google_maps_flutter_web: + dependency: transitive + description: + name: google_maps_flutter_web + sha256: a45786ea6691cc7cdbe2cf3ce2c2daf4f82a885745666b4a36baada3a4e12897 + url: "https://pub.dev" + source: hosted + version: "0.5.12" + hexcolor: + dependency: "direct main" + description: + name: hexcolor + sha256: "0f237eed7db96ebacd8fda00d17f5ae262aaa84c213d53457c06b1dcbdfa81f2" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + iconsax_flutter: + dependency: transitive + description: + name: iconsax_flutter + sha256: d14b4cec8586025ac15276bdd40f6eea308cb85748135965bb6255f14beb2564 + url: "https://pub.dev" + source: hosted + version: "1.0.1" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a" + url: "https://pub.dev" + source: hosted + version: "0.8.12+21" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + injector: + dependency: "direct main" + description: + name: injector + sha256: d692c37788872bfd4bd7c01b864b0712190a25ae5a346431ff69949d5728a2e1 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + local_auth: + dependency: "direct main" + description: + name: local_auth + sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + local_auth_android: + dependency: transitive + description: + name: local_auth_android + sha256: "8bba79f4f0f7bc812fce2ca20915d15618c37721246ba6c3ef2aa7a763a90cf2" + url: "https://pub.dev" + source: hosted + version: "1.0.47" + local_auth_darwin: + dependency: transitive + description: + name: local_auth_darwin + sha256: "630996cd7b7f28f5ab92432c4b35d055dd03a747bc319e5ffbb3c4806a3e50d2" + url: "https://pub.dev" + source: hosted + version: "1.4.3" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" + url: "https://pub.dev" + source: hosted + version: "1.0.10" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + url: "https://pub.dev" + source: hosted + version: "1.0.11" + logger: + dependency: "direct main" + description: + name: logger + sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 + url: "https://pub.dev" + source: hosted + version: "2.6.2" + logging: + dependency: "direct main" + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + url: "https://pub.dev" + source: hosted + version: "1.0.6" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + pausable_timer: + dependency: transitive + description: + name: pausable_timer + sha256: "6ef1a95441ec3439de6fb63f39a011b67e693198e7dae14e20675c3c00e86074" + url: "https://pub.dev" + source: hosted + version: "3.1.0+3" + percent_indicator: + dependency: "direct main" + description: + name: percent_indicator + sha256: "157d29133bbc6ecb11f923d36e7960a96a3f28837549a20b65e5135729f0f9fd" + url: "https://pub.dev" + source: hosted + version: "4.2.5" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + url: "https://pub.dev" + source: hosted + version: "11.4.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + url: "https://pub.dev" + source: hosted + version: "12.1.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + sanitize_html: + dependency: transitive + description: + name: sanitize_html + sha256: "12669c4a913688a26555323fb9cec373d8f9fbe091f2d01c40c723b33caa8989" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: "59dfd53f497340a0c3a81909b220cfdb9b8973a91055c4e5ab9b9b9ad7c513c0" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b + url: "https://pub.dev" + source: hosted + version: "5.0.2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + signalr_core: + dependency: "direct main" + description: + name: signalr_core + sha256: "27c4ce798c8fedc2f7e3e4668c2b1dbcf6ee2a93f40ad24284b5f5bbed84529d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + simple_gesture_detector: + dependency: transitive + description: + name: simple_gesture_detector + sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3 + url: "https://pub.dev" + source: hosted + version: "0.2.1" + sizer: + dependency: "direct main" + description: + name: sizer + sha256: "9963c89e4d30d7c2108de3eafc0a7e6a4a8009799376ea6be5ef0a9ad87cfbad" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + url: "https://pub.dev" + source: hosted + version: "2.5.4+6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + url: "https://pub.dev" + source: hosted + version: "2.4.1+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sse: + dependency: transitive + description: + name: sse + sha256: fcc97470240bb37377f298e2bd816f09fd7216c07928641c0560719f50603643 + url: "https://pub.dev" + source: hosted + version: "4.1.8" + sse_channel: + dependency: transitive + description: + name: sse_channel + sha256: "9aad5d4eef63faf6ecdefb636c0f857bd6f74146d2196087dcf4b17ab5b49b1b" + url: "https://pub.dev" + source: hosted + version: "0.1.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" + table_calendar: + dependency: "direct main" + description: + name: table_calendar + sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + timezone: + dependency: transitive + description: + name: timezone + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + toastification: + dependency: "direct main" + description: + name: toastification + sha256: "4d97fbfa463dfe83691044cba9f37cb185a79bb9205cfecb655fa1f6be126a13" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + url: "https://pub.dev" + source: hosted + version: "6.3.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + uuid: + dependency: transitive + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" + url: "https://pub.dev" + source: hosted + version: "1.1.18" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + url: "https://pub.dev" + source: hosted + version: "1.1.16" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + url: "https://pub.dev" + source: hosted + version: "2.4.5" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" +sdks: + dart: ">=3.8.0-0 <4.0.0" + flutter: ">=3.24.0"