From b6e6a14bf79bd9674fc36d45ee03bf833e8b205d Mon Sep 17 00:00:00 2001 From: FaizHashmi Date: Sun, 6 Sep 2026 09:25:29 +0300 Subject: [PATCH] Resolved Bugs --- assets/langs/ar-SA.json | 1 + assets/langs/en-US.json | 1 + lib/classes/consts.dart | 1 + lib/config/routes.dart | 2 +- lib/extensions/string_extensions.dart | 2 + lib/generated/codegen_loader.g.dart | 8 +- lib/generated/locale_keys.g.dart | 5 +- .../advertisment_models/ad_details_model.dart | 9 +- lib/models/user_models/user.dart | 2 +- lib/repositories/ads_repo.dart | 6 +- lib/utils/enums.dart | 1 + lib/utils/utils.dart | 2 +- lib/view_models/ad_view_model.dart | 109 +++++-- .../dashboard_view_model_provider.dart | 4 +- lib/view_models/requests_view_model.dart | 19 +- .../vehicle_details_container.dart | 62 +++- .../ads_detail_view/ads_detail_view.dart | 34 +- .../ads_detail_view/components.dart | 66 ++-- lib/views/advertisement/ads_filter_view.dart | 56 +++- .../components/ads_list_widget.dart | 13 +- lib/views/advertisement/create_ad_view.dart | 164 +++++----- .../advertisement/select_ad_type_view.dart | 6 + .../book_appointment_schedules_view.dart | 253 ++++++++++----- .../book_appointments_item_view.dart | 1 - .../widgets/custom_calender_widget.dart | 74 +++-- .../common_fragments/requests_fragment.dart | 7 +- lib/views/requests/request_detail_page.dart | 31 +- lib/views/user/confirm_new_password.dart | 123 +++---- lib/views/user/confirm_new_password_page.dart | 77 ++--- lib/views/user/forget_password_page.dart | 167 +++++----- lib/views/user/vertify_password_page.dart | 304 +++++++++--------- .../common_widgets/bottom_nav_bar.dart | 2 +- lib/widgets/dropdown/dropdow_field.dart | 13 +- lib/widgets/shimmer/home_shimmer_widgets.dart | 45 +++ lib/widgets/txt_field.dart | 95 ++++-- pubspec.yaml | 3 + 36 files changed, 1097 insertions(+), 671 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 4859c6a..6c78f09 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -481,6 +481,7 @@ "completeDeal": "إتمام الصفقة", "uploadBankReceipt": "تحميل إيصال البنك", "notesForAdmin": "ملاحظات للإدارة", + "note": "ملاحظة:", "extendAd": "تمديد الإعلان", "updateAdDetails": "هل تريد تحديث تفاصيل الإعلان", "durationExtendingAd": "يمكنك تغيير مدة الإعلان والتفاصيل قبل تمديد الإعلان", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 4a178f5..a55fec8 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -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", diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 01f0a36..a74e57c 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -286,6 +286,7 @@ class GlobalConsts { static int providerDealerRoleTypeId = 6; static int providerIndividualRoleTypeId = 5; + static int dealershipUserRoleTypeId = 7; } class MyAssets { diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 56307b8..3f1c53c 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -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(), diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index eb992d5..e811f37 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -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: diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index dc1691b..dd0545c 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -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 _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 _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 _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 _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", diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 3aedaa9..25bf87d 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -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'; diff --git a/lib/models/advertisment_models/ad_details_model.dart b/lib/models/advertisment_models/ad_details_model.dart index 2f43ffa..e169d3e 100644 --- a/lib/models/advertisment_models/ad_details_model.dart +++ b/lib/models/advertisment_models/ad_details_model.dart @@ -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']; diff --git a/lib/models/user_models/user.dart b/lib/models/user_models/user.dart index cbdd4c9..1963a10 100644 --- a/lib/models/user_models/user.dart +++ b/lib/models/user_models/user.dart @@ -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; } diff --git a/lib/repositories/ads_repo.dart b/lib/repositories/ads_repo.dart index 680fbd7..39d9f31 100644 --- a/lib/repositories/ads_repo.dart +++ b/lib/repositories/ads_repo.dart @@ -141,7 +141,8 @@ class AdsRepoImp implements AdsRepo { Future 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 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 toggleAdFavorite({required int adsID, required bool isFavorite}) async { var params = { - "adsID": adsID.toString(), + "ID": adsID.toString(), }; String endpoint = isFavorite ? ApiConsts.unFavouriteAds : ApiConsts.favouriteAdsCreate; diff --git a/lib/utils/enums.dart b/lib/utils/enums.dart index b566d73..c6feccb 100644 --- a/lib/utils/enums.dart +++ b/lib/utils/enums.dart @@ -51,6 +51,7 @@ enum CreatedByRoleEnum { customer, provider, admin, allAds } enum UserType { providerDealer, providerIndividual, + dealershipUser, customer, } diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index de2a614..d4578cc 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -42,7 +42,7 @@ class Utils { overflow: TextOverflow.ellipsis, ), autoCloseDuration: const Duration(seconds: 3), - alignment: Alignment.bottomCenter, + alignment: Alignment.topCenter, showProgressBar: false, ); } diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index 5ba14cb..3542a72 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -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> 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(); } diff --git a/lib/view_models/dashboard_view_model_provider.dart b/lib/view_models/dashboard_view_model_provider.dart index f822b13..e52b974 100644 --- a/lib/view_models/dashboard_view_model_provider.dart +++ b/lib/view_models/dashboard_view_model_provider.dart @@ -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; } diff --git a/lib/view_models/requests_view_model.dart b/lib/view_models/requests_view_model.dart index 8446178..d06add2 100644 --- a/lib/view_models/requests_view_model.dart +++ b/lib/view_models/requests_view_model.dart @@ -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 updateOfferFromProvider({ required String message, required int requestId, diff --git a/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart b/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart index 39d0be5..6492f02 100644 --- a/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart +++ b/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart @@ -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( diff --git a/lib/views/advertisement/ads_detail_view/ads_detail_view.dart b/lib/views/advertisement/ads_detail_view/ads_detail_view.dart index 2b74524..0fe45b6 100644 --- a/lib/views/advertisement/ads_detail_view/ads_detail_view.dart +++ b/lib/views/advertisement/ads_detail_view/ads_detail_view.dart @@ -133,7 +133,7 @@ class _AdsDetailViewState extends State { "${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 { "${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 { 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 { 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 { // 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: [ diff --git a/lib/views/advertisement/ads_detail_view/components.dart b/lib/views/advertisement/ads_detail_view/components.dart index d609433..cfa86d0 100644 --- a/lib/views/advertisement/ads_detail_view/components.dart +++ b/lib/views/advertisement/ads_detail_view/components.dart @@ -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 vehicleCitiesDrop = []; + Set 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(); if (adVM.state == ViewState.busy) { return const CircularProgressIndicator(); diff --git a/lib/views/advertisement/ads_filter_view.dart b/lib/views/advertisement/ads_filter_view.dart index 05ec994..b11171f 100644 --- a/lib/views/advertisement/ads_filter_view.dart +++ b/lib/views/advertisement/ads_filter_view.dart @@ -29,6 +29,8 @@ class AdsFilterView extends StatefulWidget { class _AdsFilterViewState extends State { late AdVM adVM; + final FocusNode _adIdFocusNode = FocusNode(); + final FocusNode _mobileNumberFocusNode = FocusNode(); @override void initState() { @@ -36,12 +38,25 @@ class _AdsFilterViewState extends State { 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 { 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 { 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 { if (formattedDate.isEmpty) { return; } - adVM.updateSelectionDurationStartDate(formattedDate); DropValue value = DropValue(0, formattedDate, ""); adVM.addToVehicleAdCreatedDateSearchHistory(value: value); }, @@ -317,14 +344,27 @@ class _AdsFilterViewState extends State { 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), + ), ); }, ), diff --git a/lib/views/advertisement/components/ads_list_widget.dart b/lib/views/advertisement/components/ads_list_widget.dart index 86a902f..de125af 100644 --- a/lib/views/advertisement/components/ads_list_widget.dart +++ b/lib/views/advertisement/components/ads_list_widget.dart @@ -285,15 +285,16 @@ class _AdCardState extends State { 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().isReservedFilterSelected) ...[ Utils.statusContainerChip( @@ -342,7 +343,7 @@ class _AdCardState extends State { ), 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 { 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(), ], ), ], diff --git a/lib/views/advertisement/create_ad_view.dart b/lib/views/advertisement/create_ad_view.dart index 38e3168..df658ac 100644 --- a/lib/views/advertisement/create_ad_view.dart +++ b/lib/views/advertisement/create_ad_view.dart @@ -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, diff --git a/lib/views/advertisement/select_ad_type_view.dart b/lib/views/advertisement/select_ad_type_view.dart index 65a7694..2d68dcd 100644 --- a/lib/views/advertisement/select_ad_type_view.dart +++ b/lib/views/advertisement/select_ad_type_view.dart @@ -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); } diff --git a/lib/views/appointments/book_appointment_schedules_view.dart b/lib/views/appointments/book_appointment_schedules_view.dart index a3c8856..a0ba4ad 100644 --- a/lib/views/appointments/book_appointment_schedules_view.dart +++ b/lib/views/appointments/book_appointment_schedules_view.dart @@ -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 timeSlots = appointmentsVM.serviceAppointmentScheduleList.first.selectedCustomTimeDateSlotModel!.availableSlots ?? []; + List 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, ); } diff --git a/lib/views/appointments/book_appointments_item_view.dart b/lib/views/appointments/book_appointments_item_view.dart index 823d8f8..9633bcc 100644 --- a/lib/views/appointments/book_appointments_item_view.dart +++ b/lib/views/appointments/book_appointments_item_view.dart @@ -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().resetCategorySelectionBottomSheet(); Navigator.pop(context); diff --git a/lib/views/appointments/widgets/custom_calender_widget.dart b/lib/views/appointments/widgets/custom_calender_widget.dart index 8eb3435..f9c7ab1 100644 --- a/lib/views/appointments/widgets/custom_calender_widget.dart +++ b/lib/views/appointments/widgets/custom_calender_widget.dart @@ -22,10 +22,12 @@ class CustomCalenderAppointmentWidget extends StatefulWidget { }); @override - State createState() => _CustomCalenderAppointmentWidgetState(); + State createState() => + _CustomCalenderAppointmentWidgetState(); } -class _CustomCalenderAppointmentWidgetState extends State { +class _CustomCalenderAppointmentWidgetState + extends State { List allDates = []; List allMonths = []; List datesInSelectedMonth = []; @@ -47,23 +49,32 @@ class _CustomCalenderAppointmentWidgetState extends State 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 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 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 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 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 isSameDay(selectedDay, element)); + int index = datesInSelectedMonth + .indexWhere((element) => isSameDay(selectedDay, element)); if (index == -1) { return; @@ -220,8 +250,10 @@ class _CustomCalenderAppointmentWidgetState extends State 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, diff --git a/lib/views/requests/request_detail_page.dart b/lib/views/requests/request_detail_page.dart index 1d92815..17fe103 100644 --- a/lib/views/requests/request_detail_page.dart +++ b/lib/views/requests/request_detail_page.dart @@ -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( diff --git a/lib/views/user/confirm_new_password.dart b/lib/views/user/confirm_new_password.dart index b676e45..91fb117 100644 --- a/lib/views/user/confirm_new_password.dart +++ b/lib/views/user/confirm_new_password.dart @@ -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(); - 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(); +// 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, +// ); +// }, +// ), +// ], +// ), +// ), +// ), +// ); +// } +// } diff --git a/lib/views/user/confirm_new_password_page.dart b/lib/views/user/confirm_new_password_page.dart index 0ddf424..8c41cca 100644 --- a/lib/views/user/confirm_new_password_page.dart +++ b/lib/views/user/confirm_new_password_page.dart @@ -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 createState() => _ConfirmNewPasswordPageState(); @@ -21,77 +22,41 @@ class ConfirmNewPasswordPage extends StatefulWidget { class _ConfirmNewPasswordPageState extends State { String newPassword = ""; String confirmPassword = ''; - bool _obscureNewPassword = true; - bool _obscureConfirmPassword = true; @override Widget build(BuildContext context) { UserVM userVM = context.read(); 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, diff --git a/lib/views/user/forget_password_page.dart b/lib/views/user/forget_password_page.dart index cedaf04..2fe7d83 100644 --- a/lib/views/user/forget_password_page.dart +++ b/lib/views/user/forget_password_page.dart @@ -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 { @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 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, diff --git a/lib/views/user/vertify_password_page.dart b/lib/views/user/vertify_password_page.dart index 28a62f3..0868c83 100644 --- a/lib/views/user/vertify_password_page.dart +++ b/lib/views/user/vertify_password_page.dart @@ -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 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 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); +// // }, +// // ), +// // ), +// // ); +// // }, +// // )); +// } diff --git a/lib/widgets/common_widgets/bottom_nav_bar.dart b/lib/widgets/common_widgets/bottom_nav_bar.dart index f009ff4..ebf1871 100644 --- a/lib/widgets/common_widgets/bottom_nav_bar.dart +++ b/lib/widgets/common_widgets/bottom_nav_bar.dart @@ -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), diff --git a/lib/widgets/dropdown/dropdow_field.dart b/lib/widgets/dropdown/dropdow_field.dart index 4a7b0be..6d3ba97 100644 --- a/lib/widgets/dropdown/dropdow_field.dart +++ b/lib/widgets/dropdown/dropdow_field.dart @@ -63,11 +63,14 @@ class _DropdownFieldState extends State { 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, diff --git a/lib/widgets/shimmer/home_shimmer_widgets.dart b/lib/widgets/shimmer/home_shimmer_widgets.dart index ef8b273..ea044ac 100644 --- a/lib/widgets/shimmer/home_shimmer_widgets.dart +++ b/lib/widgets/shimmer/home_shimmer_widgets.dart @@ -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); diff --git a/lib/widgets/txt_field.dart b/lib/widgets/txt_field.dart index 253a4e0..b094583 100644 --- a/lib/widgets/txt_field.dart +++ b/lib/widgets/txt_field.dart @@ -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 { 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 { 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 - ? [FilteringTextInputFormatter.allow(RegExp('[0-9]')), LengthLimitingTextInputFormatter(1)] + inputFormatters: widget.allowOnlyOneDigit && + widget.numbersOnly + ? [ + FilteringTextInputFormatter.allow(RegExp('[0-9]')), + LengthLimitingTextInputFormatter(1) + ] : widget.numbersOnly - ? [FilteringTextInputFormatter.allow(RegExp('[0-9]'))] + ? [ + 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 { : 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, diff --git a/pubspec.yaml b/pubspec.yaml index 3946f8a..a5071ba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 \ No newline at end of file