Reserve Module Completed

faiz_development_common
FaizHashmi 2 days ago
parent c02319fefa
commit f75c1a1130

@ -597,6 +597,8 @@ extension AdReserveStatusEnum on int {
} else if (this == 5) {
return AdReserveStatus.dealDone;
} else if (this == 6) {
return AdReserveStatus.paymentDoneByCustomer;
} else if (this == 7) {
return AdReserveStatus.fullPaymentVerified;
} else {
return AdReserveStatus.defaultStatus;

@ -43,6 +43,9 @@ class AdDetailsModel {
String? modifiedOn;
AdPostStatus? adPostStatus;
AdReserveStatus? adReserveStatus;
int? adsReserveId; // Reserve ID from AdsReserve_Get API
bool? isReservable;
String? reserveCustomerMessage;
bool? isMyAd;
bool? isReservedByMe;
bool? isAdFavorite;
@ -92,6 +95,9 @@ class AdDetailsModel {
this.isOnWhatsApp,
this.adPostStatus,
this.adReserveStatus,
this.adsReserveId,
this.isReservable,
this.reserveCustomerMessage,
this.isMyAd,
this.isReservedByMe,
this.isAdFavorite,
@ -160,6 +166,8 @@ class AdDetailsModel {
adOwnerDetails = (json['vehicle'] != null && json['vehicle']['aDsUser'] != null) ? (AdOwnerDetails.fromJson(json['vehicle']['aDsUser'])) : null;
adPostStatus = ((json['statusID'] ?? 1) as int).toAdPostEnum();
adReserveStatus = AdReserveStatus.defaultStatus;
isReservable = json['isReservable'] ?? false;
reserveCustomerMessage = json['reserveCustomerMessage'];
createdByRoleEnum = (json['createdByRole'] as int).toCreatedByRoleEnum();
isMyAd = isMyAds;
isReservedByMe = false;

@ -74,7 +74,7 @@ abstract class AdsRepo {
Future<GenericRespModel> deleteAd({required int adId});
Future<GenericRespModel> cancelMyAdReservation({required int adId, required int adsReserveStatus, required String reason});
Future<GenericRespModel> cancelMyAdReservation({required int adId, required int adsReserveId, required int adsReserveStatus, required String reason});
Future<GenericRespModel> createReserveAd({required int adId});
@ -450,6 +450,7 @@ class AdsRepoImp implements AdsRepo {
Future<List<AdDetailsModel>> getMyReservedAds() async {
var params = {
"userID": appState.getUser.data!.userInfo!.userId ?? "",
"IsAdCurrentlyReserved": "true",
};
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(
@ -492,26 +493,29 @@ class AdsRepoImp implements AdsRepo {
List<AdDetailsModel> vehicleAdsDetails = List.generate(adsGenericModel.data.length, (index) => AdDetailsModel.fromJson(adsGenericModel.data[index], true, adsGenericModel.totalItemsCount ?? 1));
// Create a map for O(1) lookup: adsID -> reserve status
// Create maps for O(1) lookup: adsID -> reserve status and reserve ID
Map<int, AdReserveStatus> statusMap = {};
Map<int, int> reserveIdMap = {}; // adsID -> reserve ID
for (var reserved in reservedAds) {
log("adsReserveStatus: ${reserved.adsReserveStatus}");
statusMap[reserved.adsID ?? 0] = (reserved.adsReserveStatus ?? 0).toAdRserveStatusEnum();
reserveIdMap[reserved.adsID ?? 0] = reserved.id ?? 0; // Store reserve ID
}
log("📋 Status Map created: ${statusMap.length} entries");
statusMap.forEach((key, value) {
log(" Ad ID: $key -> Status: $value");
log(" Ad ID: $key -> Status: $value, Reserve ID: ${reserveIdMap[key]}");
});
// Assign reserve status by matching ad ID (not by index!)
// Assign reserve status and reserve ID by matching ad ID (not by index!)
log("📦 Processing ${vehicleAdsDetails.length} ads from second API");
for (var ad in vehicleAdsDetails) {
if (statusMap.containsKey(ad.id)) {
ad.adReserveStatus = statusMap[ad.id]!;
ad.adsReserveId = reserveIdMap[ad.id]!; // Store reserve ID
// Backend will set adPostStatus=reserved in the response, so no need to set it manually
log(" ✅ Ad ${ad.id}: Assigned adReserveStatus ${ad.adReserveStatus}");
log(" ✅ Ad ${ad.id}: Assigned adReserveStatus ${ad.adReserveStatus}, adsReserveId ${ad.adsReserveId}");
} else {
// Ad exists but no reserve status found - shouldn't happen but handle it
ad.adReserveStatus = AdReserveStatus.defaultStatus;
@ -689,10 +693,11 @@ class AdsRepoImp implements AdsRepo {
}
@override
Future<GenericRespModel> cancelMyAdReservation({required int adId, required int adsReserveStatus, required String reason}) async {
Future<GenericRespModel> cancelMyAdReservation({required int adId, required int adsReserveId, required int adsReserveStatus, required String reason}) async {
int customerID = AppState().getUser.data!.userInfo!.customerId ?? 0;
var postParams = {
"ID": adsReserveId, // Reserve ID (mandatory as per backend requirement)
"adsID": adId,
"adsReserveStatus": adsReserveStatus,
"comment": reason,
@ -793,7 +798,7 @@ class AdsRepoImp implements AdsRepo {
String token = appState.getUser.data!.accessToken ?? "";
GenericRespModel adsGenericModel = await apiClient.postJsonForObject(
(json) => GenericRespModel.fromJson(json),
"",
ApiConsts.reserveAdPaymentOnDealDoneCreate,
postParams,
token: token,
);

@ -44,6 +44,7 @@ enum AdReserveStatus {
cancelledByAdmin,
timeOver,
dealDone,
paymentDoneByCustomer,
fullPaymentVerified,
}

@ -276,6 +276,11 @@ class Utils {
return MyColors.adCancelledStatusColor; // Cancelled - red
case AdReserveStatus.dealDone:
return MyColors.textColor; // pending - gray
case AdReserveStatus.paymentDoneByCustomer:
return MyColors.textColor; // paymentDone - green
case AdReserveStatus.fullPaymentVerified:
return MyColors.greenColor; // Completed successfully - green
@ -299,8 +304,11 @@ class Utils {
case AdReserveStatus.cancelledByAdmin:
return LocaleKeys.cancelledByAdmin.tr();
case AdReserveStatus.dealDone:
return LocaleKeys.dealCompleted.tr();
case AdReserveStatus.dealDone: // means pending complete payment
return LocaleKeys.pendingForPayment.tr();
case AdReserveStatus.paymentDoneByCustomer:
return LocaleKeys.waitingAdminsResponse.tr();
case AdReserveStatus.fullPaymentVerified:
return LocaleKeys.paymentVerified.tr();

@ -14,6 +14,7 @@ import 'package:mc_common_app/main.dart';
import 'package:mc_common_app/models/advertisment_models/ad_details_model.dart';
import 'package:mc_common_app/models/advertisment_models/ads_bank_details_model.dart';
import 'package:mc_common_app/models/advertisment_models/ads_duration_model.dart';
import 'package:mc_common_app/models/advertisment_models/pdf_receipt_model.dart';
import 'package:mc_common_app/models/advertisment_models/special_service_model.dart';
import 'package:mc_common_app/models/advertisment_models/ss_car_check_schedule_model.dart';
import 'package:mc_common_app/models/advertisment_models/ss_photo_schedule_model.dart';
@ -522,7 +523,7 @@ class AdVM extends BaseVM {
Future<void> getAdBankingAccountInfo({required int adId}) async {
setState(ViewState.busy);
try {
adsBankDetailsModel = await adsRepo.getAdBankingAccountInfo(adId: 21);
adsBankDetailsModel = await adsRepo.getAdBankingAccountInfo(adId: adId);
setState(ViewState.idle);
} catch (e) {
setState(ViewState.idle);
@ -562,9 +563,9 @@ class AdVM extends BaseVM {
navigateReplaceWithName(context, AppRoutes.dashboard, arguments: DashboardRouteEnum.fromAdsSubmit);
}
Future<void> cancelMyAdReservation(BuildContext context, {required int adId, required String reason}) async {
Future<void> cancelMyAdReservation(BuildContext context, {required int adId, required int adsReserveId, required String reason}) async {
Utils.showLoading(context);
GenericRespModel respModel = await adsRepo.cancelMyAdReservation(adId: adId, adsReserveStatus: 11, reason: reason); // 11 is to Cancel Reservation
GenericRespModel respModel = await adsRepo.cancelMyAdReservation(adId: adId, adsReserveId: adsReserveId, adsReserveStatus: 2, reason: reason); // 2 for AdsReserveStatus is to Cancel Reservation
if (respModel.messageStatus != 1) {
Utils.hideLoading(context);
@ -1773,31 +1774,81 @@ class AdVM extends BaseVM {
notifyListeners();
}
List<ImageModel> pickedReceiptPdfFiles = [];
// Single receipt file for Complete Deal flow
ImageModel? pickedReceiptFile;
String receiptPdfFileError = "";
String receiptFileError = "";
void removePdfFileFromList(String filePath) {
int index = pickedReceiptPdfFiles.indexWhere((element) => element.filePath == filePath);
if (index == -1) {
return;
}
pickedReceiptPdfFiles.removeAt(index);
void removeReceiptFile() {
pickedReceiptFile = null;
notifyListeners();
}
void pickPdfReceiptFile(BuildContext context) async {
List<ImageModel> imageModels = [];
List<File>? files = await commonServices.pickMultipleFiles(context);
if (files == null) return;
for (var element in files) {
imageModels.add(ImageModel(filePath: element.path, isFromNetwork: false));
}
pickedReceiptPdfFiles.addAll(imageModels);
if (pickedReceiptPdfFiles.isNotEmpty) receiptPdfFileError = "";
void pickReceiptFile(BuildContext context) async {
List<File>? files = await commonServices.pickMultipleFiles(context, allowMultiple: false);
if (files == null || files.isEmpty) return;
pickedReceiptFile = ImageModel(filePath: files.first.path, isFromNetwork: false);
if (pickedReceiptFile != null) receiptFileError = "";
notifyListeners();
}
// Upload bank receipt for Complete Deal
Future<void> uploadBankReceipt(BuildContext context, {required int adId}) async {
if (pickedReceiptFile == null) {
receiptFileError = LocaleKeys.selectAttachment.tr();
notifyListeners();
return;
}
try {
Utils.showLoading(context);
// Convert file to base64
File file = File(pickedReceiptFile!.filePath!);
List<int> fileBytes = await file.readAsBytes();
String base64File = base64Encode(fileBytes);
String fileName = file.path.split('/').last;
// Create PdfReceiptModel for API
PdfReceiptModel pdfReceipt = PdfReceiptModel(
id: 0,
pdfName: fileName,
pdfString: base64File,
);
// Call repository method
GenericRespModel response = await adsRepo.uploadBankReceiptsOnReserveDealDone(
adId: adId,
detailNote: completeDealNotesForAdmin,
pdfReceiptsList: [pdfReceipt],
);
Utils.hideLoading(context);
if (response.messageStatus == 1) {
Utils.showToast(response.message ?? "Receipt uploaded successfully");
// Reset values
pickedReceiptFile = null;
completeDealNotesForAdmin = "";
receiptFileError = "";
notifyListeners();
// Close bottom sheet
if (context.mounted) {
Navigator.of(context).pop();
}
} else {
Utils.showToast(response.message ?? "Failed to upload receipt");
}
} catch (e) {
Utils.hideLoading(context);
Utils.showToast("Error uploading receipt: $e");
log("Error in uploadBankReceipt: $e");
}
}
// sourceFlag for Camera = 0
// sourceFlag for Gallery = 1
void pickDamagePartImage(int index) async {
@ -1882,6 +1933,9 @@ class AdVM extends BaseVM {
vehicleDamageCards.clear();
specialServiceCards.clear();
vehicleAdsSpecialServices.clear();
pickedReceiptFile = null;
completeDealNotesForAdmin = "";
receiptFileError = "";
currentProgressStep = AdCreationStepsEnum.vehicleDetails;
vehicleTypeId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
vehicleModelId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");

@ -119,11 +119,21 @@ class DashboardVmCustomer extends BaseVM with WidgetsBindingObserver {
await appointmentsVM.applyFilterOnBranches(index: 0); // to get all branches!
await appointmentsVM.getMyRecentBranches(); // to get my recent branches
if (dashboardRouteEnum != DashboardRouteEnum.fromAdsPayment && dashboardRouteEnum != DashboardRouteEnum.fromAdsSubmit) {
if (dashboardRouteEnum != DashboardRouteEnum.fromAdsPayment &&
dashboardRouteEnum != DashboardRouteEnum.fromAdsSubmit &&
dashboardRouteEnum != DashboardRouteEnum.fromReserveAdsPayment) {
await adVM.getMyAds();
await adVM.getExploreAds();
}
// Handle reserve ads payment completion
if (dashboardRouteEnum == DashboardRouteEnum.fromReserveAdsPayment) {
adVM.updateIsExploreAds(false);
onNavbarTapped(2); // Go to My Ads tab (customer has different tab index)
await adVM.applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.reserved);
log("✅ Successfully navigated to Reserved Ads after payment");
}
await requestsVM.getRequests();
await adVM.getVehicleTypes();
await adVM.getVehicleAdsDuration();

@ -1,3 +1,4 @@
import 'dart:developer';
import 'dart:io';
import 'package:flutter/widgets.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
@ -135,10 +136,19 @@ class DashboardVMProvider extends BaseVM with WidgetsBindingObserver {
await requestsVM.getRequests();
await subscriptionsVM.getSubscriptionBySP(AppState().getUser.data?.userInfo?.providerId.toString() ?? "", true);
if (dashboardRouteEnum != DashboardRouteEnum.fromAdsPayment && dashboardRouteEnum != DashboardRouteEnum.fromAdsSubmit) {
if (dashboardRouteEnum != DashboardRouteEnum.fromAdsPayment && dashboardRouteEnum != DashboardRouteEnum.fromAdsSubmit && dashboardRouteEnum != DashboardRouteEnum.fromReserveAdsPayment) {
await adVM.getMyAds();
await adVM.getExploreAds();
}
// Handle reserve ads payment completion
if (dashboardRouteEnum == DashboardRouteEnum.fromReserveAdsPayment) {
adVM.updateIsExploreAds(false);
onNavbarTapped(3); // Go to My Ads tab
await adVM.applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.reserved);
log("✅ Successfully navigated to Reserved Ads after payment");
}
await shippingManagementVM.populateShippingRequestFilterList();
await shippingManagementVM.populateSelfPickupRequestFilterList();
await appointmentVM.applyFilterOnAppointmentsVMForProviders(

@ -246,39 +246,16 @@ class PaymentVM extends ChangeNotifier {
void onAdReservePaymentSuccess(BuildContext context) {
log("onAdReservePaymentSuccess - Reserve ads payment completed");
// Always use navigatorKey for reliable navigation after payment
// Payment flow often unmounts context, so navigatorKey is more reliable
// Navigate to dashboard with reserved ads route argument
// The dashboard's onRefresh method will handle setting up the reserved filter
log("Navigating to dashboard with reserved ads filter");
// Navigate to dashboard first
navigatorKey.currentState?.pushNamedAndRemoveUntil(
AppRoutes.dashboard,
(route) => false,
arguments: DashboardRouteEnum.fromReserveAdsPayment,
);
// Use a post-frame callback to ensure navigation completes before updating state
WidgetsBinding.instance.addPostFrameCallback((_) {
final context = navigatorKey.currentContext;
if (context != null && context.mounted) {
try {
// Navigate to My Ads tab and apply Reserved filter
context.read<AdVM>().updateIsExploreAds(false);
if (AppState().currentAppType == AppType.provider) {
context.read<DashboardVMProvider>().onNavbarTapped(3); // My Ads tab
context.read<AdVM>().applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.reserved);
} else {
context.read<DashboardVmCustomer>().onNavbarTapped(3); // My Ads tab
context.read<AdVM>().applyFilterOnMyAds(adPostStatusEnum: AdPostStatus.reserved);
}
log("✅ Successfully navigated to Reserved Ads");
} catch (e) {
log("⚠️ Error updating AdVM after navigation: $e");
}
} else {
log("⚠️ Context not available for post-navigation updates");
}
});
log("✅ Navigation initiated - Dashboard will handle filter setup");
}
void onSubscriptionPaymentSuccess() {

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/app_state.dart';
@ -50,9 +51,10 @@ class _AdsDetailViewState extends State<AdsDetailView> {
}
Future<void> onAdDetailsLoaded() async {
log("${widget.adDetails.isMyAd}, ${widget.adDetails.isReservedByMe} ,${widget.adDetails.adPostStatus}");
context.read<PaymentVM>().updateCurrentAdId(id: widget.adDetails.id!);
// Load banking info when viewing MY created ad that someone else reserved
if ((widget.adDetails.isMyAd ?? false) && !(widget.adDetails.isReservedByMe ?? false) && (widget.adDetails.adPostStatus == AdPostStatus.reserved)) {
if ((widget.adDetails.isMyAd ?? false) && (widget.adDetails.isReservedByMe ?? false) && (widget.adDetails.adPostStatus == AdPostStatus.reserved)) {
await adVM.getAdBankingAccountInfo(adId: widget.adDetails.id!);
}
}

@ -47,6 +47,25 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
description: Column(
crossAxisAlignment: CrossAxisAlignment.start,
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(
color: MyColors.lightGreyBgColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: MyColors.borderColor),
),
child: adDetailsModel.reserveCustomerMessage!.toText(
fontSize: 14,
maxLines: 10,
height: 1.5,
),
),
20.height,
],
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -91,7 +110,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.carInsuranceService.tr().toText(fontSize: 14, isBold: true, color: MyColors.lightTextColor, fontWeight: FontWeight.w500),
LocaleKeys.carInsuranceService.tr().toText(fontSize: 14, color: MyColors.lightTextColor, fontWeight: FontWeight.w500),
LocaleKeys.toBeDecided.tr().toText(fontSize: 12, isBold: true),
],
),
@ -99,7 +118,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.registrationCarPlates.tr().toText(fontSize: 14, isBold: true, color: MyColors.lightTextColor),
LocaleKeys.registrationCarPlates.tr().toText(fontSize: 14, color: MyColors.lightTextColor),
LocaleKeys.toBeDecided.tr().toText(fontSize: 12, isBold: true),
],
),
@ -107,7 +126,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.homeDeliveryService.tr().toText(fontSize: 14, isBold: true, color: MyColors.lightTextColor),
LocaleKeys.homeDeliveryService.tr().toText(fontSize: 14, color: MyColors.lightTextColor),
LocaleKeys.toBeDecided.tr().toText(fontSize: 12, isBold: true),
],
),
@ -262,7 +281,13 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget {
case CreatedByRoleEnum.provider:
return defaultActionForProviderAndCustomer(context, adDetailsModel);
case CreatedByRoleEnum.admin:
return reserveAdAction(context, adDetailsModel);
// Check if admin ad is reservable
if (adDetailsModel.isReservable == true && adDetailsModel.adPostStatus != AdPostStatus.sold) {
return reserveAdAction(context, adDetailsModel);
} else {
// Show contact buttons just like customer/provider ads
return defaultActionForProviderAndCustomer(context, adDetailsModel);
}
case CreatedByRoleEnum.allAds:
return const SizedBox.shrink();
}
@ -799,18 +824,22 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget {
// Save reason before showing confirmation dialog
final String cancelReason = adVM.reservationCancelReason;
return actionConfirmationBottomSheet(
context: parentContext, // Use parent context
context: parentContext,
// Use parent context
title: LocaleKeys.doWantCancelReservation.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44),
subtitle: "yourAdVisibletoBuy.".tr(),
subtitle: LocaleKeys.yourAdVisibletoBuy.tr(),
actionButtonYes: Expanded(
child: ShowFillButton(
maxHeight: 55,
title: LocaleKeys.yes.tr(),
fontSize: 15,
onPressed: () async {
Navigator.pop(parentContext); // Pop confirmation
// Call with parent context that's still valid
await adVM.cancelMyAdReservation(parentContext, adId: adDetails.id!, reason: cancelReason);
Navigator.pop(parentContext); // Pop confirmation
// Call with parent context that's still valid, pass reserve ID
await adVM.cancelMyAdReservation(parentContext,
adId: adDetails.id!,
adsReserveId: adDetails.adsReserveId ?? 0, // Pass reserve ID
reason: cancelReason);
},
),
),
@ -910,42 +939,93 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget {
),
15.height,
LocaleKeys.attachFile.tr().toText(fontSize: 20, isBold: true, letterSpacing: -0.5),
if (adVM.pickedReceiptPdfFiles.isNotEmpty) ...[
16.height,
PickedFilesContainer(
pickedFiles: adVM.pickedReceiptPdfFiles,
onCrossPressedPrimary: adVM.removePdfFileFromList,
onAddFilePressed: () {
context.read<AdVM>().pickPdfReceiptFile(context);
},
isPdf: true,
),
16.height,
if (adVM.pickedReceiptFile != null) ...[
// Show selected file
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: MyColors.lightGreyBgColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: MyColors.borderColor),
),
child: Row(
children: [
Icon(
adVM.pickedReceiptFile!.filePath!.endsWith('.pdf') ? Icons.picture_as_pdf : Icons.image,
size: 40,
color: MyColors.primaryColor,
),
12.width,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
adVM.pickedReceiptFile!.filePath!.split('/').last.toText(fontSize: 14, isBold: true, maxLines: 1),
4.height,
LocaleKeys.change.tr().toText(
fontSize: 12,
color: MyColors.lightTextColor,
),
],
),
),
IconButton(
icon: const Icon(Icons.close, color: MyColors.redColor),
onPressed: () {
context.read<AdVM>().removeReceiptFile();
},
),
],
),
).onPress(() {
context.read<AdVM>().pickReceiptFile(context);
}),
] else ...[
// Show add file button (original design)
Row(
children: [
Container(
height: 90,
width: 90,
decoration: BoxDecoration(color: MyColors.greyButtonColor, border: Border.all(width: 2, color: MyColors.greyAddBorderColor)),
decoration: BoxDecoration(
color: MyColors.greyButtonColor,
border: Border.all(width: 2, color: MyColors.greyAddBorderColor),
),
margin: const EdgeInsets.all(8),
alignment: Alignment.center,
child: Container(
height: 24,
width: 24,
decoration: const BoxDecoration(shape: BoxShape.circle, color: MyColors.darkTextColor),
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: MyColors.darkTextColor,
),
child: const Icon(Icons.add, color: MyColors.white),
),
).onPress(() {
context.read<AdVM>().pickPdfReceiptFile(context);
context.read<AdVM>().pickReceiptFile(context);
}),
],
),
],
if (adVM.receiptFileError.isNotEmpty) ...[
8.height,
adVM.receiptFileError.toText(
fontSize: 12,
color: MyColors.redColor,
),
],
15.height,
ShowFillButton(
title: LocaleKeys.submit.tr(),
onPressed: () {
//Upload Attachment
if (adVM.pickedReceiptFile == null) {
adVM.receiptFileError = LocaleKeys.selectAttachment.tr();
adVM.notifyListeners();
return;
}
adVM.uploadBankReceipt(context, adId: adDetails.id!);
},
maxWidth: double.infinity,
),
@ -1035,6 +1115,9 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget {
}
return pendingForReviewAction(pendingText: LocaleKeys.waitingAdminsResponse.tr());
case AdReserveStatus.paymentDoneByCustomer:
return pendingForReviewAction(pendingText: LocaleKeys.waitingAdminsResponse.tr());
case AdReserveStatus.fullPaymentVerified:
return pendingForReviewAction(pendingText: LocaleKeys.paymentVerified.tr());
@ -1045,8 +1128,6 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget {
@override
Widget build(BuildContext context) {
log("adDetailsModel.adPostStatus: ${adDetailsModel.adPostStatus}");
switch (adDetailsModel.adPostStatus!) {
case AdPostStatus.pendingForPayment:
return pendingForPaymentAction(context, ad: adDetailsModel);

@ -235,7 +235,6 @@ class _AdCardState extends State<AdCard> {
if (widget.adDetails.adPostStatus == AdPostStatus.sold) ...[
// Blur overlay
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
child: Container(
@ -248,10 +247,7 @@ class _AdCardState extends State<AdCard> {
// SOLD text
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(4),
),
decoration: const BoxDecoration(color: Colors.red),
child: LocaleKeys.sold.tr().toText(
fontSize: 12,
color: Colors.white,
@ -275,25 +271,38 @@ class _AdCardState extends State<AdCard> {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// Status tags row (for explore ads)
if (isAdsFragment && context.read<AdVM>().isExploreAdsTapped) ...[
Wrap(
spacing: 4,
runSpacing: 4,
children: [
// Show "Mowater Ad" tag for admin ads
if (widget.adDetails.createdByRoleEnum == CreatedByRoleEnum.admin) Utils.statusContainerChip(text: "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 &&
widget.adDetails.adReserveStatus != AdReserveStatus.defaultStatus &&
context.read<AdVM>().isReservedFilterSelected) ...[
Utils.statusContainerChip(
text: Utils.getReserveStatusLabel(widget.adDetails.adReserveStatus!),
chipColor: Utils.getChipColorByReserveStatus(widget.adDetails.adReserveStatus!)
),
text: Utils.getReserveStatusLabel(widget.adDetails.adReserveStatus!), chipColor: Utils.getChipColorByReserveStatus(widget.adDetails.adReserveStatus!)),
]
// Otherwise, show regular ad status for MyAds
else if (isAdsFragment && widget.adDetails.adPostStatus! == AdPostStatus.sold && widget.adDetails.statuslabel != null && widget.adDetails.statuslabel!.isNotEmpty) ...[
Utils.statusContainerChip(text: widget.adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(widget.adDetails.adPostStatus!)),
Padding(
padding: const EdgeInsets.only(top: 5),
child: Utils.statusContainerChip(text: widget.adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(widget.adDetails.adPostStatus!)),
),
] else if (isAdsFragment && !context.read<AdVM>().isExploreAdsTapped && widget.adDetails.statuslabel != null && widget.adDetails.statuslabel!.isNotEmpty) ...[
Utils.statusContainerChip(text: widget.adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(widget.adDetails.adPostStatus!)),
],
// Show Accident Free chip for explore ads when isAccidentFree is true
if (isAdsFragment && context.read<AdVM>().isExploreAdsTapped && widget.adDetails.vehicle?.isAccidentFree == true) ...[
Utils.statusContainerChip(text: LocaleKeys.accidentFree.tr(), chipColor: Colors.green),
Padding(
padding: const EdgeInsets.only(top: 5),
child: Utils.statusContainerChip(text: widget.adDetails.statuslabel ?? "", chipColor: Utils.getChipColorByAdStatus(widget.adDetails.adPostStatus!)),
),
],
(widget.adDetails.vehicle!.vehicleTitle ?? "").toText(
fontSize: 16,

Loading…
Cancel
Save