Bug Fixes

faiz_development_common
FaizHashmi 3 hours ago
parent 311157b0d7
commit ba0c2c56e7

@ -215,7 +215,7 @@ class GlobalConsts {
// When false, API logs print only the request URL and payload. When true, the
// response body is also printed. Flip this on only while actively debugging a
// specific API issue -- flip back off afterwards, since response bodies are noisy.
static bool showApiResponseInLogs = false;
static bool showApiResponseInLogs = true;
static String appNameCustomer = "MOWATER Customer";
static String appNameProvider = "MOWATER Provider";

@ -416,7 +416,7 @@ class AdsRepoImp implements AdsRepo {
"CityIDs": cityIdsList ?? [],
"VehicleTypeIDs": vehicleTypeIdsList ?? [],
"VehicleBrandIDs": vehicleBrandIdsList ?? [],
"VehicleModelIDs": vehicleModelIdsList ?? [],
"VehicleModels": vehicleModelIdsList ?? [],
"VehicleModelYearIDs": vehicleModelYearIdsList ?? [],
"CreatedByRoles": createdByRolesIdsList ?? [],
"AdsStatuses": ["${AdPostStatus.active.getIdFromAdPostStatusEnum()}"], //only Active ADS
@ -509,7 +509,7 @@ class AdsRepoImp implements AdsRepo {
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) {

@ -251,8 +251,8 @@ class AppointmentRepoImp implements AppointmentRepo {
String providerId = appState.getUser.data!.userInfo!.providerId.toString();
var params = {
"ServiceProviderID": providerId.toString(),
"ProviderBranchID": branchID.toString(),
"IsSearchFilter": true.toString(),
"ProviderBranchID": branchID.toString(),
"SPServiceIDs": serviceProviderServiceIdsList ?? [],
"CustomerNames": customerNamesList ?? [],
"CustomerMobileNos": customerPhonesList ?? [],
@ -260,6 +260,9 @@ class AppointmentRepoImp implements AppointmentRepo {
"FromDate": fromDate ?? "",
"DateTo": toDate ?? "",
};
// 0 means "all branches" (e.g. the home dashboard carousel) -- omit the key entirely
// rather than sending "0", since branch IDs are real backend records starting at 1
// and "0" likely just doesn't match any branch, returning an empty result.
if (appointmentStatusEnum != null) {
params.addAll({"AppointmentStatusID": appointmentStatusEnum.getIdFromAppointmentStatusEnum().toString()});

@ -1526,7 +1526,7 @@ class AdVM extends BaseVM {
final isArabic = AppState().isArabic(context);
actionConfirmationBottomSheet(
context: context,
title:( isArabic ? "حفظ كمسودة" : "Save As Draft").toText(fontSize: 28, isBold: true, letterSpacing: -1.44),
title: (isArabic ? "حفظ كمسودة" : "Save As Draft").toText(fontSize: 28, isBold: true, letterSpacing: -1.44),
subtitle: isArabic
? "سيتم حفظ إعلانك كمسودة، ويمكنك المتابعة لاحقًا من حيث توقفت. هل تريد الحفظ والرجوع؟"
: "Your ad will be saved as draft and you can continue later from where you left off. Do you want to save and go back?",
@ -2056,6 +2056,22 @@ class AdVM extends BaseVM {
isAccidentFree = false;
isAccidentFreeSet = false;
accidentFreeError = "";
// These error strings aren't part of a SelectionModel (unlike the dropdowns reset
// above), so they were never cleared here -- confirmed on a real device: once
// isVehicleDetailsValidated() set one of these, it stayed set and showed up on the
// very next ad creation, before the user had touched anything.
demandAmountError = "";
vehicleVinError = "";
vehicleTitleError = "";
warrantyError = "";
vehicleDescError = "";
vehicleImageError = "";
// Edit-mode flags also need resetting -- isAdEditEnabled in particular gates the
// minimum-image-count check in isVehicleDetailsValidated(), so leaving it stale as
// true would silently skip that requirement for a genuinely new ad.
isAdEditEnabled = false;
isDraftEditEnabled = false;
isExtendAdEditEnabled = false;
clearSpecialServiceCard();
updateFinanceAvailableStatus(false);
notifyListeners();
@ -3000,40 +3016,59 @@ class AdVM extends BaseVM {
await updateSelectionVehicleBrandId(SelectionModel(selectedId: vehicleBrands[index].id!, selectedOption: vehicleBrands[index].vehicleBrandDescription ?? ""));
}
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(
selectedId: previousAdDetails!.vehicle!.color!.id!, selectedOption: isArabic ? previousAdDetails!.vehicle!.color!.labelN.toString() : previousAdDetails!.vehicle!.color!.label ?? ""));
updateSelectionVehicleConditionId(SelectionModel(
selectedId: previousAdDetails!.vehicle!.condition!.id!,
selectedOption: isArabic ? previousAdDetails!.vehicle!.condition!.labelN.toString() : previousAdDetails!.vehicle!.condition!.label ?? ""));
updateSelectionVehicleCategoryId(SelectionModel(
selectedId: previousAdDetails!.vehicle!.category!.id!, selectedOption: isArabic ? previousAdDetails!.vehicle!.category!.labelN.toString() : previousAdDetails!.vehicle!.category!.label ?? ""));
updateSelectionVehicleMileageId(SelectionModel(
selectedId: previousAdDetails!.vehicle!.mileage!.id!,
selectedOption: "${previousAdDetails!.vehicle!.mileage!.mileageStart} - ${previousAdDetails!.vehicle!.mileage!.mileageEnd}",
));
updateSelectionVehicleTransmissionId(SelectionModel(
selectedId: previousAdDetails!.vehicle!.transmission!.id!,
selectedOption: isArabic ? previousAdDetails!.vehicle!.transmission!.labelN.toString() : previousAdDetails!.vehicle!.transmission!.label ?? ""));
updateSelectionVehicleSellerTypeId(SelectionModel(
selectedId: previousAdDetails!.vehicle!.sellertype!.id!,
selectedOption: isArabic ? previousAdDetails!.vehicle!.sellertype!.labelN.toString() : previousAdDetails!.vehicle!.sellertype!.label ?? ""));
// Confirmed from a real draft API response: an unset nested field comes back as a
// non-null placeholder object ({"id": 0, "label": null, ...}), not an absent/null
// field -- same "0 means not set" convention the demandAmount/odometer/warantyYears
// fields below already account for. Blindly trusting .id! here (without this check)
// set selectedId to 0 instead of -1, which silently passed the `selectedId == -1`
// required-field check on Next even though nothing was selected. Always assigning
// (real value OR an explicit empty SelectionModel) rather than conditionally skipping
// also avoids leaking a stale selection left over from a previous ad-editing session.
updateSelectionVehicleModelYearId((previousAdDetails!.vehicle!.modelyear?.id ?? 0) > 0
? SelectionModel(selectedId: previousAdDetails!.vehicle!.modelyear!.id!, selectedOption: previousAdDetails!.vehicle!.modelyear!.label ?? "")
: SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""));
updateSelectionVehicleColorId((previousAdDetails!.vehicle!.color?.id ?? 0) > 0
? SelectionModel(
selectedId: previousAdDetails!.vehicle!.color!.id!, selectedOption: isArabic ? previousAdDetails!.vehicle!.color!.labelN.toString() : previousAdDetails!.vehicle!.color!.label ?? "")
: SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""));
updateSelectionVehicleConditionId((previousAdDetails!.vehicle!.condition?.id ?? 0) > 0
? SelectionModel(
selectedId: previousAdDetails!.vehicle!.condition!.id!,
selectedOption: isArabic ? previousAdDetails!.vehicle!.condition!.labelN.toString() : previousAdDetails!.vehicle!.condition!.label ?? "")
: SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""));
updateSelectionVehicleCategoryId((previousAdDetails!.vehicle!.category?.id ?? 0) > 0
? SelectionModel(
selectedId: previousAdDetails!.vehicle!.category!.id!,
selectedOption: isArabic ? previousAdDetails!.vehicle!.category!.labelN.toString() : previousAdDetails!.vehicle!.category!.label ?? "")
: SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""));
updateSelectionVehicleMileageId((previousAdDetails!.vehicle!.mileage?.id ?? 0) > 0
? SelectionModel(
selectedId: previousAdDetails!.vehicle!.mileage!.id!,
selectedOption: "${previousAdDetails!.vehicle!.mileage!.mileageStart} - ${previousAdDetails!.vehicle!.mileage!.mileageEnd}",
)
: SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""));
updateSelectionVehicleTransmissionId((previousAdDetails!.vehicle!.transmission?.id ?? 0) > 0
? SelectionModel(
selectedId: previousAdDetails!.vehicle!.transmission!.id!,
selectedOption: isArabic ? previousAdDetails!.vehicle!.transmission!.labelN.toString() : previousAdDetails!.vehicle!.transmission!.label ?? "")
: SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""));
updateSelectionVehicleSellerTypeId((previousAdDetails!.vehicle!.sellertype?.id ?? 0) > 0
? SelectionModel(
selectedId: previousAdDetails!.vehicle!.sellertype!.id!,
selectedOption: isArabic ? previousAdDetails!.vehicle!.sellertype!.labelN.toString() : previousAdDetails!.vehicle!.sellertype!.label ?? "")
: SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""));
int indexCountry = vehicleCountries.indexWhere((element) => element.id == previousAdDetails!.vehicle!.countryID);
if (indexCountry != -1) {
updateSelectionVehicleCountryId(SelectionModel(selectedId: vehicleCountries[indexCountry].id!, selectedOption: vehicleCountries[indexCountry].countryName ?? ""));
}
updateSelectionVehicleCityId(SelectionModel(selectedId: previousAdDetails!.vehicle!.cityID!, selectedOption: previousAdDetails!.vehicle!.cityName ?? ""));
vehicleDemandAmount = (previousAdDetails!.vehicle!.demandAmount == null || previousAdDetails!.vehicle!.demandAmount == 0)
? ""
: previousAdDetails!.vehicle!.demandAmount!.toInt().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 == 0)
? ""
: previousAdDetails!.vehicle!.odometer.toString();
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;
@ -3098,9 +3133,7 @@ class AdVM extends BaseVM {
adPhoneNumber = previousAdDetails!.adOwnerDetails!.mobileNo ?? "";
isNumberOnWhatsApp = previousAdDetails!.whatsAppNo != null;
warrantyDuration = (previousAdDetails!.warrantyYears == null || previousAdDetails!.warrantyYears == "0")
? ""
: previousAdDetails!.warrantyYears!;
warrantyDuration = (previousAdDetails!.warrantyYears == null || previousAdDetails!.warrantyYears == "0") ? "" : previousAdDetails!.warrantyYears!;
notifyListeners();
}

