fixing jira bugs

faiz_development_common
Faiz Hashmi 3 days ago
parent 388e5b4aed
commit ff10b66b14

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

@ -254,6 +254,9 @@
"sendOTP": "إرسال رمز OTP",
"noUserAddedYet": "لم يتم إضافة مستخدم بعد",
"assignDealerUser": "تعيين مستخدم تاجر",
"confirmAssignDealerUser": "هل أنت متأكد من رغبتك في تعيين مستخدم التاجر هذا للفرع؟",
"removeDealerUser": "إزالة مستخدم تاجر",
"confirmRemoveDealerUser": "هل أنت متأكد من رغبتك في إزالة مستخدم التاجر هذا من الفرع؟",
"createNewUser": "إنشاء مستخدم جديد",
"dealerUser": "مستخدم تاجر",
"setorEditBranchSchedule": "تعيين أو تحرير جدول الفروع",
@ -590,6 +593,9 @@
"myNearbyBranches": "فروعي القريبة",
"myRecentBranches": "فروعي الأخيرة",
"favoriteList": "قائمة المفضلة",
"myFavouriteProviders": "مزودي الخدمة المفضلين",
"myFavouriteAds": "إعلاناتي المفضلة",
"noFavouriteAds": "لا توجد إعلانات مفضلة",
"noReviewsBranch": "هذا الفرع لا يوجد لديه تعليقات.",
"readLess": "اقرأ أقل",
"readMoreReviews": "اقرأ المزيد من التقييمات",
@ -885,6 +891,12 @@
"month": "شهر",
"currentPackage": "الحزمة الحالية",
"forVar": "لمدة",
"branch": "الفرع"
"branch": "الفرع",
"shareAd": "مشاركة الإعلان",
"checkOutThisAd": "تفقد هذه السيارة الرائعة!",
"downloadApp": "تحميل تطبيق موّاتر:",
"androidApp": "أندرويد",
"iosApp": "آيفون",
"pleaseWait": "يرجى الانتظار..."
}

@ -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",
@ -591,6 +594,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 +889,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..."
}

@ -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 {
},
);
}

@ -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";

