diff --git a/VIEWONLY_CUSTOMERID_CHANGES.md b/VIEWONLY_CUSTOMERID_CHANGES.md deleted file mode 100644 index 9551f0d..0000000 --- a/VIEWONLY_CUSTOMERID_CHANGES.md +++ /dev/null @@ -1,63 +0,0 @@ -# View-Only Mode: CustomerID Parameter Exclusion - -## Summary -Updated all API calls to exclude `customerID` parameter when `AppState().getIsViewOnly` is `true`. - -## Files Modified - -### 1. **request_repo.dart** -- ✅ `createRequest()` - Excludes customerID in view-only mode -- ✅ `getRequests()` - Excludes customerID for customer app type in view-only mode -- ✅ `getRequestBasedOnFilters()` - Excludes customerID for customer app type in view-only mode -- ✅ `getOffersFromProvidersByRequest()` - Excludes customerID in view-only mode - -### 2. **ads_repo.dart** -- ✅ `cancelMyAdReservation()` - Excludes customerID in view-only mode -- ✅ `createReserveAd()` - Excludes customerID in view-only mode -- ✅ `uploadBankReceiptsOnReserveDealDone()` - Excludes customerID in view-only mode - -### 3. **appointment_repo.dart** -- ✅ `createAppointmentsMulti()` - Excludes customerID in view-only mode -- ✅ `getMyAppointmentsForCustomersByFilters()` - Excludes customerID in view-only mode - -### 4. **branch_repo.dart** -- ✅ `submitBranchRatings()` - Excludes customerID in view-only mode -- ✅ `addProviderToFavourite()` - Excludes customerID in view-only mode -- ✅ `getMyFavoriteProviders()` - Excludes customerID in view-only mode - -### 5. **setting_options_repo.dart** -- ✅ `appInvitationCreate()` - Excludes customerID for customer app type in view-only mode - -## How It Works - -When `AppState().getIsViewOnly` is `true`, the application is in demonstration/view-only mode. In this mode: - -1. **API parameters are conditionally added**: Instead of always including `customerID`, we check the view-only flag first -2. **Pattern used**: - ```dart - var params = { - "otherParam": value, - }; - - if (!appState.getIsViewOnly) { - params["customerID"] = customerId; - } - ``` - -3. **App types considered**: The changes properly handle both customer and provider app types - -## Testing Recommendations - -1. Test in normal mode (getIsViewOnly = false): - - All APIs should include customerID as before - - All existing functionality should work - -2. Test in view-only mode (getIsViewOnly = true): - - APIs should NOT include customerID parameter - - Read-only operations should work - - Write operations should be blocked by view-only mode - -## Note - -The `createComplainFromProvider()` method in `common_repo.dart` was NOT modified because the `customerID` in that API represents the target customer being complained about, not the authenticated user making the request. - diff --git a/lib/api/api_client.dart b/lib/api/api_client.dart index 333d3d1..6a3083d 100644 --- a/lib/api/api_client.dart +++ b/lib/api/api_client.dart @@ -249,7 +249,7 @@ class ApiClientImp implements ApiClient { logger.i(url); logger.i("$queryParameters"); } - var response = await _get(Uri.parse(url), headers: headers0).timeout(const Duration(seconds: 60)); + var response = await _get(Uri.parse(url), headers: headers0).timeout(const Duration(seconds: 120)); if (!kReleaseMode) { logger.i(jsonDecode(response.body)); diff --git a/lib/repositories/ads_repo.dart b/lib/repositories/ads_repo.dart index c0a3716..e4455a5 100644 --- a/lib/repositories/ads_repo.dart +++ b/lib/repositories/ads_repo.dart @@ -560,7 +560,8 @@ class AdsRepoImp implements AdsRepo { String token = appState.getUser.data!.accessToken ?? ""; GenericRespModel adsGenericModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), - ApiConsts.adsExtendDurationCreate, + ApiConsts. + adsExtendDurationCreate, postParams, token: token, ); diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index 2467ca7..5e8e536 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -27,12 +27,15 @@ import 'package:mc_common_app/models/general_models/widgets_models.dart'; import 'package:mc_common_app/repositories/ads_repo.dart'; import 'package:mc_common_app/repositories/common_repo.dart'; import 'package:mc_common_app/services/common_services.dart'; +import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/utils/date_helper.dart'; +import 'package:mc_common_app/utils/dialogs_and_bottomsheets.dart'; import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/utils/utils.dart'; import 'package:mc_common_app/view_models/base_view_model.dart'; import 'package:mc_common_app/view_models/chat_view_model.dart'; +import 'package:mc_common_app/widgets/button/show_fill_button.dart'; import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; import 'package:provider/provider.dart'; @@ -1243,26 +1246,153 @@ class AdVM extends BaseVM { void onBackButtonPressed(BuildContext context) { switch (currentProgressStep) { case AdCreationStepsEnum.vehicleDetails: - isAdEditEnabled = false; - isDraftEditEnabled = false; - resetValues(); - pop(context); + // Check if user has entered any data + bool hasData = vehicleTypeId.selectedId != -1 || + vehicleBrandId.selectedId != -1 || + vehicleModelId.selectedId != -1 || + vehicleModelYearId.selectedId != -1 || + vehicleColorId.selectedId != -1 || + vehicleCategoryId.selectedId != -1 || + vehicleConditionId.selectedId != -1 || + vehicleMileageId.selectedId != -1 || + vehicleTransmissionId.selectedId != -1 || + vehicleCountryId.selectedId != -1 || + vehicleCityId.selectedId != -1 || + vehicleDemandAmount.isNotEmpty || + vehicleVin.isNotEmpty || + vehicleTitle.isNotEmpty || + warrantyDuration.isNotEmpty || + vehicleDescription.isNotEmpty || + pickedPostingImages.isNotEmpty; + + if (hasData) { + // Show confirmation dialog if user has entered data + showSaveAsDraftConfirmationDialog( + context, + onYes: () async { + Navigator.pop(context); + await saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.vehicleDetails, bypassValidation: true); + currentProgressStep = AdCreationStepsEnum.vehicleDetails; + isAdEditEnabled = false; + isDraftEditEnabled = false; + resetValues(); + pop(context); + if (AppState().currentAppType == AppType.customer) { + pop(context); + } + }, + onNo: () { + Navigator.pop(context); + isAdEditEnabled = false; + isDraftEditEnabled = false; + resetValues(); + pop(context); + }, + ); + } else { + // No data entered, go back directly + isAdEditEnabled = false; + isDraftEditEnabled = false; + resetValues(); + pop(context); + } break; case AdCreationStepsEnum.damageParts: - currentProgressStep = AdCreationStepsEnum.vehicleDetails; - notifyListeners(); + showSaveAsDraftConfirmationDialog( + context, + onYes: () async { + Navigator.pop(context); + await saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.damageParts, bypassValidation: true); + currentProgressStep = AdCreationStepsEnum.vehicleDetails; + isAdEditEnabled = false; + isDraftEditEnabled = false; + resetValues(); + pop(context); + if (AppState().currentAppType == AppType.customer) { + pop(context); + } + }, + onNo: () { + Navigator.pop(context); + currentProgressStep = AdCreationStepsEnum.vehicleDetails; + notifyListeners(); + }, + ); break; case AdCreationStepsEnum.adDuration: - currentProgressStep = AdCreationStepsEnum.damageParts; - notifyListeners(); + showSaveAsDraftConfirmationDialog( + context, + onYes: () async { + Navigator.pop(context); + await saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.adDuration, bypassValidation: true); + currentProgressStep = AdCreationStepsEnum.vehicleDetails; + isAdEditEnabled = false; + isDraftEditEnabled = false; + resetValues(); + pop(context); + if (AppState().currentAppType == AppType.customer) { + pop(context); + } + }, + onNo: () { + Navigator.pop(context); + currentProgressStep = AdCreationStepsEnum.damageParts; + notifyListeners(); + }, + ); break; case AdCreationStepsEnum.reviewAd: - currentProgressStep = AdCreationStepsEnum.adDuration; - notifyListeners(); + showSaveAsDraftConfirmationDialog( + context, + onYes: () async { + Navigator.pop(context); + await saveAdAsDraft(context: context, stepNoEnum: AdCreationStepsEnum.reviewAd, bypassValidation: true); + currentProgressStep = AdCreationStepsEnum.vehicleDetails; + isAdEditEnabled = false; + isDraftEditEnabled = false; + resetValues(); + pop(context); + if (AppState().currentAppType == AppType.customer) { + pop(context); + } + }, + onNo: () { + Navigator.pop(context); + currentProgressStep = AdCreationStepsEnum.adDuration; + notifyListeners(); + }, + ); break; } } + void showSaveAsDraftConfirmationDialog(BuildContext context, {required VoidCallback onYes, required VoidCallback onNo}) { + actionConfirmationBottomSheet( + context: context, + title: "Save As Draft".toText(fontSize: 28, isBold: true, letterSpacing: -1.44), + subtitle: "Your ad will be saved as draft and you can continue later from where you left off. Do you want to save and go back?", + actionButtonYes: Expanded( + child: ShowFillButton( + maxHeight: 55, + title: LocaleKeys.yes.tr(), + fontSize: 15, + onPressed: onYes, + ), + ), + actionButtonNo: Expanded( + child: ShowFillButton( + maxHeight: 55, + isFilled: false, + borderColor: MyColors.darkPrimaryColor, + title: LocaleKeys.no.tr(), + txtColor: MyColors.darkPrimaryColor, + fontSize: 15, + onPressed: onNo, + ), + ), + ); + } + Future saveAdVehicleDetailsDraft({required bool isNew, required BuildContext context, required AdCreationStepsEnum stepNoEnum}) async { AppState appState = injector.get(); @@ -1354,6 +1484,99 @@ class AdVM extends BaseVM { } } + Future saveAdAsDraft({required BuildContext context, required AdCreationStepsEnum stepNoEnum, bool bypassValidation = false}) async { + AppState appState = injector.get(); + + List adsSelectedServices = []; + + for (var value in specialServiceCards) { + adsSelectedServices.add(value.serviceSelectedId!.selectedId); + } + + Utils.showLoading(context); + try { + Ads ads = Ads( + id: (previousAdDetails != null && previousAdDetails!.id != null) ? previousAdDetails!.id : 0, + adsDurationID: vehicleAdDurationId.selectedId == -1 ? 0 : vehicleAdDurationId.selectedId, + startDate: selectionDurationStartDate.isNotEmpty ? selectionDurationStartDate : null, + countryId: vehicleCountryId.selectedId != -1 ? vehicleCountryId.selectedId : null, + specialServiceIDs: adsSelectedServices, + showContactDetail: isPhoneNumberShown, + isOnWhatsApp: isNumberOnWhatsApp, + ); + List vehicleImages = []; + + for (var image in pickedPostingImages) { + vehicleImages.add(await convertFileToVehiclePostingImages(imageModel: image)); + } + + List vehicleDamageImages = []; + + for (var card in vehicleDamageCards) { + if (card.partImages != null && card.partImages!.isNotEmpty) { + for (var image in card.partImages!) { + VehiclePostingDamageParts stringImage = await convertFileToVehiclePostingDamageParts( + imageModel: image, + damagePartId: card.partSelectedId!.selectedId, + commentParam: card.damagePartDescription ?? "", + ); + + vehicleDamageImages.add(stringImage); + } + } + } + + VehiclePosting vehiclePosting = VehiclePosting( + id: (previousAdDetails != null && previousAdDetails!.vehiclePostingID != null) ? previousAdDetails!.vehiclePostingID : null, + userID: appState.getUser.data!.userInfo!.userId, + vehicleType: vehicleTypeId.selectedId != -1 ? vehicleTypeId.selectedId : null, + vehicleModelID: vehicleModelId.selectedId != -1 ? vehicleModelId.selectedId : null, + vehicleModelYearID: vehicleModelYearId.selectedId != -1 ? vehicleModelYearId.selectedId : null, + vehicleColorID: vehicleColorId.selectedId != -1 ? vehicleColorId.selectedId : null, + vehicleCategoryID: vehicleCategoryId.selectedId != -1 ? vehicleCategoryId.selectedId : null, + vehicleConditionID: vehicleConditionId.selectedId != -1 ? vehicleConditionId.selectedId : null, + vehicleMileageID: vehicleMileageId.selectedId != -1 ? vehicleMileageId.selectedId : null, + vehicleTransmissionID: vehicleTransmissionId.selectedId != -1 ? vehicleTransmissionId.selectedId : null, + vehicleSellerTypeID: vehicleSellerTypeId.selectedId == -1 ? 1 : vehicleSellerTypeId.selectedId, + cityID: vehicleCityId.selectedId != -1 ? vehicleCityId.selectedId : null, + price: vehicleDemandAmount.isNotEmpty ? int.tryParse(vehicleDemandAmount) : null, + vehicleVIN: vehicleVin.isNotEmpty ? vehicleVin : null, + vehicleDescription: vehicleDescription.isNotEmpty ? vehicleDescription : null, + vehicleTitle: vehicleTitle.isNotEmpty ? vehicleTitle : null, + vehicleDescriptionN: vehicleDescription.isNotEmpty ? vehicleDescription : null, + isFinanceAvailable: financeAvailableStatus, + warantyYears: warrantyDuration.isNotEmpty ? int.tryParse(warrantyDuration) : null, + demandAmount: vehicleDemandAmount.isNotEmpty ? int.tryParse(vehicleDemandAmount) : null, + vehiclePostingImages: vehicleImages, + vehiclePostingDamageParts: vehicleDamageImages, + phoneNo: (isPhoneNumberShown && adPhoneNumber.isNotEmpty) ? adPhoneNumberDialCode + adPhoneNumber : null, + whatsAppNo: (isPhoneNumberShown && isNumberOnWhatsApp && adPhoneNumber.isNotEmpty) ? adPhoneNumberDialCode + adPhoneNumber : null, + odometer: odometer.isNotEmpty ? int.tryParse(odometer) : 0, + isInUsed: isInUsed, + isAccidentFree: isAccidentFree, + ); + + AdsCreationPayloadModel adsCreationPayloadModel = AdsCreationPayloadModel(ads: ads, vehiclePosting: vehiclePosting); + + GenericRespModel respModel = await adsRepo.createOrUpdateDraftAd( + adsCreationPayloadModel: adsCreationPayloadModel, + isCreateNew: previousAdDetails == null || previousAdDetails!.id == null, + stepNo: stepNoEnum, + ); + Utils.hideLoading(context); + if (respModel.messageStatus == 1) { + Utils.showToast('Saved as Draft'); + getMyDraftAds(); + } else { + Utils.showToast(respModel.message ?? "Failed to save draft"); + } + } catch (e) { + Utils.hideLoading(context); + logger.e(e.toString()); + Utils.showToast("Error saving draft: ${e.toString()}"); + } + } + Future saveAdDamagePartDetailsDraft() async {} Future saveAdDurationsDraft() async {} diff --git a/lib/views/advertisement/create_ad_view.dart b/lib/views/advertisement/create_ad_view.dart index fca12ae..f06e88a 100644 --- a/lib/views/advertisement/create_ad_view.dart +++ b/lib/views/advertisement/create_ad_view.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; @@ -7,7 +5,6 @@ import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/utils/enums.dart'; 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/ad_creation_steps/ad_duration_container.dart'; import 'package:mc_common_app/views/advertisement/ad_creation_steps/ad_review_containers.dart'; @@ -80,14 +77,39 @@ class BuildFooterButton extends StatelessWidget { builder: (BuildContext context, AdVM adVm, Widget? child) { switch (adVm.currentProgressStep) { case AdCreationStepsEnum.vehicleDetails: - return SizedBox( - width: double.infinity, - child: ShowFillButton( - title: LocaleKeys.next.tr(), - onPressed: () { - adVm.updateCurrentStep(context); - }, - ), + return Row( + children: [ + Expanded( + child: ShowFillButton( + txtColor: MyColors.black, + maxHeight: 55, + title: "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) { + pop(context); + } + }, + backgroundColor: MyColors.greyButtonColor, + ), + ), + 12.width, + Expanded( + child: ShowFillButton( + maxHeight: 55, + title: LocaleKeys.next.tr(), + onPressed: () { + adVm.updateCurrentStep(context); + }, + backgroundColor: MyColors.darkPrimaryColor, + ), + ), + ], ); case AdCreationStepsEnum.damageParts: return Row( @@ -96,16 +118,17 @@ class BuildFooterButton extends StatelessWidget { child: ShowFillButton( txtColor: MyColors.black, maxHeight: 55, - title: LocaleKeys.cancel.tr(), - onPressed: () { + title: "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) { pop(context); } - Utils.showToast('Saved as Draft'); - adVm.getMyDraftAds(); }, backgroundColor: MyColors.greyButtonColor, ), @@ -130,17 +153,17 @@ class BuildFooterButton extends StatelessWidget { child: ShowFillButton( txtColor: MyColors.black, maxHeight: 55, - title: LocaleKeys.cancel.tr(), - onPressed: () { + title: "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) { pop(context); } - Utils.showToast('Saved as Draft'); - adVm.getMyDraftAds(); }, backgroundColor: MyColors.greyButtonColor, ), @@ -165,17 +188,17 @@ class BuildFooterButton extends StatelessWidget { child: ShowFillButton( txtColor: MyColors.black, maxHeight: 55, - title: LocaleKeys.cancel.tr(), - onPressed: () { + title: "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) { pop(context); } - Utils.showToast('Saved as Draft'); - adVm.getMyDraftAds(); }, backgroundColor: MyColors.greyButtonColor, ), diff --git a/lib/views/advertisement/select_ad_type_view.dart b/lib/views/advertisement/select_ad_type_view.dart index 1597368..06b5602 100644 --- a/lib/views/advertisement/select_ad_type_view.dart +++ b/lib/views/advertisement/select_ad_type_view.dart @@ -78,8 +78,7 @@ class SelectAdTypeView extends StatelessWidget { onTap: () async { adVM.updateSelectionVehicleTypeId(SelectionModel(selectedId: vehicleTypeModel.id!, selectedOption: vehicleTypeModel.vehicleTypeName ?? "", errorValue: "")); if (adVM.isAdEditEnabled) { - adVM. - autoFillSelectedVehicleAdsDetails(); + adVM.autoFillSelectedVehicleAdsDetails(); } if (AppState().currentAppType == AppType.provider) { if (isFromExtendAd && !adVM.isAdEditEnabled) {