@ -67,6 +67,34 @@ class AppointmentsVM extends BaseVM {
List<FilterListModel> appointmentsFilterOptions = [];
List<AppointmentListModel> myFilteredAppointmentsForProvider = [];
// AppointmentsVM is a single long-lived Provider instance for the app's whole lifetime,
// not recreated per login -- confirmed on a real device that logging out and back in as
// a different user left the previous user's selectedBranchIdForAppointments (and search
// filter history) in place, silently scoping the new user's appointments/home carousel
// to a branch that belongs to someone else's account. Call this from logout.
void resetForNewSession() {
selectedAppointmentIndex = 0;
selectedAppointmentSubIndex = 0;
selectedAppointmentId = 0;
selectedBranchIdForAppointments = 0;
myAppointments = [];
myUpComingAppointments = [];
myFilteredAppointmentsForCustomers = [];
myFilteredAppointmentsForProvider = [];
branchFilterProviderSearchHistory = [];
branchFilterCategorySearchHistory = [];
branchFilterServicesSearchHistory = [];
appointmentFilterProviderSearchHistory = [];
appointmentFilterBranchSearchHistory = [];
appointmentFilterCategorySearchHistory = [];
appointmentFilterServicesSearchHistory = [];
appointmentFilterItemsSearchHistory = [];
appointmentFilterCustomerNameSearchHistory = [];
appointmentFilterMobilePhoneSearchHistory = [];
appointmentFilterServiceDeliverySearchHistory = [];
appointmentFiltersCounter = 0;
}
bool isFetchingServices = false;
List<DropValue> branchCategories = [];

@ -159,6 +159,11 @@ class DashboardVMProvider extends BaseVM with WidgetsBindingObserver {
requestsVM.getRequests(),
subscriptionsVM.getSubscriptionBySP(AppState().getUser.data?.userInfo?.providerId.toString() ?? "", true),
appointmentVM.applyFilterOnAppointmentsVMForProviders(
// Explicit 0 (all branches) rather than relying on the branchID default --
// AppointmentsVM is a single long-lived instance, so if the user previously
// viewed a specific branch's appointments, selectedBranchIdForAppointments would
// still be set to that branch and silently scope the home carousel to it.
branchID: 0,
appointmentStatusEnum: AppointmentStatusEnum.allAppointments,
isNeedCustomerFilter: true,
shouldPopulateUpcoming: true,

@ -16,6 +16,7 @@ import 'package:mc_common_app/main.dart';
import 'package:mc_common_app/models/general_models/generic_resp_model.dart';
import 'package:mc_common_app/models/general_models/generic_resp_model.dart';
import 'package:mc_common_app/models/subscriptions_models/subscription_model.dart';
import 'package:mc_common_app/view_models/appointments_view_model.dart';
import 'package:mc_common_app/models/user_models/basic_otp.dart';
import 'package:mc_common_app/models/user_models/change_email.dart';
import 'package:mc_common_app/models/user_models/change_mobile.dart';
@ -763,6 +764,10 @@ class UserVM extends BaseVM {
SharedPrefManager.setPhoneOrEmail("");
SharedPrefManager.setUserPassword("");
context.read<ChatVM>().closeHubConnection();
// AppointmentsVM is a single long-lived instance, not recreated per login -- without
// this, the next user to log in on this device would inherit the previous user's
// selected branch filter and cached appointment lists.
context.read<AppointmentsVM>().resetForNewSession();
if (AppState().getUser.data!.userInfo!.userLocalImage != null) {
AppState().getUser.data!.userInfo!.userLocalImage = null;
AppState().getUser.data!.userInfo!.userImageUrl = null;

@ -245,13 +245,16 @@ class _AdCardState extends State<AdCard> {
),
),
if (widget.adDetails.adPostStatus == AdPostStatus.sold) ...[
// Blur overlay
// Blur overlay -- sized to match the 80x80 image box above (not
// 100x100): Stack sizes itself to its largest non-positioned child, so
// a bigger overlay here was silently growing the whole image area only
// for sold ads.
ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
child: Container(
width: 100,
height: 100,
width: 80,
height: 80,
color: Colors.black.withOpacity(0.3),
),
),

@ -196,6 +196,14 @@ class AdsFragment extends StatelessWidget {
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
// AdVM is a long-lived instance, not recreated per ad -- without this, a
// brand new ad could start showing validation error messages and/or field
// selections left over from a previous draft/cancelled ad in the same
// session. previousAdDetails = null makes sure this is treated as a genuinely
// new ad, not an edit (isCreateNew checks in saveAdAsDraft rely on this).
adVM.resetValues();
adVM.previousAdDetails = null;
// ✅ Await API calls with loading state
await adVM.getVehicleAdsDuration();

@ -79,7 +79,7 @@ class ShowFillButton extends StatelessWidget {
: null),
margin: EdgeInsets.symmetric(horizontal: horizontalMargin, vertical: verticalMargin),
child: MaterialButton(
onPressed: (isLoading || onPressed == null) ? null : onPressed,
onPressed: (isLoading || isDisabled || onPressed == null) ? null : onPressed,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(radius),
side: isFilled ? BorderSide.none : BorderSide(width: 2, color: borderColor),

Loading…
Cancel
Save