@ -12,21 +12,20 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:mc_common_app/generated/locale_keys.g.dart';
extension EmailValidator on String {
Widget toText(
{Color? color,
bool isBold = false,
double? fontSize,
bool isUnderLine = false,
bool isItalic = false,
TextDecoration? textDecoration,
double letterSpacing = 0,
TextAlign? textAlign,
FontWeight? fontWeight,
double? height,
Color? decorationColor,
TextOverflow? overflow,
bool isUpperCase = false,
int? maxLines}) {
Widget toText({Color? color,
bool isBold = false,
double? fontSize,
bool isUnderLine = false,
bool isItalic = false,
TextDecoration? textDecoration,
double letterSpacing = 0,
TextAlign? textAlign,
FontWeight? fontWeight,
double? height,
Color? decorationColor,
TextOverflow? overflow,
bool isUpperCase = false,
int? maxLines}) {
bool isArabic = AppState().postParamsObject!.languageID == 1; // arabic -> 1 , English -> 2
return AutoSizeText(
isUpperCase ? toUpperCase() : this,
@ -290,6 +289,8 @@ extension AppointmentStatusToInt on AppointmentStatusEnum {
case AppointmentStatusEnum.upcoming:
return 11;
case AppointmentStatusEnum.served:
return 12;
case AppointmentStatusEnum.allAppointments:
return 0;
@ -386,6 +387,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;
}

@ -606,6 +606,9 @@ class CodegenLoader extends AssetLoader{
"myNearbyBranches": "فروعي القريبة",
"myRecentBranches": "فروعي الأخيرة",
"favoriteList": "قائمة المفضلة",
"myFavouriteProviders": "مزودي الخدمة المفضلين",
"myFavouriteAds": "إعلاناتي المفضلة",
"noFavouriteAds": "لا توجد إعلانات مفضلة",
"noReviewsBranch": "هذا الفرع لا يوجد لديه تعليقات.",
"readLess": "اقرأ أقل",
"readMoreReviews": "اقرأ المزيد من التقييمات",
@ -833,7 +836,7 @@ class CodegenLoader extends AssetLoader{
"specialRequestsChats": "دردشات الطلبات الخاصة",
"selectDeliveryOption": "اختر خيار التوصيل",
"deliveryOptions": "خيارات التوصيل",
"selfPickup": "الاستلام الذاتي",
"selfPickup": "استلام ذاتي",
"updateYourLocationInfo": "قم بتحديث معلومات موقعك",
"initiateSelfPickup": "بدء الاستلام الذاتي",
"selfPickupStatus": "حالة الاستلام الذاتي",
@ -841,43 +844,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 +904,13 @@ class CodegenLoader extends AssetLoader{
"month": "شهر",
"currentPackage": "الحزمة الحالية",
"forVar": "لمدة",
"branch": "الفرع"
"branch": "الفرع",
"shareAd": "مشاركة الإعلان",
"checkOutThisAd": "تفقد هذه السيارة الرائعة!",
"downloadApp": "تحميل تطبيق موّاتر:",
"androidApp": "أندرويد",
"iosApp": "آيفون",
"pleaseWait": "يرجى الانتظار..."
};
static const Map<String,dynamic> _en_US = {
"firstTimeLogIn": "First Time Log In",
@ -1496,6 +1505,9 @@ static const Map<String,dynamic> _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 +1776,12 @@ static const Map<String,dynamic> _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 +1797,16 @@ static const Map<String,dynamic> _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<String, Map<String,dynamic>> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US};
}

@ -2,7 +2,7 @@
// ignore_for_file: constant_identifier_names
abstract class LocaleKeys {
abstract class LocaleKeys {
static const firstTimeLogIn = 'firstTimeLogIn';
static const signUp = 'signUp';
static const changeMobile = 'changeMobile';
@ -49,7 +49,7 @@ abstract class LocaleKeys {
static const emailChangedSuccessfully = 'emailChangedSuccessfully';
static const passwordIsUpdated = 'passwordIsUpdated';
static const passwordShouldContains = 'passwordShouldContains';
static const successfullyRegistered = ' successfullyRegistered';
static const successfullyRegistered = ' successfullyRegistered';
static const pleaseEnterSamePassword = 'pleaseEnterSamePassword';
static const firstNameMandatory = 'firstNameMandatory';
static const surnameNameMandatory = 'surnameNameMandatory';
@ -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';
@ -571,6 +574,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 +843,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 +853,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 +873,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';
}

@ -45,6 +45,7 @@ class AdDetailsModel {
AdReserveStatus? adReserveStatus;
bool? isMyAd;
bool? isReservedByMe;
bool? isAdFavorite;
String? phoneNo;
String? whatsAppNo;
String? adOwnerName;
@ -93,6 +94,7 @@ class AdDetailsModel {
this.adReserveStatus,
this.isMyAd,
this.isReservedByMe,
this.isAdFavorite,
this.phoneNo,
this.whatsAppNo,
this.adOwnerName,
@ -161,6 +163,7 @@ class AdDetailsModel {
createdByRoleEnum = (json['createdByRole'] as int).toCreatedByRoleEnum();
isMyAd = isMyAds;
isReservedByMe = false;
isAdFavorite = json['isFavourite'] ?? false;
totalItemsCount = totalItems;
}
}

@ -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"],

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

@ -15,6 +15,7 @@ abstract class PaymentService {
required PaymentTypes paymentType,
required Function() onSuccess,
required Function() onFailure,
Function()? onExit,
});
Future<void> 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);

@ -178,6 +178,7 @@ enum AppointmentStatusEnum {
today,
past,
upcoming,
served,
}
enum AppointmentPaymentStatusEnum {

@ -36,7 +36,12 @@ class Utils {
toastification.show(
type: ToastificationType.info,
style: ToastificationStyle.minimal,
title: Text(message),
title: Text(
message,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
autoCloseDuration: const Duration(seconds: 2),
alignment: Alignment.bottomCenter,
showProgressBar: false,

@ -89,6 +89,7 @@ class AdVM extends BaseVM {
AdDetailsModel? previousAdDetails;
List<AdDetailsModel> myDraftAds = [];
List<AdDetailsModel> favouriteAds = [];
List<AdDetailsModel> exploreAds = [];
List<AdDetailsModel> exploreAdsFilteredList = [];
List<AdDetailsModel> myAdsFilteredList = [];
@ -337,6 +338,25 @@ class AdVM extends BaseVM {
return true;
}
Future<bool> toggleAdFavorite({required int adsID, required bool isFavorite}) async {
try {
GenericRespModel genericRespModel = await adsRepo.toggleAdFavorite(adsID: adsID, isFavorite: isFavorite);
if (genericRespModel.messageStatus == 1) {
if (genericRespModel.message != null && genericRespModel.message!.isNotEmpty) {
Utils.showToast(genericRespModel.message!);
}
return true;
} else {
Utils.showToast(genericRespModel.message ?? LocaleKeys.somethingWrong.tr());
return false;
}
} catch (e) {
logger.e(e.toString());
Utils.showToast(LocaleKeys.somethingWrong.tr());
return false;
}
}
Future<void> getExploreAds() async {
pageIndexForExploreAds = 1;
hasMoreDataForExploreAds = true;
@ -364,6 +384,18 @@ class AdVM extends BaseVM {
}
}
Future<void> getFavouriteAds() async {
try {
setState(ViewState.busy);
favouriteAds = await adsRepo.getFavouriteAds();
notifyListeners();
setState(ViewState.idle);
} catch (e) {
setState(ViewState.idle);
logger.e(e.toString());
}
}
Future<void> fetchMoreDraftAds() async {
if (isLoadingMore) return;
hasMoreDataForMyDraftsAds = true;

@ -2183,36 +2183,29 @@ class AppointmentsVM extends BaseVM {
}
}
Future<bool> addProviderToFavorite(
{required int serviceProviderID, required BuildContext context}) async {
Utils.showLoading(context);
Future<bool> 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<bool> removeProviderFromFavorite(
{required int serviceProviderID, required BuildContext context}) async {
Utils.showLoading(context);
Future<bool> 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 +2226,25 @@ class AppointmentsVM extends BaseVM {
}
}
Future<bool> 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<BranchRatingModel> currentBranchReviews = [];
bool isReadMoreEnabled = false;

@ -76,14 +76,14 @@ class PaymentVM extends ChangeNotifier {
Future<void> verifyPayments(
{required BuildContext context, required int id, List<int>? 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<void> placeThePayment({required PaymentTypes paymentTypeEnum, required BuildContext context}) async {
Future<void> 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<void> onVisaCardSelected(BuildContext context, PaymentTypes paymentType) async {
Future<void> 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;
}
}

@ -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<AdsDetailView> {
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: [
@ -529,3 +547,179 @@ class _AdsDetailViewState extends State<AdsDetailView> {
);
}
}
class FavoriteAdButton extends StatefulWidget {
final AdDetailsModel adDetails;
const FavoriteAdButton({super.key, required this.adDetails});
@override
State<FavoriteAdButton> createState() => _FavoriteAdButtonState();
}
class _FavoriteAdButtonState extends State<FavoriteAdButton> with SingleTickerProviderStateMixin {
bool isLoading = false;
late bool isFavorite;
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
isFavorite = widget.adDetails.isAdFavorite ?? false;
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.3).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
Future<void> toggleFavorite() async {
if (isLoading) return;
setState(() {
isLoading = true;
});
_animationController.forward().then((_) => _animationController.reverse());
bool success = await context.read<AdVM>().toggleAdFavorite(
adsID: widget.adDetails.id!,
isFavorite: isFavorite,
);
if (success) {
setState(() {
isFavorite = !isFavorite;
widget.adDetails.isAdFavorite = isFavorite;
isLoading = false;
});
} else {
setState(() {
isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: toggleFavorite,
child: Container(
width: 8.6.w,
height: 8.6.w,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: const Color(0xff000000).withOpacity(.07),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Center(
child: isLoading
? SizedBox(
width: 4.4.w,
height: 4.4.w,
child: const CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(MyColors.darkPrimaryColor),
),
)
: ScaleTransition(
scale: _scaleAnimation,
child: Icon(
isFavorite ? Icons.favorite : Icons.favorite_border,
color: isFavorite ? MyColors.darkPrimaryColor : MyColors.darkIconColor,
size: 5.5.w,
),
),
),
),
);
}
}
class ShareAdButton extends StatelessWidget {
final AdDetailsModel adDetails;
const ShareAdButton({super.key, required this.adDetails});
void _shareAd() {
final vehicle = adDetails.vehicle!;
// Build share message
String shareMessage = '';
// Add header
shareMessage += '${LocaleKeys.checkOutThisAd.tr()}\n\n';
// Vehicle details
shareMessage += '${vehicle.vehicleTitle ?? ""}\n';
shareMessage += '${LocaleKeys.model.tr()}: ${vehicle.modelyear?.label ?? ""}\n';
shareMessage += '${LocaleKeys.mileage.tr()}: ${vehicle.mileage?.mileageEnd ?? ""}\n';
shareMessage += '${LocaleKeys.price.tr()}: ${vehicle.demandAmount?.toInt()} ${vehicle.currency ?? ""}\n';
if (vehicle.cityName != null && vehicle.cityName!.isNotEmpty) {
shareMessage += '${LocaleKeys.location.tr()}: ${vehicle.cityName}\n';
}
shareMessage += '\n';
// App download links
shareMessage += '${LocaleKeys.downloadApp.tr()}\n';
if (Platform.isAndroid) {
shareMessage += '${LocaleKeys.androidApp.tr()}: https://play.google.com/store/apps/details?id=com.mowater.app\n';
} else if (Platform.isIOS) {
shareMessage += '${LocaleKeys.iosApp.tr()}: https://apps.apple.com/app/mowater/id123456789\n';
} else {
// Include both if platform detection fails
shareMessage += '${LocaleKeys.androidApp.tr()}: https://play.google.com/store/apps/details?id=com.mowater.app\n';
shareMessage += '${LocaleKeys.iosApp.tr()}: https://apps.apple.com/app/mowater/id123456789\n';
}
// Share using share_plus
Share.share(shareMessage);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _shareAd,
child: Container(
width: 8.6.w,
height: 8.6.w,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: const Color(0xff000000).withOpacity(.07),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Center(
child: Icon(
Icons.share,
color: MyColors.darkIconColor,
size: 5.5.w,
),
),
),
);
}
}

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

@ -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<AdCard> createState() => _AdCardState();
}
class _AdCardState extends State<AdCard> {
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,98 @@ 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(
borderRadius: BorderRadius.circular(8),
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(
@ -153,12 +276,12 @@ class AdCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
if (isAdsFragment && adDetails.adPostStatus! == AdPostStatus.sold && adDetails.statuslabel != null && adDetails.statuslabel!.isNotEmpty) ...[
Utils.statusContainerChip(text: adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(adDetails.adPostStatus!)),
] else if (isAdsFragment && !context.read<AdVM>().isExploreAdsTapped && adDetails.statuslabel != null && adDetails.statuslabel!.isNotEmpty) ...[
Utils.statusContainerChip(text: adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(adDetails.adPostStatus!)),
if (isAdsFragment && widget.adDetails.adPostStatus! == AdPostStatus.sold && widget.adDetails.statuslabel != null && widget.adDetails.statuslabel!.isNotEmpty) ...[
Utils.statusContainerChip(text: widget.adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(widget.adDetails.adPostStatus!)),
] else if (isAdsFragment && !context.read<AdVM>().isExploreAdsTapped && widget.adDetails.statuslabel != null && widget.adDetails.statuslabel!.isNotEmpty) ...[
Utils.statusContainerChip(text: widget.adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(widget.adDetails.adPostStatus!)),
],
(adDetails.vehicle!.vehicleTitle ?? "").toText(
(widget.adDetails.vehicle!.vehicleTitle ?? "").toText(
fontSize: 16,
letterSpacing: -0.64,
height: 25 / 16,
@ -172,7 +295,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,
@ -188,7 +311,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,
@ -202,8 +325,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(),
(widget.adDetails.vehicle!.cityName ?? "").toText(color: MyColors.lightTextColor),
widget.adDetails.createdOn != null ? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor) : const SizedBox(),
],
),
],
@ -216,9 +339,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),
],
),
),
@ -261,6 +384,7 @@ class AdCard extends StatelessWidget {
).toWhiteContainer(
width: double.infinity,
allPading: 12,
isBorderRequired: widget.needBorder,
);
}
}

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

@ -78,7 +78,7 @@ class SelectAdTypeView extends StatelessWidget {
onTap: () async {
adVM.updateSelectionVehicleTypeId(SelectionModel(selectedId: vehicleTypeModel.id!, selectedOption: vehicleTypeModel.vehicleTypeName ?? "", errorValue: ""));
if (adVM.isAdEditEnabled) {
adVM.autoFillSelectedVehicleAdsDetails();
adVM.autoFillSelectedVehicleAdsDetails(context);
}
if (AppState().currentAppType == AppType.provider) {
if (isFromExtendAd && !adVM.isAdEditEnabled) {

@ -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<PaymentMethodsView> createState() => _PaymentMethodsViewState();
}
class _PaymentMethodsViewState extends State<PaymentMethodsView> {
@override
void initState() {
super.initState();
// Automatically trigger payment flow when screen opens
scheduleMicrotask(() async {
// Show loading for 2 seconds so user can see it
await Future.delayed(const Duration(seconds: 2));
bool isNeedToCallPayment = true;
if (widget.onTempContinue != null) {
isNeedToCallPayment = await widget.onTempContinue!();
}
// Pop the loading screen BEFORE calling payment to avoid context issues
if (mounted) {
Navigator.of(context).pop();
}
if (isNeedToCallPayment && mounted) {
// Call payment which will open webview (context still works for PaymentVM)
await context.read<PaymentVM>().onVisaCardSelected(context, widget.paymentType);
}
});
}
@override
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<Color>(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<PaymentVM>().onContinuePressed(context, paymentType: paymentType);
}
},
backgroundColor: MyColors.darkPrimaryColor,
),
24.height,
LocaleKeys.pleaseWait.tr().toText(
fontSize: 16,
color: MyColors.darkTextColor,
),
],
),

@ -83,9 +83,9 @@ class _SettingOptionsMoreState extends State<SettingOptionsMore> {
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<SettingOptionsMore> {
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,

@ -15,11 +15,31 @@ class SplashPage extends StatefulWidget {
}
class SplashPageState extends State<SplashPage> 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
@ -68,12 +88,22 @@ class SplashPageState extends State<SplashPage> 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(

@ -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<AddPhoneNumWidget> {
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<AddPhoneNumWidget> {
);
}
void getCountry(BuildContext context) {
List<DropValue> dropList = [];
_country!.data?.forEach((element) {
dropList.add(DropValue(element.id ?? 0, "${element.countryName ?? ""} ${element.countryCode ?? ""}", element.countryCode ?? ""));
});
return showCustomBottomPicker(
items: dropList,
onItemSelected: (value) {
AppState().setUserRegisterCountrySelection = value;
countryCode = value.subValue;
countryCodeVal = value;
setState(() {});
},
context: context);
}
bool validation() {
bool isValid = true;
if (countryCode.isEmpty) {
@ -177,73 +162,3 @@ class _AddPhoneNumWidgetState extends State<AddPhoneNumWidget> {
return isValid;
}
}
class CustomBottomPicker extends StatelessWidget {
final List<DropValue> items;
final void Function(DropValue selectedItem) onItemSelected;
const CustomBottomPicker({
super.key,
required this.items,
required this.onItemSelected,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
color: Colors.black.withOpacity(0.4),
child: DraggableScrollableSheet(
initialChildSize: 0.2,
maxChildSize: 0.4,
minChildSize: 0.2,
expand: true,
builder: (context, scrollController) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: ListView.builder(
controller: scrollController,
itemCount: items.length,
shrinkWrap: true,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].value),
onTap: () {
onItemSelected(items[index]);
Navigator.of(context).pop();
},
);
},
),
);
},
),
),
);
}
}
// Function to show the bottom picker
void showCustomBottomPicker({
required BuildContext context,
required List<DropValue> items,
required void Function(DropValue selectedItem) onItemSelected,
}) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: false,
backgroundColor: Colors.transparent,
builder: (context) {
return CustomBottomPicker(
items: items,
onItemSelected: onItemSelected,
);
},
);
}

@ -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),
],
),
],
),
),
],
),
],
),
),

Loading…
Cancel
Save