Resolved Bugs

master
FaizHashmi 4 weeks ago
parent 76da9ae512
commit b6e6a14bf7

@ -481,6 +481,7 @@
"completeDeal": "إتمام الصفقة",
"uploadBankReceipt": "تحميل إيصال البنك",
"notesForAdmin": "ملاحظات للإدارة",
"note": "ملاحظة:",
"extendAd": "تمديد الإعلان",
"updateAdDetails": "هل تريد تحديث تفاصيل الإعلان",
"durationExtendingAd": "يمكنك تغيير مدة الإعلان والتفاصيل قبل تمديد الإعلان",

@ -485,6 +485,7 @@
"completeDeal": "Complete Deal",
"uploadBankReceipt": "Upload Bank Receipt",
"notesForAdmin": "Notes For Admin",
"note": "Note:",
"extendAd": "Extend Ad",
"updateAdDetails": "Do you want to update the Ad Details",
"durationExtendingAd": "You can change the ad duration and details before extending the ad",

@ -286,6 +286,7 @@ class GlobalConsts {
static int providerDealerRoleTypeId = 6;
static int providerIndividualRoleTypeId = 5;
static int dealershipUserRoleTypeId = 7;
}
class MyAssets {

@ -193,7 +193,7 @@ class AppRoutes {
loginWithPassword: (context) => LoginWithPassword(isRemoveBackButton: (ModalRoute.of(context)!.settings.arguments ?? true) as bool),
loginMethodSelection: (context) => LoginMethodSelectionPage(ModalRoute.of(context)!.settings.arguments as String),
completeProfile: (context) => CompleteProfilePage(ModalRoute.of(context)!.settings.arguments as RegisterUserRespModel),
verifyPassword: (context) => VerifyPasswordPage(),
// verifyPassword: (context) => VerifyPasswordPage(),
confirmNewPasswordPage: (context) => ConfirmNewPasswordPage(ModalRoute.of(context)!.settings.arguments as String),
forgetPasswordMethodPage: (context) => ForgetPasswordMethodPage(ModalRoute.of(context)!.settings.arguments as String),
changeMobilePage: (context) => ChangeMobilePage(),

@ -1230,6 +1230,8 @@ extension UserTypeExtension on UserType {
return "Provider - Dealership";
case UserType.providerIndividual:
return "Provider - Individual";
case UserType.dealershipUser:
return "Dealership User";
case UserType.customer:
return "Customer";
default:

@ -402,6 +402,7 @@ class CodegenLoader extends AssetLoader{
"appointmentBookedSuccessfully": "تم حجز موعدك بنجاح",
"appointmentStatusUpdated": "تم تحديث حالة الموعد",
"paymentStatusUpdated": "تم تحديث حالة الدفع",
"serviceStatusUpdated": "تم تحديث حالة الخدمة",
"selectOneSubService": "يرجى اختيار خدمة فرعية واحدة على الأقل",
"chargesBreakdown": "تفاصيل الرسوم",
"services": "الخدمات",
@ -496,6 +497,7 @@ class CodegenLoader extends AssetLoader{
"completeDeal": "إتمام الصفقة",
"uploadBankReceipt": "تحميل إيصال البنك",
"notesForAdmin": "ملاحظات للإدارة",
"note": "ملاحظة:",
"extendAd": "تمديد الإعلان",
"updateAdDetails": "هل تريد تحديث تفاصيل الإعلان",
"durationExtendingAd": "يمكنك تغيير مدة الإعلان والتفاصيل قبل تمديد الإعلان",
@ -958,7 +960,7 @@ static const Map<String,dynamic> _en_US = {
"continu": "Continue",
"loading": "Loading...",
"pleaseSelectAccidentFree": "Please select if the vehicle is accident free",
"pleaseSelectMileageIncrease": "Please select if mileage can be increased",
"pleaseSelectMileageIncrease": "Please select if mileage could increase",
"confirmPassword": "Confirm Password",
"createPassword": "Create Password",
"email": "Email",
@ -1322,6 +1324,7 @@ static const Map<String,dynamic> _en_US = {
"appointmentBookedSuccessfully": "Your appointment has been booked successfully",
"appointmentStatusUpdated": "Appointment Status Updated",
"paymentStatusUpdated": "Payment Status Updated",
"serviceStatusUpdated": "Service Status Updated",
"selectOneSubService": "Please select at least one sub service",
"chargesBreakdown": "Charges Breakdown",
"services": "Services",
@ -1365,7 +1368,7 @@ static const Map<String,dynamic> _en_US = {
"usedSparePart": "Used Spare Part",
"hasYourCarHadAccident": "Has your car had any accident?",
"odometerReading": "Odometer Reading (Km)",
"canMileageIncrease": "Can the mileage be increased when checking?",
"canMileageIncrease": "Mileage Could Increase?",
"currentlyInUse": "Currently in Use",
"demand": "Demand",
"adDurationExpired": "Your Ad Duration time is over",
@ -1417,6 +1420,7 @@ static const Map<String,dynamic> _en_US = {
"completeDeal": "Complete Deal",
"uploadBankReceipt": "Upload Bank Receipt",
"notesForAdmin": "Notes For Admin",
"note": "Note:",
"extendAd": "Extend Ad",
"updateAdDetails": "Do you want to update the Ad Details",
"durationExtendingAd": "You can change the ad duration and details before extending the ad",

@ -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';
@ -50,7 +50,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';
@ -462,6 +462,7 @@ abstract class LocaleKeys {
static const completeDeal = 'completeDeal';
static const uploadBankReceipt = 'uploadBankReceipt';
static const notesForAdmin = 'notesForAdmin';
static const note = 'note';
static const extendAd = 'extendAd';
static const updateAdDetails = 'updateAdDetails';
static const durationExtendingAd = 'durationExtendingAd';

@ -50,6 +50,7 @@ class AdDetailsModel {
bool? isMyAd;
bool? isReservedByMe;
bool? isAdFavorite;
bool? onBehalfOfCustomer;
int? favouriteAdID; // Favorite record ID (used when unfavoriting)
String? phoneNo;
String? whatsAppNo;
@ -104,6 +105,7 @@ class AdDetailsModel {
this.isMyAd,
this.isReservedByMe,
this.isAdFavorite,
this.onBehalfOfCustomer,
this.favouriteAdID,
this.phoneNo,
this.whatsAppNo,
@ -150,7 +152,7 @@ class AdDetailsModel {
taxPrice = json['taxPrice'];
totalPrice = json['totalPrice'];
userID = json['userID'];
vehiclePostingID = (json['vehiclePostingID'] == null || json['vehiclePostingID'] == 0) ? json['id'] : 0;
vehiclePostingID = (json['vehiclePostingID'] == null || json['vehiclePostingID'] == 0) ? json['id'] : json['vehiclePostingID'];
qrCodePath = json['qrCodePath'];
isCustomerAcknowledged = json['isCustomerAcknowledged'];
createdByRole = json['createdByRole'];
@ -177,6 +179,7 @@ class AdDetailsModel {
isMyAd = isMyAds;
isReservedByMe = false;
isAdFavorite = json['isFavourite'] ?? false;
onBehalfOfCustomer = json['onBehalfOfCustomer'] ?? false;
favouriteAdID = json['favouriteAdID'];
totalItemsCount = totalItems;
}
@ -284,8 +287,8 @@ class Vehicle {
vehicleType = json['vehicleType'];
vehicleVIN = json['vehicleVIN'];
countryID = json['countryID'];
countryName = json['duration'] != null && json['duration']['country'] != null ? json['duration']['country']['label'] : null;
countryNameN = json['duration'] != null && json['duration']['country'] != null ? json['duration']['country']['label'] : null;
countryName = json['countryName'];
countryNameN = json['countryNameN'] ?? json['countryName'];
currency = json['currency'];
odometer = json['odometer'];
isInUsed = json['isInUsed'];

@ -137,7 +137,7 @@ class UserInfo {
} else if (json["roleID"] == 6) {
AppState().userType = UserType.providerIndividual;
} else if (json["roleID"] == 7) {
AppState().userType = UserType.providerIndividual;
AppState().userType = UserType.dealershipUser;
} else {
AppState().userType = UserType.customer;
}

@ -141,7 +141,8 @@ class AdsRepoImp implements AdsRepo {
Future<GenericRespModel> createOrUpdateAd({required AdsCreationPayloadModel adsCreationPayloadModel, required bool isCreateNew, required bool isExtendAdEditEnabled}) async {
List vehiclePostingImages = [];
log("isExtendAdEditEnabled: $isExtendAdEditEnabled");
log("isCreateNew: $isCreateNew");
log("vehiclePostingID: $isExtendAdEditEnabled");
log("adsCreationPayloadModel.ads!.adsDurationID,: ${adsCreationPayloadModel.ads!.adsDurationID}");
adsCreationPayloadModel.vehiclePosting!.vehiclePostingImages?.forEach((element) {
var imageMap = {
@ -231,6 +232,7 @@ class AdsRepoImp implements AdsRepo {
@override
Future<GenericRespModel> createOrUpdateDraftAd({required AdsCreationPayloadModel adsCreationPayloadModel, required bool isCreateNew, required AdCreationStepsEnum stepNo}) async {
log("isNew: $isCreateNew |||createOrUpdateDraftAd stepNo: $stepNo ||| adsCreationPayloadModel: ${adsCreationPayloadModel.ads?.id}");
int sellerTypeID = AppState().getUser.data!.userInfo!.roleId ?? 0;
List vehiclePostingImages = [];
@ -602,7 +604,7 @@ class AdsRepoImp implements AdsRepo {
@override
Future<GenericRespModel> toggleAdFavorite({required int adsID, required bool isFavorite}) async {
var params = {
"adsID": adsID.toString(),
"ID": adsID.toString(),
};
String endpoint = isFavorite ? ApiConsts.unFavouriteAds : ApiConsts.favouriteAdsCreate;

@ -51,6 +51,7 @@ enum CreatedByRoleEnum { customer, provider, admin, allAds }
enum UserType {
providerDealer,
providerIndividual,
dealershipUser,
customer,
}

@ -42,7 +42,7 @@ class Utils {
overflow: TextOverflow.ellipsis,
),
autoCloseDuration: const Duration(seconds: 3),
alignment: Alignment.bottomCenter,
alignment: Alignment.topCenter,
showProgressBar: false,
);
}

@ -196,7 +196,7 @@ class AdVM extends BaseVM {
exploreAdsFilterOptions.clear();
myAdsFilterOptions.clear();
for (int i = 0; i < myAdsStatusEnums.length; i++) {
myAdsFilterOptions.add(FilterListModel(title: myAdsStatusEnums[i].enumValueStr, titleN: myAdsStatusEnums[i].enumValueStrN, isSelected: false, id: myAdsStatusEnums[i].enumValue));
myAdsFilterOptions.add(FilterListModel(title: myAdsStatusEnums[i].enumValueStr, titleN: myAdsStatusEnums[i].enumValueStrN, isSelected: false, id: myAdsStatusEnums[i].enumValue));
}
myAdsFilterOptions.insert(0, FilterListModel(title: "All Ads", titleN: "جميع الاعلانات", isSelected: true, id: 0));
@ -209,7 +209,7 @@ class AdVM extends BaseVM {
isSelected: false,
));
}
exploreAdsFilterOptions.insert(0, FilterListModel(title: "All Ads", titleN: "جميع الاعلانات", isSelected: true, id: 0));
exploreAdsFilterOptions.insert(0, FilterListModel(title: "All Ads", titleN: "جميع الاعلانات", isSelected: true, id: 0));
notifyListeners();
}
@ -1233,7 +1233,7 @@ class AdVM extends BaseVM {
} else {
vehicleDescError = "";
}
// Validate Accident Free field (mandatory)
if (!isAccidentFreeSet) {
accidentFreeError = LocaleKeys.pleaseSelectAccidentFree.tr();
@ -1241,7 +1241,7 @@ class AdVM extends BaseVM {
} else {
accidentFreeError = "";
}
// Validate Is In Used field (mandatory)
if (isInUsed == null) {
isInUsedError = LocaleKeys.pleaseSelectMileageIncrease.tr();
@ -1249,7 +1249,7 @@ class AdVM extends BaseVM {
} else {
isInUsedError = "";
}
if ((pickedPostingImages.isEmpty || pickedPostingImages.length < 3) && !isAdEditEnabled) {
vehicleImageError = GlobalConsts.attachImageError;
isValidated = false;
@ -1369,6 +1369,17 @@ class AdVM extends BaseVM {
pickedPostingImages.isNotEmpty;
if (hasData) {
// Skip save confirmation when extending ad
if (isExtendAdEditEnabled) {
isAdEditEnabled = false;
isDraftEditEnabled = false;
isExtendAdEditEnabled = false;
previousAdDetails = null;
resetValues();
pop(context);
return;
}
// Show confirmation dialog if user has entered data
showSaveAsDraftConfirmationDialog(
context,
@ -1388,6 +1399,7 @@ class AdVM extends BaseVM {
Navigator.pop(context);
isAdEditEnabled = false;
isDraftEditEnabled = false;
previousAdDetails = null; // Clear when user cancels without saving
resetValues();
pop(context);
},
@ -1396,11 +1408,19 @@ class AdVM extends BaseVM {
// No data entered, go back directly
isAdEditEnabled = false;
isDraftEditEnabled = false;
previousAdDetails = null; // Clear when going back without data
resetValues();
pop(context);
}
break;
case AdCreationStepsEnum.damageParts:
// Skip save confirmation when extending ad
if (isExtendAdEditEnabled) {
currentProgressStep = AdCreationStepsEnum.vehicleDetails;
notifyListeners();
return;
}
showSaveAsDraftConfirmationDialog(
context,
onYes: () async {
@ -1423,6 +1443,13 @@ class AdVM extends BaseVM {
);
break;
case AdCreationStepsEnum.adDuration:
// Skip save confirmation when extending ad
if (isExtendAdEditEnabled) {
currentProgressStep = AdCreationStepsEnum.damageParts;
notifyListeners();
return;
}
showSaveAsDraftConfirmationDialog(
context,
onYes: () async {
@ -1445,6 +1472,13 @@ class AdVM extends BaseVM {
);
break;
case AdCreationStepsEnum.reviewAd:
// Skip save confirmation when extending ad
if (isExtendAdEditEnabled) {
currentProgressStep = AdCreationStepsEnum.adDuration;
notifyListeners();
return;
}
showSaveAsDraftConfirmationDialog(
context,
onYes: () async {
@ -1661,6 +1695,8 @@ class AdVM extends BaseVM {
AdsCreationPayloadModel adsCreationPayloadModel = AdsCreationPayloadModel(ads: ads, vehiclePosting: vehiclePosting);
log("adsCreationPayloadModel Ads: ${adsCreationPayloadModel.ads.toString()}");
log("adsCreationPayloadModel Posting: ${adsCreationPayloadModel.vehiclePosting.toString()}");
GenericRespModel respModel = await adsRepo.createOrUpdateDraftAd(
adsCreationPayloadModel: adsCreationPayloadModel,
isCreateNew: previousAdDetails == null || previousAdDetails!.id == null,
@ -1688,10 +1724,13 @@ class AdVM extends BaseVM {
switch (currentProgressStep) {
case AdCreationStepsEnum.vehicleDetails:
if (isVehicleDetailsValidated()) {
int adID = await saveAdVehicleDetailsDraft(isNew: previousAdDetails == null || previousAdDetails!.id == null, context: context, stepNoEnum: AdCreationStepsEnum.vehicleDetails);
if (previousAdDetails == null || previousAdDetails!.id == null) {
previousAdDetails = AdDetailsModel();
previousAdDetails!.id = adID;
// Skip draft saving when extending ad - ad is already approved
if (!isExtendAdEditEnabled) {
int adID = await saveAdVehicleDetailsDraft(isNew: previousAdDetails == null || previousAdDetails!.id == null, context: context, stepNoEnum: AdCreationStepsEnum.vehicleDetails);
previousAdDetails ??= AdDetailsModel();
if (previousAdDetails!.id == null) {
previousAdDetails!.id = adID;
}
}
currentProgressStep = AdCreationStepsEnum.damageParts;
@ -1701,7 +1740,10 @@ class AdVM extends BaseVM {
break;
case AdCreationStepsEnum.damageParts:
if (isDamagePartsValidated()) {
await saveAdVehicleDetailsDraft(isNew: false, context: context, stepNoEnum: AdCreationStepsEnum.damageParts);
// Skip draft saving when extending ad - ad is already approved
if (!isExtendAdEditEnabled) {
await saveAdVehicleDetailsDraft(isNew: false, context: context, stepNoEnum: AdCreationStepsEnum.damageParts);
}
currentProgressStep = AdCreationStepsEnum.adDuration;
if (vehicleAdsDurations.isEmpty) {
getVehicleAdsDuration();
@ -1714,7 +1756,10 @@ class AdVM extends BaseVM {
case AdCreationStepsEnum.adDuration:
// log("state: ${AppState().getUser.data!.accessToken}");
if (isAdDurationValidated()) {
await saveAdVehicleDetailsDraft(isNew: false, context: context, stepNoEnum: AdCreationStepsEnum.adDuration);
// Skip draft saving when extending ad - ad is already approved
if (!isExtendAdEditEnabled) {
await saveAdVehicleDetailsDraft(isNew: false, context: context, stepNoEnum: AdCreationStepsEnum.adDuration);
}
currentProgressStep = AdCreationStepsEnum.reviewAd;
notifyListeners();
}
@ -1730,6 +1775,7 @@ class AdVM extends BaseVM {
isAdEditEnabled = false;
isDraftEditEnabled = false;
isExtendAdEditEnabled = false;
previousAdDetails = null; // Clear after successful ad creation
Utils.hideLoading(context);
currentProgressStep = AdCreationStepsEnum.vehicleDetails;
resetValues();
@ -2178,8 +2224,11 @@ class AdVM extends BaseVM {
}
}
log("Creating VehiclePosting - isAdEditEnabled: $isAdEditEnabled, isExtendAdEditEnabled: $isExtendAdEditEnabled");
log("Creating VehiclePosting - previousAdDetails?.vehiclePostingID: ${previousAdDetails?.vehiclePostingID}");
VehiclePosting vehiclePosting = VehiclePosting(
id: isExtendAdEditEnabled ? null : (isAdEditEnabled ? previousAdDetails!.vehiclePostingID : null),
id: (isAdEditEnabled || isExtendAdEditEnabled) && previousAdDetails != null && previousAdDetails!.vehiclePostingID != null ? previousAdDetails!.vehiclePostingID : null,
userID: appState.getUser.data!.userInfo!.userId,
vehicleType: vehicleTypeId.selectedId,
vehicleModelID: vehicleModelId.selectedId,
@ -2625,8 +2674,10 @@ class AdVM extends BaseVM {
vehicleAdCreatedDateSearchHistory.clear();
vehicleAdConditionSearchHistory.clear();
adFilterMobilePhoneSearchHistory.clear();
vehicleBrandsAdSearchHistory.clear();
adFilterDemandStartPrice = '';
adFilterDemandEndPrice = '';
clearAdFilterSelections(); // Clear dropdown selections
adsFiltersCounter = 0;
getExploreAds();
notifyListeners();
@ -2637,6 +2688,7 @@ class AdVM extends BaseVM {
vehicleBrandId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
vehicleModelYearId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
vehicleOwnerId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
vehicleConditionId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
}
Future<List<AdDetailsModel>> getAdsBasedOnFilters({
@ -2763,15 +2815,21 @@ class AdVM extends BaseVM {
}
void onEditUpdateAdPressed({required BuildContext context, required AdDetailsModel previousDetails, required bool isFromExtendAd, bool isForDraft = false}) {
log("previousDetails: ${previousDetails.vehiclePostingID}");
log("previousDetails: ${previousDetails.vehicle?.id}");
isAdEditEnabled = true;
isDraftEditEnabled = isForDraft; // Only true if editing a draft
isExtendAdEditEnabled = isFromExtendAd;
previousAdDetails = previousDetails;
log("previousAdDetails: ${previousAdDetails?.vehicle?.id}");
autoFillSelectedVehicleType();
autoFillSelectedVehicleAdsDuration();
if (isForDraft) {
autoFillSelectedVehicleAdsDetails(context);
}
// if (isForDraft) {
autoFillSelectedVehicleAdsDetails(context);
// }
navigateWithName(context, AppRoutes.selectAdTypeView, arguments: [AppState().currentAppType == AppType.provider, isFromExtendAd, previousDetails.id]);
}
@ -2848,6 +2906,8 @@ class AdVM extends BaseVM {
}
void autoFillSelectedVehicleAdsDetails(BuildContext context) async {
bool isArabic = AppState().isArabic(context);
if (vehicleBrands.isEmpty) {
await getVehicleBrandsByVehicleTypeId();
}
@ -2856,7 +2916,6 @@ class AdVM extends BaseVM {
if (index != -1) {
await updateSelectionVehicleBrandId(SelectionModel(selectedId: vehicleBrands[index].id!, selectedOption: vehicleBrands[index].vehicleBrandDescription ?? ""));
}
bool isArabic = AppState().isArabic(context);
updateSelectionVehicleModelId(SelectionModel(selectedId: previousAdDetails!.vehicle!.model!.id!, selectedOption: previousAdDetails!.vehicle!.model!.label ?? ""));
updateSelectionVehicleModelYearId(SelectionModel(selectedId: previousAdDetails!.vehicle!.modelyear!.id!, selectedOption: previousAdDetails!.vehicle!.modelyear!.label ?? ""));
updateSelectionVehicleColorId(SelectionModel(
@ -2882,12 +2941,16 @@ class AdVM extends BaseVM {
}
updateSelectionVehicleCityId(SelectionModel(selectedId: previousAdDetails!.vehicle!.cityID!, selectedOption: previousAdDetails!.vehicle!.cityName ?? ""));
vehicleDemandAmount = previousAdDetails!.vehicle!.demandAmount!.toInt().toString();
vehicleVin = previousAdDetails!.vehicle!.vehicleVIN.toString();
vehicleTitle = previousAdDetails!.vehicle!.vehicleTitle.toString();
vehicleDescription = previousAdDetails!.vehicle!.vehicleDescription.toString();
vehicleDemandAmount = (previousAdDetails!.vehicle!.demandAmount == null || previousAdDetails!.vehicle!.demandAmount == 0)
? ""
: previousAdDetails!.vehicle!.demandAmount!.toInt().toString();
vehicleVin = previousAdDetails!.vehicle!.vehicleVIN ?? "";
vehicleTitle = previousAdDetails!.vehicle!.vehicleTitle ?? "";
vehicleDescription = previousAdDetails!.vehicle!.vehicleDescription ?? "";
financeAvailableStatus = previousAdDetails!.vehicle!.isFinanceAvailable ?? false;
odometer = previousAdDetails!.vehicle!.odometer != null ? previousAdDetails!.vehicle!.odometer.toString() : "0";
odometer = (previousAdDetails!.vehicle!.odometer == null || previousAdDetails!.vehicle!.odometer == 0)
? ""
: previousAdDetails!.vehicle!.odometer.toString();
isInUsed = previousAdDetails!.vehicle!.isInUsed ?? 0;
isAccidentFree = previousAdDetails!.vehicle!.isAccidentFree ?? false;
isAccidentFreeSet = true;
@ -2952,7 +3015,9 @@ class AdVM extends BaseVM {
adPhoneNumber = previousAdDetails!.adOwnerDetails!.mobileNo ?? "";
isNumberOnWhatsApp = previousAdDetails!.whatsAppNo != null;
warrantyDuration = previousAdDetails!.warrantyYears ?? "";
warrantyDuration = (previousAdDetails!.warrantyYears == null || previousAdDetails!.warrantyYears == "0")
? ""
: previousAdDetails!.warrantyYears!;
notifyListeners();
}

@ -83,8 +83,8 @@ class DashboardVMProvider extends BaseVM with WidgetsBindingObserver {
log("token: ${AppState().getDeviceToken.toString()}");
// Block access to Requests tab (index 4) for Individual Providers
// Only Dealerships can access Special Requests
if (index == 4 && AppState().userType != UserType.providerDealer) {
// Only Dealerships and Dealership Users can access Special Requests
if (index == 4 && AppState().userType != UserType.providerDealer && AppState().userType != UserType.dealershipUser) {
logger.w("Access denied: Individual providers cannot access Special Requests");
return;
}

@ -223,7 +223,12 @@ class RequestsVM extends BaseVM {
if (acceptedRequestsTypeFilterOptions.isEmpty) {
for (int i = 0; i < myRequestsTypeEnum.length; i++) {
acceptedRequestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, isSelected: false, id: myRequestsTypeEnum[i].enumValue));
acceptedRequestsTypeFilterOptions.add(FilterListModel(
title: myRequestsTypeEnum[i].enumValueStr,
titleN: myRequestsTypeEnum[i].enumValueStrN,
isSelected: false,
id: myRequestsTypeEnum[i].enumValue,
));
}
acceptedRequestsTypeFilterOptions[0].isSelected = true;
}
@ -389,8 +394,9 @@ class RequestsVM extends BaseVM {
if (requestsTypeFilterOptions.isEmpty || acceptedRequestsTypeFilterOptions.isEmpty) {
for (int i = 0; i < myRequestsTypeEnum.length; i++) {
requestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, isSelected: false, id: myRequestsTypeEnum[i].enumValue));
acceptedRequestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, isSelected: false, id: myRequestsTypeEnum[i].enumValue));
requestsTypeFilterOptions.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, titleN: myRequestsTypeEnum[i].enumValueStrN, isSelected: false, id: myRequestsTypeEnum[i].enumValue));
acceptedRequestsTypeFilterOptions
.add(FilterListModel(title: myRequestsTypeEnum[i].enumValueStr, titleN: myRequestsTypeEnum[i].enumValueStrN, isSelected: false, id: myRequestsTypeEnum[i].enumValue));
}
}
@ -398,7 +404,7 @@ class RequestsVM extends BaseVM {
if (sparePartsConditionFilterOptions.isEmpty) {
sparePartsConditionFilterOptions.add(FilterListModel(title: "All", titleN: "الكل", isSelected: true, id: 3)); // All
sparePartsConditionFilterOptions.add(FilterListModel(title: "New", titleN: " جديدة", isSelected: false, id: 1)); // New
sparePartsConditionFilterOptions.add(FilterListModel(title: "Used",titleN: " مستعملة", isSelected: false, id: 2)); // Used
sparePartsConditionFilterOptions.add(FilterListModel(title: "Used", titleN: " مستعملة", isSelected: false, id: 2)); // Used
}
if (vehicleBrands.isEmpty) {
@ -897,13 +903,13 @@ class RequestsVM extends BaseVM {
log("requestTypeId.selectedId.toRequestTypeEnum(): ${requestTypeId.selectedId.toRequestTypeEnum()}");
Utils.showToast(LocaleKeys.requestSuccessfullyCreated.tr());
Navigator.pop(context);
// 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());
@ -1229,7 +1235,6 @@ class RequestsVM extends BaseVM {
return requestImages;
}
Future<void> updateOfferFromProvider({
required String message,
required int requestId,

@ -62,7 +62,11 @@ class VehicleDetails extends StatelessWidget {
return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleBrandId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleBrandsDrop,
dropdownValue: adVM.vehicleBrandId.selectedId != -1 ? DropValue(adVM.vehicleBrandId.selectedId, adVM.vehicleBrandId.selectedOption, "") : null,
dropdownValue: adVM.vehicleBrandId.selectedId != -1
? (vehicleBrandsDrop.any((item) => item.id == adVM.vehicleBrandId.selectedId)
? vehicleBrandsDrop.firstWhere((item) => item.id == adVM.vehicleBrandId.selectedId)
: null)
: null,
hint: LocaleKeys.vehicleBrand.tr(),
errorValue: adVM.vehicleBrandId.errorValue,
);
@ -116,7 +120,11 @@ class VehicleDetails extends StatelessWidget {
(DropValue value) => adVM.updateSelectionVehicleModelId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleModelsDrop,
// dropdownValue: adVM.vehicleModelId.selectedId != -1 ? DropValue(adVM.vehicleModelId.selectedId, adVM.vehicleModelId.selectedOption, "") : null,
dropdownValue: adVM.vehicleModelId.selectedId != -1 ? vehicleModelsDrop.firstWhere((item) => item.id == adVM.vehicleModelId.selectedId,) : null,
dropdownValue: adVM.vehicleModelId.selectedId != -1
? (vehicleModelsDrop.any((item) => item.id == adVM.vehicleModelId.selectedId)
? vehicleModelsDrop.firstWhere((item) => item.id == adVM.vehicleModelId.selectedId)
: null)
: null,
hint: LocaleKeys.vehicleModel.tr(),
errorValue: adVM.vehicleModelId.errorValue,
);
@ -131,7 +139,11 @@ class VehicleDetails extends StatelessWidget {
return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleModelYearId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleYearModelsDrop,
dropdownValue: adVM.vehicleModelYearId.selectedId != -1 ? DropValue(adVM.vehicleModelYearId.selectedId, adVM.vehicleModelYearId.selectedOption, "") : null,
dropdownValue: adVM.vehicleModelYearId.selectedId != -1
? (vehicleYearModelsDrop.any((item) => item.id == adVM.vehicleModelYearId.selectedId)
? vehicleYearModelsDrop.firstWhere((item) => item.id == adVM.vehicleModelYearId.selectedId)
: null)
: null,
hint: LocaleKeys.vehicleModelYear.tr(),
errorValue: adVM.vehicleModelYearId.errorValue,
);
@ -150,7 +162,11 @@ class VehicleDetails extends StatelessWidget {
list: vehicleColorsDrop,
hint: LocaleKeys.vehicleColor.tr(),
// dropdownValue: adVM.vehicleColorId.selectedId != -1 ? DropValue(adVM.vehicleColorId.selectedId, adVM.vehicleColorId.selectedOption, "") : null,
dropdownValue: adVM.vehicleColorId.selectedId != -1 ? vehicleColorsDrop.firstWhere((item) => item.id == adVM.vehicleColorId.selectedId,) : null,
dropdownValue: adVM.vehicleColorId.selectedId != -1
? (vehicleColorsDrop.any((item) => item.id == adVM.vehicleColorId.selectedId)
? vehicleColorsDrop.firstWhere((item) => item.id == adVM.vehicleColorId.selectedId)
: null)
: null,
errorValue: adVM.vehicleColorId.errorValue,
);
}),
@ -164,7 +180,11 @@ class VehicleDetails extends StatelessWidget {
return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleConditionId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleConditionsDrop,
dropdownValue: adVM.vehicleConditionId.selectedId != -1 ? vehicleConditionsDrop.firstWhere((item) => item.id == adVM.vehicleConditionId.selectedId,) : null,
dropdownValue: adVM.vehicleConditionId.selectedId != -1
? (vehicleConditionsDrop.any((item) => item.id == adVM.vehicleConditionId.selectedId)
? vehicleConditionsDrop.firstWhere((item) => item.id == adVM.vehicleConditionId.selectedId)
: null)
: null,
// dropdownValue: adVM.vehicleConditionId.selectedId != -1 ? DropValue(adVM.vehicleConditionId.selectedId, adVM.vehicleConditionId.selectedOption, "") : null,
hint: LocaleKeys.vehicleCondition.tr(),
errorValue: adVM.vehicleConditionId.errorValue,
@ -181,7 +201,11 @@ class VehicleDetails extends StatelessWidget {
(DropValue value) => adVM.updateSelectionVehicleCategoryId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleCategoriesDrop,
hint: LocaleKeys.vehicleCategory.tr(),
dropdownValue: adVM.vehicleCategoryId.selectedId != -1 ? vehicleCategoriesDrop.firstWhere((item) => item.id == adVM.vehicleCategoryId.selectedId,) : null,
dropdownValue: adVM.vehicleCategoryId.selectedId != -1
? (vehicleCategoriesDrop.any((item) => item.id == adVM.vehicleCategoryId.selectedId)
? vehicleCategoriesDrop.firstWhere((item) => item.id == adVM.vehicleCategoryId.selectedId)
: null)
: null,
// dropdownValue: adVM.vehicleCategoryId.selectedId != -1 ? DropValue(adVM.vehicleCategoryId.selectedId, adVM.vehicleCategoryId.selectedOption, "") : null,
errorValue: adVM.vehicleCategoryId.errorValue,
);
@ -201,7 +225,11 @@ class VehicleDetails extends StatelessWidget {
return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleMileageId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleMileageDrop,
dropdownValue: adVM.vehicleMileageId.selectedId != -1 ? DropValue(adVM.vehicleMileageId.selectedId, adVM.vehicleMileageId.selectedOption, "") : null,
dropdownValue: adVM.vehicleMileageId.selectedId != -1
? (vehicleMileageDrop.any((item) => item.id == adVM.vehicleMileageId.selectedId)
? vehicleMileageDrop.firstWhere((item) => item.id == adVM.vehicleMileageId.selectedId)
: null)
: null,
hint: LocaleKeys.vehicleMileage.tr(),
errorValue: adVM.vehicleMileageId.errorValue,
);
@ -217,7 +245,11 @@ class VehicleDetails extends StatelessWidget {
(DropValue value) => adVM.updateSelectionVehicleTransmissionId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleTransmissionsDrop,
hint: LocaleKeys.vehicleTransmission.tr(),
dropdownValue: adVM.vehicleTransmissionId.selectedId != -1 ? vehicleTransmissionsDrop.firstWhere((item) => item.id == adVM.vehicleTransmissionId.selectedId,) : null,
dropdownValue: adVM.vehicleTransmissionId.selectedId != -1
? (vehicleTransmissionsDrop.any((item) => item.id == adVM.vehicleTransmissionId.selectedId)
? vehicleTransmissionsDrop.firstWhere((item) => item.id == adVM.vehicleTransmissionId.selectedId)
: null)
: null,
//dropdownValue: adVM.vehicleTransmissionId.selectedId != -1 ? DropValue(adVM.vehicleTransmissionId.selectedId, adVM.vehicleTransmissionId.selectedOption, "") : null,
errorValue: adVM.vehicleTransmissionId.errorValue,
);
@ -233,7 +265,11 @@ class VehicleDetails extends StatelessWidget {
return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleCountryId(SelectionModel(selectedOption: value.value, selectedId: value.id)),
list: vehicleCountriesDrop,
dropdownValue: adVM.vehicleCountryId.selectedId != -1 ? vehicleCountriesDrop.firstWhere((item) => item.id == adVM.vehicleCountryId.selectedId,) : null,
dropdownValue: adVM.vehicleCountryId.selectedId != -1
? (vehicleCountriesDrop.any((item) => item.id == adVM.vehicleCountryId.selectedId)
? vehicleCountriesDrop.firstWhere((item) => item.id == adVM.vehicleCountryId.selectedId)
: null)
: null,
// dropdownValue: adVM.vehicleCountryId.selectedId != -1 ? DropValue(adVM.vehicleCountryId.selectedId, adVM.vehicleCountryId.selectedOption, "") : null,
hint: LocaleKeys.vehicleCountry.tr(),
errorValue: adVM.vehicleCountryId.errorValue,
@ -256,7 +292,11 @@ class VehicleDetails extends StatelessWidget {
return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleCityId(SelectionModel(selectedId: value.id, selectedOption: value.value)),
list: vehicleCitiesDrop,
dropdownValue: adVM.vehicleCityId.selectedId != -1 ? vehicleCitiesDrop.firstWhere((item) => item.id == adVM.vehicleCityId.selectedId,) : null,
dropdownValue: adVM.vehicleCityId.selectedId != -1
? (vehicleCitiesDrop.any((item) => item.id == adVM.vehicleCityId.selectedId)
? vehicleCitiesDrop.firstWhere((item) => item.id == adVM.vehicleCityId.selectedId)
: null)
: null,
// dropdownValue: adVM.vehicleCityId.selectedId != -1 ? DropValue(adVM.vehicleCityId.selectedId, adVM.vehicleCityId.selectedOption, "") : null,
hint: LocaleKeys.vehicleCity.tr(),
errorValue: adVM.vehicleCityId.errorValue,
@ -377,7 +417,7 @@ class VehicleDetails extends StatelessWidget {
],
),
28.height,
LocaleKeys.vehiclePictures.tr().toText(fontSize: 18, isBold: true),
LocaleKeys.vehiclePictures.tr().toText(fontSize: 16, isBold: false),
8.height,
if (adVM.pickedPostingImages.isEmpty) ...[
DottedRectContainer(

@ -133,7 +133,7 @@ class _AdsDetailViewState extends State<AdsDetailView> {
"${isArabic ? widget.adDetails.vehicle!.model!.labelN : widget.adDetails.vehicle!.model!.label}".toText(fontSize: 12),
],
),
(isArabic ? widget.adDetails.vehicle!.countryNameN?? '' : widget.adDetails.vehicle!.countryName?? '').toText(fontSize: 10, color: MyColors.lightTextColor),
(isArabic ? widget.adDetails.vehicle!.countryNameN ?? '' : widget.adDetails.vehicle!.countryName ?? '').toText(fontSize: 10, color: MyColors.lightTextColor),
],
),
Row(
@ -151,8 +151,12 @@ class _AdsDetailViewState extends State<AdsDetailView> {
"${widget.adDetails.vehicle?.mileage?.mileageStart ?? ""} - ${widget.adDetails.vehicle?.mileage?.mileageEnd ?? ""} (${LocaleKeys.km.tr()})".toText(fontSize: 12),
],
),
widget.adDetails.createdOn != null
? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo(isArabic: isArabic).toText(fontSize: 10,color: MyColors.lightTextColor,): const SizedBox(),
widget.adDetails.createdOn != null
? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo(isArabic: isArabic).toText(
fontSize: 10,
color: MyColors.lightTextColor,
)
: const SizedBox(),
//widget.adDetails.createdOn != null ? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo().toText(fontSize: 10, color: MyColors.lightTextColor) : const SizedBox(),
],
),
@ -172,7 +176,7 @@ class _AdsDetailViewState extends State<AdsDetailView> {
Row(
children: [
("${LocaleKeys.vehicleCondition.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor),
(widget.adDetails.vehicle!.condition?.id == 2 ? LocaleKeys.used.tr() : LocaleKeys.used.tr()).toText(fontSize: 12),
(widget.adDetails.vehicle!.condition?.id == 2 ? LocaleKeys.used.tr() : LocaleKeys.newCondition.tr()).toText(fontSize: 12),
],
),
],
@ -255,7 +259,7 @@ class _AdsDetailViewState extends State<AdsDetailView> {
Widget buildBankDetailsIfAvailable() {
return Consumer(builder: (BuildContext context, AdVM adVM, Widget? child) {
if (adVM.adsBankDetailsModel != null) {
if (adVM.adsBankDetailsModel != null && (widget.adDetails.isMyAd ?? false) && (widget.adDetails.isReservedByMe ?? false) && (widget.adDetails.adPostStatus == AdPostStatus.reserved)) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
@ -400,17 +404,23 @@ class _AdsDetailViewState extends State<AdsDetailView> {
// if (widget.adDetails.enddate != null) {
// endDate = "${DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.enddate!))} at ${DateHelper.formatAsTime(DateTime.parse(widget.adDetails.enddate!))}";
// }
if (widget.adDetails.startdate != null) {startDate = "${DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.startdate!),
isArabic: isArabic,)} $atText ${DateHelper.formatAsTime(
if (widget.adDetails.startdate != null) {
startDate = "${DateHelper.formatAsDayMonthYear(
DateTime.parse(widget.adDetails.startdate!),
isArabic: isArabic,
)} $atText ${DateHelper.formatAsTime(
DateTime.parse(widget.adDetails.startdate!),
isArabic: isArabic,
)}";
}
if (widget.adDetails.enddate != null) {endDate = "${DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.enddate!),
isArabic: isArabic,)} $atText ${DateHelper.formatAsTime(
DateTime.parse(widget.adDetails.enddate!),
isArabic: isArabic,
)}";
if (widget.adDetails.enddate != null) {
endDate = "${DateHelper.formatAsDayMonthYear(
DateTime.parse(widget.adDetails.enddate!),
isArabic: isArabic,
)} $atText ${DateHelper.formatAsTime(
DateTime.parse(widget.adDetails.enddate!),
isArabic: isArabic,
)}";
}
return Column(
children: [

@ -18,7 +18,6 @@ import 'package:mc_common_app/utils/navigator.dart';
import 'package:mc_common_app/utils/utils.dart';
import 'package:mc_common_app/view_models/ad_view_model.dart';
import 'package:mc_common_app/views/advertisement/bottom_sheets/ad_duration_selection_sheet.dart';
import 'package:mc_common_app/views/advertisement/components/picked_images_container_widget.dart';
import 'package:mc_common_app/views/appointments/widgets/custom_calender_widget.dart';
import 'package:mc_common_app/widgets/bottom_sheet.dart';
import 'package:mc_common_app/widgets/button/show_fill_button.dart';
@ -49,8 +48,6 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
children: [
// Show instructions if available
if (adDetailsModel.reserveCustomerMessage != null && adDetailsModel.reserveCustomerMessage!.isNotEmpty) ...[
LocaleKeys.shippingManagementInstruction.tr().toText(fontSize: 16, isBold: true),
8.height,
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
@ -58,10 +55,17 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
border: Border.all(color: MyColors.borderColor),
),
child: adDetailsModel.reserveCustomerMessage!.toText(
fontSize: 14,
maxLines: 10,
height: 1.5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.note.tr().toText(fontSize: 14),
4.height,
adDetailsModel.reserveCustomerMessage!.toText(
fontSize: 14,
maxLines: 10,
height: 1.5,
),
],
),
),
20.height,
@ -164,11 +168,13 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
size: 19,
).paddingOnly(bottom: 2),
3.width,
LocaleKeys.servicesReservingAd.tr().toText(
color: MyColors.adPendingStatusColor,
fontSize: 12,
isItalic: true,
),
Expanded(
child: LocaleKeys.servicesReservingAd.tr().toText(
color: MyColors.adPendingStatusColor,
fontSize: 12,
isItalic: true,
),
)
],
),
15.height,
@ -208,7 +214,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
},
),
),
if (adDetailsModel.whatsAppNo != null && adDetailsModel.whatsAppNo!.isNotEmpty) ...[
if ((adDetailsModel.isOnWhatsApp ?? false) && adDetailsModel.whatsAppNo != null && adDetailsModel.whatsAppNo!.isNotEmpty) ...[
8.width,
Container(
height: 55,
@ -240,7 +246,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
Widget defaultActionForProviderAndCustomer(BuildContext context, AdDetailsModel adDetailsModel) {
return (adDetailsModel.phoneNo != null && adDetailsModel.phoneNo!.isNotEmpty)
? Row(
? Row(
children: [
Expanded(
child: ShowFillButton(
@ -257,7 +263,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
},
),
),
if (adDetailsModel.whatsAppNo != null && adDetailsModel.whatsAppNo!.isNotEmpty) ...[
if ((adDetailsModel.isOnWhatsApp ?? false) && adDetailsModel.whatsAppNo != null && adDetailsModel.whatsAppNo!.isNotEmpty) ...[
8.width,
Container(
height: 55,
@ -281,7 +287,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
case CreatedByRoleEnum.provider:
return defaultActionForProviderAndCustomer(context, adDetailsModel);
case CreatedByRoleEnum.admin:
// Check if admin ad is reservable
// Check if admin ad is reservable - Reserve Ad feature is customer-app only
if (adDetailsModel.isReservable == true && adDetailsModel.adPostStatus != AdPostStatus.sold) {
return reserveAdAction(context, adDetailsModel);
} else {
@ -328,16 +334,24 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget {
: Builder(
builder: (context) {
List<DropValue> vehicleCitiesDrop = [];
Set<int> addedOfficeIds = {};
for (int i = 0; i < adVM.photoSSSchedulesByOffices.length; i++) {
var element = adVM.photoSSSchedulesByOffices[i];
vehicleCitiesDrop.add(DropValue(element.photoOfficeID?.toInt() ?? 0, element.photoOfficeName ?? "", i.toString()));
int officeId = element.photoOfficeID?.toInt() ?? 0;
// Skip duplicate offices (same office can appear more than once from API)
if (addedOfficeIds.contains(officeId)) continue;
addedOfficeIds.add(officeId);
vehicleCitiesDrop.add(DropValue(officeId, element.photoOfficeName ?? "", i.toString()));
}
print(adVM.photoOfficeSelectedId.selectedId);
bool officeExists = vehicleCitiesDrop.any((element) => element.id == adVM.photoOfficeSelectedId.selectedId);
return DropdownField(
(DropValue value) => adVM.updatePhotoOfficeSelectedId(SelectionModel(selectedId: value.id, selectedOption: value.value, itemPrice: value.subValue)),
list: vehicleCitiesDrop,
dropdownValue: adVM.photoOfficeSelectedId.selectedId != -1 ? DropValue(adVM.photoOfficeSelectedId.selectedId, adVM.photoOfficeSelectedId.selectedOption, "") : null,
dropdownValue: (adVM.photoOfficeSelectedId.selectedId != -1 && officeExists)
? vehicleCitiesDrop.firstWhere((element) => element.id == adVM.photoOfficeSelectedId.selectedId)
: null,
hint: LocaleKeys.selectOffice.tr(),
errorValue: adVM.photoOfficeSelectedId.errorValue,
);
@ -508,11 +522,13 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget {
size: 19,
).paddingOnly(bottom: 2),
3.width,
LocaleKeys.servicesReservingAd.tr().toText(
color: MyColors.adPendingStatusColor,
fontSize: 12,
isItalic: true,
),
Expanded(
child: LocaleKeys.servicesReservingAd.tr().toText(
color: MyColors.adPendingStatusColor,
fontSize: 12,
isItalic: true,
),
)
],
),
15.height,
@ -1136,6 +1152,10 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget {
case AdPostStatus.reserved:
// adPostStatus=reserved is set in getMyReservedAds()
// adReserveStatus tells us the detailed status (reserved, timeOver, dealDone, etc.)
// Reserve/Cancel Reservation feature is customer-app only - provider app should never reach this state
if (AppState().currentAppType != AppType.customer) {
return const SizedBox.shrink();
}
AdVM adVM = context.watch<AdVM>();
if (adVM.state == ViewState.busy) {
return const CircularProgressIndicator();

@ -29,6 +29,8 @@ class AdsFilterView extends StatefulWidget {
class _AdsFilterViewState extends State<AdsFilterView> {
late AdVM adVM;
final FocusNode _adIdFocusNode = FocusNode();
final FocusNode _mobileNumberFocusNode = FocusNode();
@override
void initState() {
@ -36,12 +38,25 @@ class _AdsFilterViewState extends State<AdsFilterView> {
scheduleMicrotask(() async {
await adVM.populateDataForAdFilter();
});
// Clear unconfirmed typed value when the user taps away without pressing the tick/confirm button
_adIdFocusNode.addListener(() {
if (!_adIdFocusNode.hasFocus && adVM.currentAdIDFilter.isNotEmpty) {
adVM.onCurrentAdIDFilterChanged("");
}
});
_mobileNumberFocusNode.addListener(() {
if (!_mobileNumberFocusNode.hasFocus && adVM.currentAdMobilePhoneFilter.isNotEmpty) {
adVM.onCurrentAdMobilePhoneFilterChanged("");
}
});
super.initState();
}
@override
void dispose() {
adVM.clearAdFilterSelections();
_adIdFocusNode.dispose();
_mobileNumberFocusNode.dispose();
super.dispose();
}
@ -82,14 +97,27 @@ class _AdsFilterViewState extends State<AdsFilterView> {
value: adVM.currentAdIDFilter,
onChanged: (value) => adVM.onCurrentAdIDFilterChanged(value),
hint: LocaleKeys.enterAdID.tr(),
focusNode: _adIdFocusNode,
postfixWidget: Consumer(
builder: (BuildContext context, AdVM adVM, Widget? child) {
if (adVM.currentAdIDFilter.isEmpty) {
return const SizedBox();
}
return IconButton(
onPressed: () => adVM.addAdFilterAdIDSearchHistory(value: adVM.currentAdIDFilter),
icon: const Icon(Icons.done, color: MyColors.lightIconColor),
return InkWell(
onTap: () {
adVM.addAdFilterAdIDSearchHistory(value: adVM.currentAdIDFilter);
_adIdFocusNode.unfocus();
},
child: Container(
width: 20,
height: 20,
margin: const EdgeInsets.all(8),
decoration: const BoxDecoration(
color: MyColors.darkPrimaryColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.done, color: Colors.white, size: 19),
),
);
},
),
@ -290,7 +318,7 @@ class _AdsFilterViewState extends State<AdsFilterView> {
actionWidget: TxtField(
isNeedBorder: true,
hint: LocaleKeys.createdOn.tr(),
value: adVM.vehicleAdDurationId.selectedOption,
value: "",
isNeedClickAll: true,
postFixDataColor: MyColors.darkTextColor,
onTap: () async {
@ -298,7 +326,6 @@ class _AdsFilterViewState extends State<AdsFilterView> {
if (formattedDate.isEmpty) {
return;
}
adVM.updateSelectionDurationStartDate(formattedDate);
DropValue value = DropValue(0, formattedDate, "");
adVM.addToVehicleAdCreatedDateSearchHistory(value: value);
},
@ -317,14 +344,27 @@ class _AdsFilterViewState extends State<AdsFilterView> {
hint: LocaleKeys.enterMobileNumber.tr(),
keyboardType: TextInputType.phone,
numbersOnly: true,
focusNode: _mobileNumberFocusNode,
postfixWidget: Consumer(
builder: (BuildContext context, AdVM adVM, Widget? child) {
if (adVM.currentAdMobilePhoneFilter.isEmpty) {
return const SizedBox();
}
return IconButton(
onPressed: () => adVM.addAdFilterMobilePhoneSearchHistory(value: adVM.currentAdMobilePhoneFilter),
icon: const Icon(Icons.done, color: MyColors.lightIconColor),
return InkWell(
onTap: () {
adVM.addAdFilterMobilePhoneSearchHistory(value: adVM.currentAdMobilePhoneFilter);
_mobileNumberFocusNode.unfocus();
},
child: Container(
width: 20,
height: 20,
margin: const EdgeInsets.all(8),
decoration: const BoxDecoration(
color: MyColors.darkPrimaryColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.done, color: Colors.white, size: 19),
),
);
},
),

@ -285,15 +285,16 @@ class _AdCardState extends State<AdCard> {
runSpacing: 4,
children: [
// Show "Mowater Ad" tag for admin ads
if (widget.adDetails.createdByRoleEnum == CreatedByRoleEnum.admin) Utils.statusContainerChip(text: "Mowater Ad", chipColor: MyColors.primaryColor),
if (widget.adDetails.createdByRoleEnum == CreatedByRoleEnum.admin && widget.adDetails.onBehalfOfCustomer != true)
Utils.statusContainerChip(text: !isArabic ? "Mowater Ad" : "إعلان مواتر", chipColor: MyColors.primaryColor),
// Show Accident Free chip when isAccidentFree is true
if (widget.adDetails.vehicle?.isAccidentFree == true) Utils.statusContainerChip(text: LocaleKeys.accidentFree.tr(), chipColor: Colors.green),
],
),
],
// When viewing reserved ads filter, show ONLY reserve status badge
if (widget.adDetails.isReservedByMe == true &&
widget.adDetails.adReserveStatus != null &&
if (widget.adDetails.isReservedByMe == true &&
widget.adDetails.adReserveStatus != null &&
widget.adDetails.adReserveStatus != AdReserveStatus.defaultStatus &&
context.read<AdVM>().isReservedFilterSelected) ...[
Utils.statusContainerChip(
@ -342,7 +343,7 @@ class _AdCardState extends State<AdCard> {
),
2.width,
Expanded(
child: "${widget.adDetails.vehicle?.mileage?.mileageStart ?? ""} - ${widget.adDetails.vehicle?.mileage?.mileageEnd ?? ""}km".toText(
child: "${widget.adDetails.vehicle?.mileage?.mileageStart ?? ""} - ${widget.adDetails.vehicle?.mileage?.mileageEnd ?? ""} km".toText(
overflow: TextOverflow.ellipsis,
fontSize: 12,
letterSpacing: -0.48,
@ -359,7 +360,9 @@ class _AdCardState extends State<AdCard> {
mainAxisAlignment: MainAxisAlignment.start,
children: [
(widget.adDetails.vehicle!.cityName ?? "").toText(color: MyColors.lightTextColor, fontSize: 4),
widget.adDetails.createdOn != null ? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor, fontSize: 4) : const SizedBox(),
widget.adDetails.createdOn != null
? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo(isArabic: isArabic).toText(color: MyColors.lightTextColor, fontSize: 4)
: const SizedBox(),
],
),
],

@ -80,27 +80,30 @@ class BuildFooterButton extends StatelessWidget {
case AdCreationStepsEnum.vehicleDetails:
return Row(
children: [
Expanded(
child: ShowFillButton(
txtColor: MyColors.black,
maxHeight: 55,
// title: "Save As Draft",
title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.vehicleDetails, bypassValidation: true);
adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues();
pop(context);
if (AppState().currentAppType == AppType.customer) {
// Hide "Save as Draft" when extending ad
if (!adVm.isExtendAdEditEnabled) ...[
Expanded(
child: ShowFillButton(
txtColor: MyColors.black,
maxHeight: 55,
// title: "Save As Draft",
title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.vehicleDetails, bypassValidation: true);
adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues();
pop(context);
}
},
backgroundColor: MyColors.greyButtonColor,
if (AppState().currentAppType == AppType.customer) {
pop(context);
}
},
backgroundColor: MyColors.greyButtonColor,
),
),
),
12.width,
12.width,
],
Expanded(
child: ShowFillButton(
maxHeight: 55,
@ -116,27 +119,30 @@ class BuildFooterButton extends StatelessWidget {
case AdCreationStepsEnum.damageParts:
return Row(
children: [
Expanded(
child: ShowFillButton(
txtColor: MyColors.black,
maxHeight: 55,
// title: "Save As Draft",
title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.damageParts, bypassValidation: true);
adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues();
pop(context);
if (AppState().currentAppType == AppType.customer) {
// Hide "Save as Draft" when extending ad
if (!adVm.isExtendAdEditEnabled) ...[
Expanded(
child: ShowFillButton(
txtColor: MyColors.black,
maxHeight: 55,
// title: "Save As Draft",
title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.damageParts, bypassValidation: true);
adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues();
pop(context);
}
},
backgroundColor: MyColors.greyButtonColor,
if (AppState().currentAppType == AppType.customer) {
pop(context);
}
},
backgroundColor: MyColors.greyButtonColor,
),
),
),
12.width,
12.width,
],
Expanded(
child: ShowFillButton(
maxHeight: 55,
@ -152,27 +158,30 @@ class BuildFooterButton extends StatelessWidget {
case AdCreationStepsEnum.adDuration:
return Row(
children: [
Expanded(
child: ShowFillButton(
txtColor: MyColors.black,
maxHeight: 55,
// title: "Save As Draft",
title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.adDuration, bypassValidation: true);
adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues();
pop(context);
if (AppState().currentAppType == AppType.customer) {
// Hide "Save as Draft" when extending ad
if (!adVm.isExtendAdEditEnabled) ...[
Expanded(
child: ShowFillButton(
txtColor: MyColors.black,
maxHeight: 55,
// title: "Save As Draft",
title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.adDuration, bypassValidation: true);
adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues();
pop(context);
}
},
backgroundColor: MyColors.greyButtonColor,
if (AppState().currentAppType == AppType.customer) {
pop(context);
}
},
backgroundColor: MyColors.greyButtonColor,
),
),
),
12.width,
12.width,
],
Expanded(
child: ShowFillButton(
maxHeight: 55,
@ -188,27 +197,30 @@ class BuildFooterButton extends StatelessWidget {
case AdCreationStepsEnum.reviewAd:
return Row(
children: [
Expanded(
child: ShowFillButton(
txtColor: MyColors.black,
maxHeight: 55,
//title: "Save As Draft",
title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.reviewAd, bypassValidation: true);
adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues();
pop(context);
if (AppState().currentAppType == AppType.customer) {
// Hide "Save as Draft" when extending ad
if (!adVm.isExtendAdEditEnabled) ...[
Expanded(
child: ShowFillButton(
txtColor: MyColors.black,
maxHeight: 55,
//title: "Save As Draft",
title: isArabic ? "حفظ كمسودة" : "Save As Draft",
onPressed: () async {
await adVm.saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.reviewAd, bypassValidation: true);
adVm.isDraftEditEnabled = false;
adVm.isAdEditEnabled = false;
adVm.currentProgressStep = AdCreationStepsEnum.vehicleDetails;
adVm.resetValues();
pop(context);
}
},
backgroundColor: MyColors.greyButtonColor,
if (AppState().currentAppType == AppType.customer) {
pop(context);
}
},
backgroundColor: MyColors.greyButtonColor,
),
),
),
12.width,
12.width,
],
Expanded(
child: ShowFillButton(
maxHeight: 55,

@ -78,6 +78,12 @@ class SelectAdTypeView extends StatelessWidget {
return InkWell(
onTap: () async {
adVM.updateSelectionVehicleTypeId(SelectionModel(selectedId: vehicleTypeModel.id!, selectedOption: isArabic ? vehicleTypeModel.vehicleTypeNameN ?? "" : vehicleTypeModel.vehicleTypeName ?? "", errorValue: ""));
// Clear previous ad details when creating NEW ad (not editing)
if (adsId == -1 && !adVM.isAdEditEnabled && !adVM.isDraftEditEnabled) {
adVM.previousAdDetails = null;
}
if (adVM.isAdEditEnabled) {
adVM.autoFillSelectedVehicleAdsDetails(context);
}

@ -21,16 +21,21 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:sizer/sizer.dart';
class ScreenArgumentsForAppointmentDetailPage {
final int routeFlag; // 1 = coming from create appointment || 2 = coming from reschedule appointment
final int appointmentId; // 1 = coming from create appointment || 2 = coming from reschedule appointment
final int
routeFlag; // 1 = coming from create appointment || 2 = coming from reschedule appointment
final int
appointmentId; // 1 = coming from create appointment || 2 = coming from reschedule appointment
ScreenArgumentsForAppointmentDetailPage({required this.routeFlag, required this.appointmentId});
ScreenArgumentsForAppointmentDetailPage(
{required this.routeFlag, required this.appointmentId});
}
class BookAppointmentSchedulesView extends StatelessWidget {
final ScreenArgumentsForAppointmentDetailPage screenArgumentsForAppointmentDetailPage;
final ScreenArgumentsForAppointmentDetailPage
screenArgumentsForAppointmentDetailPage;
const BookAppointmentSchedulesView({super.key, required this.screenArgumentsForAppointmentDetailPage});
const BookAppointmentSchedulesView(
{super.key, required this.screenArgumentsForAppointmentDetailPage});
@override
Widget build(BuildContext context) {
@ -42,98 +47,154 @@ class BookAppointmentSchedulesView extends StatelessWidget {
onBackButtonTapped: () => Navigator.pop(context),
),
body: Consumer(
builder: (BuildContext context, AppointmentsVM appointmentsVM, Widget? child) {
builder: (BuildContext context, AppointmentsVM appointmentsVM,
Widget? child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
21.height,
ListView.builder(
shrinkWrap: true,
itemCount: appointmentsVM.serviceAppointmentScheduleList.length,
itemCount:
appointmentsVM.serviceAppointmentScheduleList.length,
itemBuilder: (BuildContext context, int scheduleIndex) {
ServiceAppointmentScheduleModel scheduleData = appointmentsVM.serviceAppointmentScheduleList[scheduleIndex];
return ExpansionTile(
tilePadding: const EdgeInsets.symmetric(horizontal: 21, vertical: 10),
childrenPadding: const EdgeInsets.only(left: 16, bottom: 10, right: 16),
initiallyExpanded: true,
title: Column(
ServiceAppointmentScheduleModel scheduleData =
appointmentsVM
.serviceAppointmentScheduleList[scheduleIndex];
// Wrapped in its own transparent Material so the ExpansionTile's internal
// ListTile ink splashes render correctly on top of the white background/shadow
// applied by `toWhiteContainer` below (instead of being hidden by it).
return Material(
type: MaterialType.transparency,
child: ExpansionTile(
tilePadding: const EdgeInsets.symmetric(
horizontal: 21, vertical: 10),
childrenPadding: const EdgeInsets.only(
left: 16, bottom: 10, right: 16),
initiallyExpanded: true,
title: Column(
children: [
Row(
children: [
Expanded(
child: (scheduleData.scheduleName ?? "")
.toText(fontSize: 20, isBold: true),
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(LocaleKeys.serviceLocation.tr()).toText(
fontSize: 12,
color: MyColors.lightTextColor,
isBold: true),
2.width,
(scheduleData.appointmentTypeEnum ==
AppointmentTypeEnum.home
? LocaleKeys.customerLocation.tr()
: LocaleKeys.companyLocation.tr())
.toText(fontSize: 12, isBold: true)
.expand(),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
5.height,
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: appointmentsVM
.serviceAppointmentScheduleList[
scheduleIndex]
.servicesListInAppointment!
.length,
itemBuilder:
(BuildContext context, int serviceIndex) {
ServiceModel selectedService =
appointmentsVM
.serviceAppointmentScheduleList[
scheduleIndex]
.servicesListInAppointment![
serviceIndex];
return Row(
children: [
Expanded(
child:
("${serviceIndex + 1}. ${selectedService.providerServiceDescription}")
.toText(
fontSize: 15,
isBold: true,
color: MyColors
.lightTextColor),
),
],
);
},
),
],
),
],
),
children: [
Row(
children: [
Expanded(
child: (scheduleData.scheduleName ?? "").toText(fontSize: 20, isBold: true),
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(LocaleKeys.serviceLocation.tr()).toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
2.width,
(scheduleData.appointmentTypeEnum == AppointmentTypeEnum.home ? LocaleKeys.customerLocation.tr() : LocaleKeys.companyLocation.tr())
.toText(fontSize: 12, isBold: true)
.expand(),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
5.height,
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].servicesListInAppointment!.length,
itemBuilder: (BuildContext context, int serviceIndex) {
ServiceModel selectedService = appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].servicesListInAppointment![serviceIndex];
return Row(
children: [
Expanded(
child: ("${serviceIndex + 1}. ${selectedService.providerServiceDescription}").toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor),
),
],
);
CustomCalenderAppointmentWidget(
customTimeDateSlotList:
scheduleData.customTimeDateSlotList ?? [],
onDateSelected: (int dateIndex) {
appointmentsVM.updateSelectedAppointmentDate(
scheduleIndex: scheduleIndex,
dateIndex: dateIndex);
},
selectedCustomTimeDateSlotModel: scheduleData
.selectedCustomTimeDateSlotModel,
),
if (appointmentsVM
.serviceAppointmentScheduleList[
scheduleIndex]
.selectedDateIndex !=
null) ...[
5.height,
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
(LocaleKeys.availableSlots.tr())
.toText(fontSize: 14, isBold: true),
],
),
5.height,
SizedBox(
width: double.infinity,
child: BuildTimeSlots(
timeSlots: appointmentsVM
.serviceAppointmentScheduleList[
scheduleIndex]
.customTimeDateSlotList![appointmentsVM
.serviceAppointmentScheduleList[
scheduleIndex]
.selectedDateIndex!]
.availableSlots ??
[],
onPressed: (slotIndex) {
log("slotIndex: $slotIndex");
appointmentsVM
.updateSelectedAppointmentSlotByDate(
scheduleIndex: scheduleIndex,
slotIndex: slotIndex);
},
),
),
],
],
),
],
),
children: [
Column(
children: [
CustomCalenderAppointmentWidget(
customTimeDateSlotList: scheduleData.customTimeDateSlotList ?? [],
onDateSelected: (int dateIndex) {
appointmentsVM.updateSelectedAppointmentDate(scheduleIndex: scheduleIndex, dateIndex: dateIndex);
},
selectedCustomTimeDateSlotModel: scheduleData.selectedCustomTimeDateSlotModel,
),
if (appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex != null) ...[
5.height,
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
(LocaleKeys.availableSlots.tr()).toText(fontSize: 14, isBold: true),
],
),
5.height,
SizedBox(
width: double.infinity,
child: BuildTimeSlots(
timeSlots: appointmentsVM.serviceAppointmentScheduleList[scheduleIndex]
.customTimeDateSlotList![appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex!].availableSlots ??
[],
onPressed: (slotIndex) {
log("slotIndex: $slotIndex");
appointmentsVM.updateSelectedAppointmentSlotByDate(scheduleIndex: scheduleIndex, slotIndex: slotIndex);
},
),
),
],
],
),
],
).toWhiteContainer(width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 10));
).toWhiteContainer(
width: double.infinity,
margin: const EdgeInsets.symmetric(
horizontal: 21, vertical: 10));
},
).expand(),
Row(
@ -151,30 +212,46 @@ class BookAppointmentSchedulesView extends StatelessWidget {
Expanded(
child: ShowFillButton(
maxHeight: 55,
title: screenArgumentsForAppointmentDetailPage.routeFlag == 1 ? LocaleKeys.review.tr() : LocaleKeys.confirm.tr(),
title:
screenArgumentsForAppointmentDetailPage.routeFlag ==
1
? LocaleKeys.review.tr()
: LocaleKeys.confirm.tr(),
onPressed: () {
if (screenArgumentsForAppointmentDetailPage.routeFlag == 1) {
if (screenArgumentsForAppointmentDetailPage
.routeFlag ==
1) {
appointmentsVM.onReviewButtonPressed(context);
} else {
int selectedSlotId = 0;
int index = 0;
List<TimeSlotModel> timeSlots = appointmentsVM.serviceAppointmentScheduleList.first.selectedCustomTimeDateSlotModel!.availableSlots ?? [];
List<TimeSlotModel> timeSlots = appointmentsVM
.serviceAppointmentScheduleList
.first
.selectedCustomTimeDateSlotModel!
.availableSlots ??
[];
if (timeSlots.isNotEmpty) {
index = timeSlots.indexWhere((element) => element.isSelected);
index = timeSlots
.indexWhere((element) => element.isSelected);
}
if (index != -1) {
selectedSlotId = timeSlots[index].slotId;
} else {
Utils.showToast("Please select correct date and time.");
Utils.showToast(
"Please select correct date and time.");
return;
}
log("selectedSlotId: $selectedSlotId");
appointmentsVM.onRescheduleAppointmentConfirmPressed(
appointmentsVM
.onRescheduleAppointmentConfirmPressed(
context: context,
appointmentId: screenArgumentsForAppointmentDetailPage.appointmentId,
appointmentId:
screenArgumentsForAppointmentDetailPage
.appointmentId,
selectedSlotId: selectedSlotId,
);
}

@ -27,7 +27,6 @@ class BookAppointmentsItemView extends StatelessWidget {
title: LocaleKeys.selectItems.tr(),
isRemoveBackButton: false,
isDrawerEnabled: false,
actions: [MyAssets.searchIcon.buildSvg().paddingOnly(right: 21)],
onBackButtonTapped: () {
context.read<AppointmentsVM>().resetCategorySelectionBottomSheet();
Navigator.pop(context);

@ -22,10 +22,12 @@ class CustomCalenderAppointmentWidget extends StatefulWidget {
});
@override
State<CustomCalenderAppointmentWidget> createState() => _CustomCalenderAppointmentWidgetState();
State<CustomCalenderAppointmentWidget> createState() =>
_CustomCalenderAppointmentWidgetState();
}
class _CustomCalenderAppointmentWidgetState extends State<CustomCalenderAppointmentWidget> {
class _CustomCalenderAppointmentWidgetState
extends State<CustomCalenderAppointmentWidget> {
List<DateTime> allDates = [];
List<DropValue> allMonths = [];
List<DateTime> datesInSelectedMonth = [];
@ -47,23 +49,32 @@ class _CustomCalenderAppointmentWidgetState extends State<CustomCalenderAppointm
populateDateList() {
for (var value in widget.customTimeDateSlotList) {
DateTime dt = DateFormat('dd MMMM, yyyy').parse(value.date!.date.trim());
DropValue dv = DropValue(dt.month, "${dt.month.getMonthNameByNumber()}, ${dt.year}", "${dt.year}");
DropValue dv = DropValue(dt.month,
"${dt.month.getMonthNameByNumber()}, ${dt.year}", "${dt.year}");
allDates.add(dt);
if (!ifMonthAlreadyThere(dv)) {
allMonths.add(dv);
}
}
// Ensure dates are in chronological order - API may return them unsorted,
// which would make `.first`/`.last` unreliable and break TableCalendar's invariants.
allDates.sort((a, b) => a.compareTo(b));
selectedMonth = allDates.first.month;
selectedYear = allDates.first.year;
if (widget.selectedCustomTimeDateSlotModel != null) {
DateTime alreadySelectedDate = DateFormat('dd MMMM, yyyy').parse(widget.selectedCustomTimeDateSlotModel!.date!.date);
DateTime alreadySelectedDate = DateFormat('dd MMMM, yyyy')
.parse(widget.selectedCustomTimeDateSlotModel!.date!.date);
_selectedDay = alreadySelectedDate;
_focusedDay = alreadySelectedDate;
selectedMonth = alreadySelectedDate.month;
selectedYear = alreadySelectedDate.year;
}
datesInSelectedMonth = allDates.where((element) => element.month == selectedMonth).toList();
datesInSelectedMonth = allDates
.where((element) =>
element.month == selectedMonth && element.year == selectedYear)
.toList();
if (widget.selectedCustomTimeDateSlotModel == null) {
_focusedDay = datesInSelectedMonth.first;
@ -71,7 +82,8 @@ class _CustomCalenderAppointmentWidgetState extends State<CustomCalenderAppointm
}
bool ifMonthAlreadyThere(DropValue monthDate) {
int index = allMonths.indexWhere((element) => element.id == monthDate.id && element.subValue == monthDate.subValue);
int index = allMonths.indexWhere((element) =>
element.id == monthDate.id && element.subValue == monthDate.subValue);
if (index == -1) {
return false;
}
@ -79,7 +91,8 @@ class _CustomCalenderAppointmentWidgetState extends State<CustomCalenderAppointm
}
bool ifDateAlreadyThere(DateTime dt) {
int index = allDates.indexWhere((element) => dt.month == element.month && dt.year == element.year);
int index = allDates.indexWhere(
(element) => dt.month == element.month && dt.year == element.year);
if (index == -1) {
return false;
}
@ -89,7 +102,8 @@ class _CustomCalenderAppointmentWidgetState extends State<CustomCalenderAppointm
@override
Widget build(BuildContext context) {
return Consumer(
builder: (BuildContext context, AppointmentsVM appointmentsVM, Widget? child) {
builder:
(BuildContext context, AppointmentsVM appointmentsVM, Widget? child) {
return Column(
children: [
Row(
@ -101,14 +115,23 @@ class _CustomCalenderAppointmentWidgetState extends State<CustomCalenderAppointm
(DropValue value) {
setState(() {
selectedYear = int.parse(value.value.split(',')[1]);
selectedMonth = value.value.split(',')[0].getMonthNumberByName();
datesInSelectedMonth = allDates.where((element) => element.month == selectedMonth).toList();
selectedMonth =
value.value.split(',')[0].getMonthNumberByName();
datesInSelectedMonth = allDates
.where((element) =>
element.month == selectedMonth &&
element.year == selectedYear)
.toList();
_focusedDay = datesInSelectedMonth.first;
});
},
list: allMonths,
dropdownValue: DropValue(selectedMonth, "${selectedMonth.getMonthNameByNumber()}, $selectedYear", "$selectedYear"),
hint: "${selectedMonth.getMonthNameByNumber()}, $selectedYear",
dropdownValue: DropValue(
selectedMonth,
"${selectedMonth.getMonthNameByNumber()}, $selectedYear",
"$selectedYear"),
hint:
"${selectedMonth.getMonthNameByNumber()}, $selectedYear",
errorValue: "",
showAppointmentPickerVariant: true,
);
@ -132,11 +155,14 @@ class _CustomCalenderAppointmentWidgetState extends State<CustomCalenderAppointm
daysOfWeekHeight: 30,
availableGestures: AvailableGestures.none,
daysOfWeekStyle: DaysOfWeekStyle(
weekdayStyle: const TextStyle(fontSize: 14, color: MyColors.black),
weekendStyle: TextStyle(fontSize: 14, color: MyColors.black.withOpacity(0.5)),
weekdayStyle:
const TextStyle(fontSize: 14, color: MyColors.black),
weekendStyle: TextStyle(
fontSize: 14, color: MyColors.black.withOpacity(0.5)),
),
calendarBuilders: CalendarBuilders(
selectedBuilder: (BuildContext context, DateTime dateTime1, DateTime dateTime2) {
selectedBuilder: (BuildContext context, DateTime dateTime1,
DateTime dateTime2) {
return Container(
height: 50,
width: 50,
@ -152,8 +178,10 @@ class _CustomCalenderAppointmentWidgetState extends State<CustomCalenderAppointm
),
);
},
defaultBuilder: (BuildContext context, DateTime dateTime1, DateTime dateTime2) {
int index = datesInSelectedMonth.indexWhere((element) => isSameDay(dateTime1, element));
defaultBuilder: (BuildContext context, DateTime dateTime1,
DateTime dateTime2) {
int index = datesInSelectedMonth
.indexWhere((element) => isSameDay(dateTime1, element));
if (index == -1) {
return Container(
height: 50,
@ -183,7 +211,8 @@ class _CustomCalenderAppointmentWidgetState extends State<CustomCalenderAppointm
),
);
},
disabledBuilder: (BuildContext context, DateTime dateTime1, DateTime selectedDt) {
disabledBuilder: (BuildContext context, DateTime dateTime1,
DateTime selectedDt) {
return Container(
height: 50,
width: 50,
@ -209,7 +238,8 @@ class _CustomCalenderAppointmentWidgetState extends State<CustomCalenderAppointm
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
int index = datesInSelectedMonth.indexWhere((element) => isSameDay(selectedDay, element));
int index = datesInSelectedMonth
.indexWhere((element) => isSameDay(selectedDay, element));
if (index == -1) {
return;
@ -220,8 +250,10 @@ class _CustomCalenderAppointmentWidgetState extends State<CustomCalenderAppointm
_focusedDay = focusedDay;
});
int dateIndex = widget.customTimeDateSlotList.indexWhere((element) {
DateTime dateFromList = DateFormat('dd MMMM, yyyy').parse(element.date!.date);
int dateIndex =
widget.customTimeDateSlotList.indexWhere((element) {
DateTime dateFromList =
DateFormat('dd MMMM, yyyy').parse(element.date!.date);
if (isSameDay(dateFromList, selectedDay)) {
return true;
}

@ -77,8 +77,9 @@ class MyRequestsFragment extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Access guard: Only dealerships can access Special Requests
if (AppState().userType != UserType.providerDealer && AppState().currentAppType == AppType.provider) {
final isArabic = AppState().isArabic(context);
// Access guard: Only dealerships (and dealership staff users) can access Special Requests
if (AppState().userType != UserType.providerDealer && AppState().userType != UserType.dealershipUser && AppState().currentAppType == AppType.provider) {
return Scaffold(
appBar: CustomAppBar(
title: LocaleKeys.manageRequests.tr(),
@ -129,7 +130,7 @@ class MyRequestsFragment extends StatelessWidget {
})
],
Padding(
padding: EdgeInsets.only(top: requestsVM.requestsFiltersCounter > 0 ? 20 : 0, right: 21),
padding: EdgeInsets.only(top: requestsVM.requestsFiltersCounter > 0 ? 20 : 0, right: isArabic ? 0 : 16, left: isArabic ? 16 : 0),
child: Badge(
isLabelVisible: requestsVM.requestsFiltersCounter > 0,
largeSize: 15,

@ -43,7 +43,7 @@ class RequestDetailPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
isArabic ? requestDetail.vehicleTypeNameN.toText(fontSize: 16, letterSpacing: -0.64) : requestDetail.vehicleTypeName.toText(fontSize: 16, letterSpacing: -0.64),
isArabic ? requestDetail.vehicleTypeNameN.toText(fontSize: 16, letterSpacing: -0.64) : requestDetail.vehicleTypeName.toText(fontSize: 16, letterSpacing: -0.64),
showItem("${LocaleKeys.brand.tr()}: ", requestDetail.brand),
showItem("${LocaleKeys.model.tr()}: ", requestDetail.model),
showItem("${LocaleKeys.year.tr()}: ", "${requestDetail.year}"),
@ -55,7 +55,7 @@ class RequestDetailPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start,
children: [
"${isArabic ? requestDetail.cityNameN : requestDetail.city} "
"${isArabic ? requestDetail.countryNameN : requestDetail.countryName}"
"${isArabic ? requestDetail.countryNameN : requestDetail.countryName}"
.toText(
color: MyColors.lightTextColor,
),
@ -66,9 +66,9 @@ class RequestDetailPage extends StatelessWidget {
// DateTime.parse(requestDetail.createdOn!).getTimeAgo().toText(
// color: MyColors.lightTextColor,
// ),
DateTime.parse(requestDetail.createdOn!).getTimeAgo(isArabic: isArabic)
.toText(color: MyColors.lightTextColor,
),
DateTime.parse(requestDetail.createdOn!).getTimeAgo(isArabic: isArabic).toText(
color: MyColors.lightTextColor,
),
],
],
),
@ -310,17 +310,16 @@ class RequestDetailPage extends StatelessWidget {
appBar: CustomAppBar(
title: LocaleKeys.requestDetail.tr(),
actions: [
// requestDetailPageArguments.requestModel.isChatted
// ?
Padding(
padding: const EdgeInsets.only(top: 8, bottom: 8, right: 21),
child: const Icon(Icons.messenger_outline_rounded, color: Colors.black, size: 18).toContainer(
borderRadius: 100,
borderColor: MyColors.lightGreyEFColor,
isEnabledBorder: true,
),
).onPress(() => onViewChatTapped(context))
// : const SizedBox(),
requestDetailPageArguments.requestModel.isChatted
? Padding(
padding: const EdgeInsets.only(top: 8, bottom: 8, right: 21),
child: const Icon(Icons.messenger_outline_rounded, color: Colors.black, size: 18).toContainer(
borderRadius: 100,
borderColor: MyColors.lightGreyEFColor,
isEnabledBorder: true,
),
).onPress(() => onViewChatTapped(context))
: const SizedBox(),
],
),
body: SizedBox(

@ -1,58 +1,65 @@
import 'package:mc_common_app/extensions/string_extensions.dart';
import 'package:mc_common_app/extensions/int_extensions.dart';
import 'package:mc_common_app/generated/locale_keys.g.dart';
import 'package:mc_common_app/view_models/user_view_model.dart';
import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/button/show_fill_button.dart';
import 'package:mc_common_app/widgets/txt_field.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ConfirmNewPasswordPage extends StatelessWidget {
final String userToken;
ConfirmNewPasswordPage(this.userToken, {Key? key}) : super(key: key);
String newPassword = "";
@override
Widget build(BuildContext context) {
UserVM userVM = context.read<UserVM>();
return Scaffold(
appBar: CustomAppBar( isRemoveBackButton: true, title: LocaleKeys.forgetPassword.tr()),
body: Container(
width: double.infinity,
height: double.infinity,
padding: const EdgeInsets.all(40),
child: Column(
children: [
LocaleKeys.newPassword.tr().toText(height: 23 / 24, fontSize: 24, letterSpacing: -1.44,),
12.height,
TxtField(
// hint: "Enter New Password",
hint: LocaleKeys.enterNewPassword.tr(),
value: newPassword,
onChanged: (v) {
newPassword = v;
},
),
40.height,
ShowFillButton(
title: LocaleKeys.confirm.tr(),
maxWidth: double.infinity,
onPressed: () async {
await userVM.confirmPasswordOTP(
context,
newPassword: newPassword,
userToken: userToken,
);
},
),
],
),
),
);
}
}
// import 'package:mc_common_app/extensions/string_extensions.dart';
// import 'package:mc_common_app/extensions/int_extensions.dart';
// import 'package:mc_common_app/generated/locale_keys.g.dart';
// import 'package:mc_common_app/view_models/user_view_model.dart';
// import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
// import 'package:mc_common_app/widgets/button/show_fill_button.dart';
// import 'package:mc_common_app/widgets/txt_field.dart';
// import 'package:easy_localization/easy_localization.dart';
// import 'package:flutter/material.dart';
// import 'package:provider/provider.dart';
//
// class ConfirmNewPasswordPage extends StatelessWidget {
// final String userToken;
//
// ConfirmNewPasswordPage(this.userToken, {Key? key}) : super(key: key);
//
// String newPassword = "";
//
// @override
// Widget build(BuildContext context) {
// UserVM userVM = context.read<UserVM>();
// return Scaffold(
// appBar: CustomAppBar(
// isRemoveBackButton: true, title: LocaleKeys.forgetPassword.tr()),
// body: Container(
// width: double.infinity,
// height: double.infinity,
// padding: const EdgeInsets.all(20),
// child: SingleChildScrollView(
// child: Column(
// children: [
// 12.height,
// LocaleKeys.newPassword.tr().toText(
// fontSize: 20,
// letterSpacing: -1.44,
// ),
// 20.height,
// TxtField(
// hint: LocaleKeys.enterNewPassword.tr(),
// value: newPassword,
// isPasswordEnabled: true,
// isSidePaddingZero: true,
// onChanged: (v) {
// newPassword = v;
// },
// ),
// 30.height,
// ShowFillButton(
// title: LocaleKeys.confirm.tr(),
// maxWidth: double.infinity,
// onPressed: () async {
// await userVM.confirmPasswordOTP(
// context,
// newPassword: newPassword,
// userToken: userToken,
// );
// },
// ),
// ],
// ),
// ),
// ),
// );
// }
// }

@ -5,6 +5,7 @@ import 'package:mc_common_app/generated/locale_keys.g.dart';
import 'package:mc_common_app/view_models/user_view_model.dart';
import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/button/show_fill_button.dart';
import 'package:mc_common_app/widgets/txt_field.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -12,7 +13,7 @@ import 'package:provider/provider.dart';
class ConfirmNewPasswordPage extends StatefulWidget {
final String userToken;
const ConfirmNewPasswordPage(this.userToken, {Key? key}) : super(key: key);
const ConfirmNewPasswordPage(this.userToken, {super.key});
@override
State<ConfirmNewPasswordPage> createState() => _ConfirmNewPasswordPageState();
@ -21,77 +22,41 @@ class ConfirmNewPasswordPage extends StatefulWidget {
class _ConfirmNewPasswordPageState extends State<ConfirmNewPasswordPage> {
String newPassword = "";
String confirmPassword = '';
bool _obscureNewPassword = true;
bool _obscureConfirmPassword = true;
@override
Widget build(BuildContext context) {
UserVM userVM = context.read<UserVM>();
return Scaffold(
appBar: CustomAppBar(isRemoveBackButton: true, title: LocaleKeys.forgetPassword.tr()),
body: SingleChildScrollView(
child: Container(
// width: double.infinity,
// height: double.infinity,
padding: const EdgeInsets.all(40),
body: Container(
width: double.infinity,
height: double.infinity,
padding: const EdgeInsets.all(20),
child: SingleChildScrollView(
child: Column(
children: [
12.height,
LocaleKeys.newPassword.tr().toText(
height: 23 / 24,
fontSize: 24,
fontSize: 20,
letterSpacing: -1.44,
),
12.height,
TextFormField(
decoration: InputDecoration(
hintText: LocaleKeys.enterNewPassword.tr(),
hintStyle: const TextStyle(color: Colors.grey),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(5.0),
),
),
suffixIcon: IconButton(
icon: Icon(
_obscureNewPassword ? Icons.visibility_off : Icons.visibility,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureNewPassword = !_obscureNewPassword;
});
},
),
),
obscureText: _obscureNewPassword,
20.height,
TxtField(
hint: LocaleKeys.enterNewPassword.tr(),
value: newPassword,
isPasswordEnabled: true,
isSidePaddingZero: true,
onChanged: (v) => newPassword = v,
),
12.height,
TextFormField(
decoration: InputDecoration(
hintText: LocaleKeys.confirmPassword.tr(),
hintStyle: const TextStyle(color: Colors.grey),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(5.0),
),
),
suffixIcon: IconButton(
icon: Icon(
_obscureConfirmPassword ? Icons.visibility_off : Icons.visibility,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscureConfirmPassword = !_obscureConfirmPassword;
});
},
),
),
obscureText: _obscureConfirmPassword,
TxtField(
hint: LocaleKeys.confirmPassword.tr(),
value: confirmPassword,
isPasswordEnabled: true,
isSidePaddingZero: true,
onChanged: (v) => confirmPassword = v,
),
40.height,
30.height,
ShowFillButton(
title: LocaleKeys.confirm.tr(),
maxWidth: double.infinity,

@ -1,4 +1,5 @@
import 'dart:async';
import 'package:mc_common_app/classes/app_state.dart';
import 'package:mc_common_app/models/user_models/country.dart';
import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/utils/utils.dart';
@ -15,8 +16,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../classes/app_state.dart';
class ForgetPasswordPage extends StatefulWidget {
const ForgetPasswordPage({Key? key}) : super(key: key);
@ -52,95 +51,117 @@ class _ForgetPasswordPageState extends State<ForgetPasswordPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(isRemoveBackButton: true, title: LocaleKeys.changePassword.tr()),
appBar: CustomAppBar(title: LocaleKeys.changePassword.tr()),
body: Container(
width: double.infinity,
height: double.infinity,
padding: const EdgeInsets.all(20),
child: SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Column(
children: [
12.height,
LocaleKeys.forgetPassword.tr().toText(
fontSize: 20,
letterSpacing: -1.44,
),
20.height,
LocaleKeys.retrievePassword.tr().toText(
color: MyColors.lightTextColor,
textAlign: TextAlign.center,
fontSize: 14,
height: 23 / 24,
letterSpacing: -0.48,
child: Column(
children: [
12.height,
LocaleKeys.forgetPassword.tr().toText(
fontSize: 20,
letterSpacing: -1.44,
),
20.height,
LocaleKeys.retrievePassword.tr().toText(
color: MyColors.lightTextColor,
textAlign: TextAlign.center,
fontSize: 14,
height: 23 / 24,
letterSpacing: -0.48,
),
30.height,
LoginEmailTab(
onSelection: (ClassType type) {
setState(() {
this.type = type;
});
},
),
12.height,
type == ClassType.NUMBER
? Column(
children: [
getCountry(),
6.height,
TxtField(
hint: LocaleKeys.enterPhoneNumber.tr(),
value: userName,
keyboardType: TextInputType.phone,
numbersOnly: true,
isSidePaddingZero: true,
onChanged: (v) {
userName = v;
},
)
],
)
: TxtField(
hint: LocaleKeys.emailAddress.tr(),
value: userName,
keyboardType: TextInputType.emailAddress,
isSidePaddingZero: true,
onChanged: (v) {
userName = v;
},
),
30.height,
LoginEmailTab(
onSelection: (ClassType type) {
setState(() {
this.type = type;
});
},
),
12.height,
type == ClassType.NUMBER
? Column(
children: [
getCountry(),
6.height,
TxtField(
hint: "5********",
value: userName,
onChanged: (v) {
userName = v;
},
)
],
)
: TxtField(
hint: LocaleKeys.emailAddress.tr(),
value: userName,
onChanged: (v) {
userName = v;
},
),
20.height,
(type == ClassType.NUMBER ? LocaleKeys.retriveOnPhone.tr() : LocaleKeys.retriveOnEmail.tr()).toText(
color: MyColors.lightTextColor,
textAlign: TextAlign.center,
fontSize: 14,
height: 23 / 24,
letterSpacing: -0.48,
),
30.height,
ShowFillButton(
title: LocaleKeys.send.tr(),
maxWidth: double.infinity,
onPressed: () {
if (userName.isNum() && type == ClassType.NUMBER) {
userVM.forgetPasswordPhoneOTP(context, countryCode: countryCode, userName: userName, otpType: otpType);
} else if (!userName.isNum() && type == ClassType.EMAIL) {
userVM.forgetPasswordEmailOTP(context, countryCode: countryCode, userName: userName, otpType: otpType);
}
},
),
Utils.mFlex(10),
],
),
20.height,
(type == ClassType.NUMBER ? LocaleKeys.retriveOnPhone.tr() : LocaleKeys.retriveOnEmail.tr()).toText(
color: MyColors.lightTextColor,
textAlign: TextAlign.center,
fontSize: 14,
height: 23 / 24,
letterSpacing: -0.48,
),
30.height,
ShowFillButton(
title: LocaleKeys.send.tr(),
maxWidth: double.infinity,
onPressed: () {
if (!validation()) return;
if (userName.isNum() && type == ClassType.NUMBER) {
userVM.forgetPasswordPhoneOTP(context, countryCode: countryCode, userName: userName, otpType: otpType);
} else if (!userName.isNum() && type == ClassType.EMAIL) {
userVM.forgetPasswordEmailOTP(context, countryCode: countryCode, userName: userName, otpType: otpType);
}
},
),
20.height,
],
),
),
),
);
}
bool validation() {
bool isValid = true;
if (type == ClassType.NUMBER) {
if (countryCode.isEmpty) {
Utils.showToast(LocaleKeys.selectCountryCode.tr());
isValid = false;
} else if (userName.isEmpty || !userName.isNum()) {
Utils.showToast(LocaleKeys.addPhoneNo.tr());
isValid = false;
}
} else {
if (userName.isEmpty || !userName.isValidEmail()) {
Utils.showToast(LocaleKeys.enterValidEmail.tr());
isValid = false;
}
}
return isValid;
}
Widget getCountry() {
bool isArabic = AppState().isArabic(context);
if (_country != null) {
List<DropValue> dropList = [];
_country!.data?.forEach(
(element) {
// dropList.add(DropValue(element.id ?? 0, "${element.countryName ?? ""} ${element.countryCode ?? ""}", element.countryCode ?? ""));
// dropList.add(DropValue(element.id ?? 0, "${element.countryName ?? ""} ${element.countryCode ?? ""}", element.countryCode ?? ""));
dropList.add(
DropValue(
element.id ?? 0,

@ -1,155 +1,155 @@
import 'package:mc_common_app/classes/consts.dart';
import 'package:mc_common_app/config/routes.dart';
import 'package:mc_common_app/extensions/int_extensions.dart';
import 'package:mc_common_app/extensions/string_extensions.dart';
import 'package:mc_common_app/generated/locale_keys.g.dart';
import 'package:mc_common_app/utils/navigator.dart';
import 'package:mc_common_app/utils/utils.dart';
import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/button/show_image_button.dart';
import 'package:mc_common_app/widgets/dialog/dialogs.dart';
import 'package:mc_common_app/widgets/dialog/message_dialog.dart';
import 'package:mc_common_app/widgets/dialog/otp_dialog.dart';
import 'package:mc_common_app/widgets/txt_field.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
class VerifyPasswordPage extends StatelessWidget {
int otpType = 1;
String phoneNum = "";
String userName = "";
String userToken = "";
String otp = "";
VerifyPasswordPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar( isRemoveBackButton: true, title: LocaleKeys.changePassword.tr()),
body: Container(
width: double.infinity,
height: double.infinity,
padding: const EdgeInsets.all(40),
child: Column(
children: [
LocaleKeys.verifyNewPassword.tr().toText(height: 23 / 24, fontSize: 24, letterSpacing: -1.44,),
Utils.mFlex(1),
TxtField(
hint: "966500000000",
value: phoneNum,
onChanged: (v) {
phoneNum = v;
},
),
Utils.mFlex(2),
Row(
children: [
Expanded(
child: ShowImageButton(
onClick: () {
otpType = 1;
// sendPasswordOTP(context);
showMDialog(context, child: OtpDialog(
onClick: (String code) {
pop(context);
Utils.delay(300).then(
(value) => showMDialog(
context,
child: MessageDialog(
title: LocaleKeys.phoneNumberVerified.tr(),
onClick: () {
otpType = 1;
navigateWithName(context, AppRoutes.confirmNewPasswordPage);
// ForgetPasswordOTP(context);
},
),
),
);
},
));
},
title: LocaleKeys.SMS.tr(),
icon: MyAssets.icSmsSvg,
),
),
20.width,
Expanded(
child: ShowImageButton(
onClick: () {
otpType = 1;
// sendPasswordOTP(context);
showMDialog(context, child: OtpDialog(
onClick: (String code) {
pop(context);
Utils.delay(300).then(
(value) => showMDialog(
context,
child: MessageDialog(
title: LocaleKeys.phoneNumberVerified.tr(),
//"Phone Number Verified ",
onClick: () {
otpType = 2;
navigateWithName(context, AppRoutes.confirmNewPasswordPage);
// ForgetPasswordOTP(context);
},
),
),
);
},
));
},
title: LocaleKeys.whatsapp.tr(),
icon: MyAssets.icWhatsAppSvg,
),
),
],
),
Utils.mFlex(10),
],
),
),
);
}
// Future<void> sendPasswordOTP(BuildContext context) async {
// Utils.showLoading(context);
// PasswordOTPCompare otpCompare = await UserApiClent().ForgetPasswordOTPCompare(userToken, otp);
// Utils.hideLoading(context);
// if (otpCompare.messageStatus == 1) {
// // navigateWithName(context, AppRoutes.confirmNewPasswordPage);
// showMDialog(context, child: OtpDialog(
// onClick: (String code) async {
// pop(context);
// Utils.showLoading(context);
// // PasswordOTPCompare otpCompare = await UserApiClent().ForgetPasswordOTPCompare(userToken, otp);
// Utils.hideLoading(context);
// },
// ));
// } else {
// Utils.showToast(otpCompare.message ?? "");
// }
// }
// User otpRequest = User.fromJson(jsonDecode(res.body));
// SharedPrefManager.setUserToken(otpRequest.data!.accessToken ?? "");
// SharedPrefManager.setUserId(otpRequest.data!.userInfo!.userId ?? "");
// showMDialog(context, child: OtpDialog(
// onClick: (String code) {
// pop(context);
// delay(300).then(
// (value) => showMDialog(
// context,
// child: MessageDialog(
// title: "Phone Number Verified",
// onClick: () {
// otpType=1;
// navigateWithName(context, AppRoutes.confirmNewPasswordPage);
// // ForgetPasswordOTP(context);
// },
// import 'package:mc_common_app/classes/consts.dart';
// import 'package:mc_common_app/config/routes.dart';
// import 'package:mc_common_app/extensions/int_extensions.dart';
// import 'package:mc_common_app/extensions/string_extensions.dart';
// import 'package:mc_common_app/generated/locale_keys.g.dart';
// import 'package:mc_common_app/utils/navigator.dart';
// import 'package:mc_common_app/utils/utils.dart';
// import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
// import 'package:mc_common_app/widgets/button/show_image_button.dart';
// import 'package:mc_common_app/widgets/dialog/dialogs.dart';
// import 'package:mc_common_app/widgets/dialog/message_dialog.dart';
// import 'package:mc_common_app/widgets/dialog/otp_dialog.dart';
// import 'package:mc_common_app/widgets/txt_field.dart';
// import 'package:easy_localization/easy_localization.dart';
// import 'package:flutter/material.dart';
//
// class VerifyPasswordPage extends StatelessWidget {
// int otpType = 1;
// String phoneNum = "";
// String userName = "";
// String userToken = "";
// String otp = "";
//
// VerifyPasswordPage({Key? key}) : super(key: key);
//
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: CustomAppBar( isRemoveBackButton: true, title: LocaleKeys.changePassword.tr()),
// body: Container(
// width: double.infinity,
// height: double.infinity,
// padding: const EdgeInsets.all(40),
// child: Column(
// children: [
// LocaleKeys.verifyNewPassword.tr().toText(height: 23 / 24, fontSize: 24, letterSpacing: -1.44,),
// Utils.mFlex(1),
// TxtField(
// hint: "966500000000",
// value: phoneNum,
// onChanged: (v) {
// phoneNum = v;
// },
// ),
// Utils.mFlex(2),
// Row(
// children: [
// Expanded(
// child: ShowImageButton(
// onClick: () {
// otpType = 1;
// // sendPasswordOTP(context);
// showMDialog(context, child: OtpDialog(
// onClick: (String code) {
// pop(context);
// Utils.delay(300).then(
// (value) => showMDialog(
// context,
// child: MessageDialog(
// title: LocaleKeys.phoneNumberVerified.tr(),
// onClick: () {
// otpType = 1;
// navigateWithName(context, AppRoutes.confirmNewPasswordPage);
// // ForgetPasswordOTP(context);
// },
// ),
// ),
// );
// },
// ));
// },
// title: LocaleKeys.SMS.tr(),
// icon: MyAssets.icSmsSvg,
// ),
// ),
// 20.width,
// Expanded(
// child: ShowImageButton(
// onClick: () {
// otpType = 1;
// // sendPasswordOTP(context);
// showMDialog(context, child: OtpDialog(
// onClick: (String code) {
// pop(context);
// Utils.delay(300).then(
// (value) => showMDialog(
// context,
// child: MessageDialog(
// title: LocaleKeys.phoneNumberVerified.tr(),
// //"Phone Number Verified ",
// onClick: () {
// otpType = 2;
// navigateWithName(context, AppRoutes.confirmNewPasswordPage);
// // ForgetPasswordOTP(context);
// },
// ),
// ),
// );
// },
// ));
// },
// title: LocaleKeys.whatsapp.tr(),
// icon: MyAssets.icWhatsAppSvg,
// ),
// ),
// ],
// ),
// Utils.mFlex(10),
// ],
// ),
// ),
// );
// },
// ));
}
// }
//
// // Future<void> sendPasswordOTP(BuildContext context) async {
// // Utils.showLoading(context);
// // PasswordOTPCompare otpCompare = await UserApiClent().ForgetPasswordOTPCompare(userToken, otp);
// // Utils.hideLoading(context);
// // if (otpCompare.messageStatus == 1) {
// // // navigateWithName(context, AppRoutes.confirmNewPasswordPage);
// // showMDialog(context, child: OtpDialog(
// // onClick: (String code) async {
// // pop(context);
// // Utils.showLoading(context);
// // // PasswordOTPCompare otpCompare = await UserApiClent().ForgetPasswordOTPCompare(userToken, otp);
// // Utils.hideLoading(context);
// // },
// // ));
// // } else {
// // Utils.showToast(otpCompare.message ?? "");
// // }
// // }
//
// // User otpRequest = User.fromJson(jsonDecode(res.body));
// // SharedPrefManager.setUserToken(otpRequest.data!.accessToken ?? "");
// // SharedPrefManager.setUserId(otpRequest.data!.userInfo!.userId ?? "");
// // showMDialog(context, child: OtpDialog(
// // onClick: (String code) {
// // pop(context);
// // delay(300).then(
// // (value) => showMDialog(
// // context,
// // child: MessageDialog(
// // title: "Phone Number Verified",
// // onClick: () {
// // otpType=1;
// // navigateWithName(context, AppRoutes.confirmNewPasswordPage);
// // // ForgetPasswordOTP(context);
// // },
// // ),
// // ),
// // );
// // },
// // ));
// }

@ -50,7 +50,7 @@ class CustomBottomNavbar extends StatelessWidget {
label: LocaleKeys.ads.tr(),
),
// Only show Requests tab for Dealerships (not for Individual Providers)
if (isProvider && AppState().userType == UserType.providerDealer)
if (isProvider && (AppState().userType == UserType.providerDealer || AppState().userType == UserType.dealershipUser))
BottomNavigationBarItem(
icon: SvgPicture.asset(MyAssets.icRequests).paddingAll(5),
activeIcon: SvgPicture.asset(MyAssets.icRequests, color: MyColors.darkIconColor).paddingAll(5),

@ -63,11 +63,14 @@ class _DropdownFieldState extends State<DropdownField> {
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
(widget.hint ?? "").toText(
color: borderColor,
fontSize: 13,
fontWeight: MyFonts.Medium,
),
Expanded(
child: (widget.hint ?? "").toText(
color: borderColor,
fontSize: 10,
maxLines: 2,
fontWeight: MyFonts.Medium,
),
)
],
),
4.height,

@ -193,6 +193,51 @@ class AdsListShimmer extends StatelessWidget {
}
}
/// Shimmer for home page branches (horizontal circular avatar list)
class HomeBranchesShimmer extends StatelessWidget {
final int itemCount;
const HomeBranchesShimmer({super.key, this.itemCount = 4});
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: ListView.builder(
itemCount: itemCount,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemBuilder: (context, index) {
return SizedBox(
width: 90,
child: Column(
children: [
Container(
height: 73,
width: 73,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
),
const SizedBox(height: 9),
Container(
height: 14,
width: 60,
decoration: const BoxDecoration(
color: Colors.white,
),
),
],
),
);
},
),
);
}
}
/// Shimmer for provider branches grid
class BranchesGridShimmer extends StatelessWidget {
const BranchesGridShimmer({Key? key}) : super(key: key);

@ -37,6 +37,7 @@ class TxtField extends StatefulWidget {
bool numbersOnly;
bool allowOnlyOneDigit;
bool isNeedLabelOnTop;
FocusNode? focusNode;
TxtField({
super.key,
@ -68,6 +69,7 @@ class TxtField extends StatefulWidget {
this.numbersOnly = false,
this.allowOnlyOneDigit = false,
this.isNeedLabelOnTop = true,
this.focusNode,
});
@override
@ -79,10 +81,24 @@ class _TxtFieldState extends State<TxtField> {
bool _obscurePassword = true;
@override
Widget build(BuildContext context) {
void initState() {
super.initState();
controller.text = widget.value ?? "";
controller.selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length));
}
@override
void didUpdateWidget(TxtField oldWidget) {
super.didUpdateWidget(oldWidget);
// Only update if the value actually changed from parent
if (widget.value != oldWidget.value && widget.value != controller.text) {
controller.text = widget.value ?? "";
controller.selection = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
if (widget.isNeedLabelOnTop && widget.hint != null) ...[
@ -110,46 +126,77 @@ class _TxtFieldState extends State<TxtField> {
Expanded(
child: Container(
decoration: BoxDecoration(
color: widget.isBackgroundEnabled ? MyColors.textFieldColor : Colors.white,
color: widget.isBackgroundEnabled
? MyColors.textFieldColor
: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(0)),
),
child: TextField(
style: const TextStyle(color: MyColors.darkTextColor, fontSize: 15, fontWeight: MyFonts.Medium),
textInputAction: widget.isSearchBar ? TextInputAction.search : null,
style: const TextStyle(
color: MyColors.darkTextColor,
fontSize: 15,
fontWeight: MyFonts.Medium),
textInputAction:
widget.isSearchBar ? TextInputAction.search : null,
keyboardType: widget.keyboardType,
autofocus: false,
controller: controller,
focusNode: widget.focusNode,
enabled: widget.isNeedClickAll == true ? false : true,
maxLines: widget.maxLines,
maxLines:
widget.isPasswordEnabled == true ? 1 : widget.maxLines,
onTap: () {},
obscureText: widget.isPasswordEnabled == true ? _obscurePassword : false,
obscureText: widget.isPasswordEnabled == true
? _obscurePassword
: false,
onChanged: widget.onChanged,
onSubmitted: widget.onSubmitted,
inputFormatters: widget.allowOnlyOneDigit && widget.numbersOnly
? <TextInputFormatter>[FilteringTextInputFormatter.allow(RegExp('[0-9]')), LengthLimitingTextInputFormatter(1)]
inputFormatters: widget.allowOnlyOneDigit &&
widget.numbersOnly
? <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp('[0-9]')),
LengthLimitingTextInputFormatter(1)
]
: widget.numbersOnly
? <TextInputFormatter>[FilteringTextInputFormatter.allow(RegExp('[0-9]'))]
? <TextInputFormatter>[
FilteringTextInputFormatter.allow(
RegExp('[0-9]'))
]
: [],
decoration: InputDecoration(
labelText: widget.lable,
alignLabelWithHint: true,
fillColor: Colors.white,
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: widget.isNeedBorder ? MyColors.darkPrimaryColor : Colors.transparent, width: widget.isNeedBorder ? 2.0 : 0),
borderSide: BorderSide(
color: widget.isNeedBorder
? MyColors.darkPrimaryColor
: Colors.transparent,
width: widget.isNeedBorder ? 2.0 : 0),
borderRadius: BorderRadius.circular(0.0),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: widget.isNeedBorder ? MyColors.darkPrimaryColor : Colors.transparent, width: widget.isNeedBorder ? 2.0 : 0),
borderSide: BorderSide(
color: widget.isNeedBorder
? MyColors.darkPrimaryColor
: Colors.transparent,
width: widget.isNeedBorder ? 2.0 : 0),
borderRadius: BorderRadius.circular(0.0),
),
disabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: widget.isNeedBorder ? MyColors.darkPrimaryColor : Colors.transparent, width: widget.isNeedBorder ? 2.0 : 0),
borderSide: BorderSide(
color: widget.isNeedBorder
? MyColors.darkPrimaryColor
: Colors.transparent,
width: widget.isNeedBorder ? 2.0 : 0),
borderRadius: BorderRadius.circular(0.0),
),
suffixIcon: widget.isPasswordEnabled == true
? IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_off : Icons.visibility,
_obscurePassword
? Icons.visibility_off
: Icons.visibility,
color: Colors.grey,
),
onPressed: () {
@ -161,13 +208,23 @@ class _TxtFieldState extends State<TxtField> {
: widget.postfixData != null
? InkWell(
onTap: widget.onPostFixPressed,
child: Icon(widget.postfixData, color: widget.postFixDataColor),
child: Icon(widget.postfixData,
color: widget.postFixDataColor),
)
: widget.postfixWidget,
prefixIcon: widget.prefixData != null ? Icon(widget.prefixData, color: borderColor) : widget.preFixWidget,
labelStyle: const TextStyle(color: borderColor, fontSize: 13, fontWeight: MyFonts.Medium),
hintStyle: const TextStyle(color: borderColor, fontSize: 13, fontWeight: MyFonts.Medium),
hintText: widget.isNeedLabelOnTop ? null : widget.hint ?? "",
prefixIcon: widget.prefixData != null
? Icon(widget.prefixData, color: borderColor)
: widget.preFixWidget,
labelStyle: const TextStyle(
color: borderColor,
fontSize: 13,
fontWeight: MyFonts.Medium),
hintStyle: const TextStyle(
color: borderColor,
fontSize: 13,
fontWeight: MyFonts.Medium),
hintText:
widget.isNeedLabelOnTop ? null : widget.hint ?? "",
contentPadding: widget.prefixData == null
? EdgeInsets.only(
left: 12,

@ -124,3 +124,6 @@ flutter:
- asset: assets/fonts/GESS-Two-Bold.otf
- asset: assets/fonts/GESS-Two-Medium.otf
# copilot --resume=0a04ca57-cca2-4462-8e1b-34d84ab450e8
Loading…
Cancel
Save