diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index d4df718..7b4cfb3 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -749,6 +749,12 @@ "offerRejected": "تم رفض العرض.", "offerAccepted": "تم قبول العرض.", "you": "أنتم", + "youCannotDeactivateThisServiceRightNow": "لا يمكنك تعطيل هذه الخدمة الآن لأن لديك مواعيد معلقة لهذه الخدمة. تفاصيل آخر موعد هي:", + "done": "تم", + "notice": "إشعار", + "serviceDeactivated": "تم تعطيل الخدمة", + "totalNumberOfServices": "إجمالي عدد الخدمات:", + "noAvailableOfficesInCity": "لا توجد مكاتب متاحة في مدينتك لخدمتك.", "searchByItem": "البحث حسب العنصر" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 63b1010..2548f81 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -747,5 +747,11 @@ "offerRejected": "Offer has been Rejected.", "offerAccepted": "Offer has been Accepted.", "you": "You", + "youCannotDeactivateThisServiceRightNow": "You cannot deactivate this service right now because you have pending appointments for this service. The last appointment details are:", + "done": "Done", + "notice": "Notice", + "serviceDeactivated": "Service Deactivated", + "totalNumberOfServices": "Total number of services:", + "noAvailableOfficesInCity": "There are no available offices in your city for your service.", "searchByItem": "Search By Item" } \ No newline at end of file diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index ea28136..d5033d6 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -55,8 +55,10 @@ class ApiConsts { static String servicesGet = "${baseUrlServices}api/ServiceProviders/Services_Get"; static String serviceProviderServiceCreate = "${baseUrlServices}api/ServiceProviders/ServiceProviderService_Create"; static String serviceProviderServiceUpdate = "${baseUrlServices}api/ServiceProviders/ServiceProviderService_Update"; + static String serviceProviderServiceStatusUpdate = "${baseUrlServices}api/ServiceProviders/CategoryAndServiceDeactivateBySP"; static String getProviderServices = "${baseUrlServices}api/ServiceProviders/ServiceProviderService_Get"; static String setScheduleInactive = "${baseUrlServices}api/ServiceProviders/BranchAppointmentSchedule_IsActiveUpdate"; + static String serviceProviderAppointmentGetByCategoryOrService = "${baseUrlServices}api/ServiceProviders/ServiceProviderAppointment_GetByCategoryOrService"; static String serviceProviderServiceGet = "${baseUrlServices}api/ServiceProviders/ServiceProviderService_Get"; static String branchesAndServices = "${baseUrlServices}api/ServiceProviders/ServiceProviderDetail_Get"; diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index f3a6bd9..e86e38d 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -765,6 +765,13 @@ class CodegenLoader extends AssetLoader{ "offerRejected": "تم رفض العرض.", "offerAccepted": "تم قبول العرض.", "you": "أنتم", + "youCannotDeactivateThisServiceRightNow": "لا يمكنك تعطيل هذه الخدمة الآن لأن لديك مواعيد معلقة لهذه الخدمة. تفاصيل آخر موعد هي:", + "done": "تم", + "notice": "إشعار", + "serviceDeactivated": "تم تعطيل الخدمة", + "totalNumberOfServices": "إجمالي عدد الخدمات:", + "noAvailableOfficesInCity": "لا توجد مكاتب متاحة في مدينتك لخدمتك." + "you": "أنتم", "searchByItem": "البحث حسب العنصر" }; static const Map en_US = { @@ -1516,6 +1523,13 @@ static const Map en_US = { "offerRejected": "Offer has been Rejected.", "offerAccepted": "Offer has been Accepted.", "you": "You", + "youCannotDeactivateThisServiceRightNow": "You cannot deactivate this service right now because you have pending appointments for this service. The last appointment details are:", + "done": "Done", + "notice": "Notice", + "serviceDeactivated": "Service Deactivated", + "totalNumberOfServices": "Total number of services:", + "noAvailableOfficesInCity": "There are no available offices in your city for your service." + "you": "You", "searchByItem": "Search By Item" }; static const Map> mapLocales = {"ar_SA": ar_SA, "en_US": en_US}; diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 3d4daeb..b668b9e 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -728,6 +728,12 @@ abstract class LocaleKeys { static const offerRejected = 'offerRejected'; static const offerAccepted = 'offerAccepted'; static const you = 'you'; + static const youCannotDeactivateThisServiceRightNow = 'youCannotDeactivateThisServiceRightNow'; + static const done = 'done'; + static const notice = 'notice'; + static const serviceDeactivated = 'serviceDeactivated'; + static const totalNumberOfServices = 'totalNumberOfServices'; + static const noAvailableOfficesInCity = 'noAvailableOfficesInCity'; static const searchByItem = 'searchByItem'; } diff --git a/lib/models/advertisment_models/ss_photo_schedule_model.dart b/lib/models/advertisment_models/ss_photo_schedule_model.dart index 6aa7f54..8852e2b 100644 --- a/lib/models/advertisment_models/ss_photo_schedule_model.dart +++ b/lib/models/advertisment_models/ss_photo_schedule_model.dart @@ -12,7 +12,7 @@ class SSPhotoOfficeScheduleModel { String? areaName; String? latitude; String? longitude; - int? distanceKM; + double? distanceKM; int? totalItemsCount; List? photoOfficeScheduleSlots; List? customTimeDateSlotList; diff --git a/lib/models/appointments_models/appointment_basic_detail_model.dart b/lib/models/appointments_models/appointment_basic_detail_model.dart new file mode 100644 index 0000000..041315d --- /dev/null +++ b/lib/models/appointments_models/appointment_basic_detail_model.dart @@ -0,0 +1,45 @@ +class AppointmentBasicDetailsModel { + int? serviceSlotID; + String? slotDate; + String? startTime; + String? endTime; + int? appointmentStatusID; + String? appointmentStatusText; + int? serviceProviderID; + int? customerID; + + AppointmentBasicDetailsModel({ + this.serviceSlotID, + this.slotDate, + this.startTime, + this.endTime, + this.appointmentStatusID, + this.appointmentStatusText, + this.serviceProviderID, + this.customerID, + }); + + AppointmentBasicDetailsModel.fromJson(Map json) { + serviceSlotID = json['serviceSlotID']; + slotDate = json['slotDate']; + startTime = json['startTime']; + endTime = json['endTime']; + appointmentStatusID = json['appointmentStatusID']; + appointmentStatusText = json['appointmentStatusText']; + serviceProviderID = json['serviceProviderID']; + customerID = json['customerID']; + } + + Map toJson() { + final Map data = {}; + data['serviceSlotID'] = serviceSlotID; + data['slotDate'] = slotDate; + data['startTime'] = startTime; + data['endTime'] = endTime; + data['appointmentStatusID'] = appointmentStatusID; + data['appointmentStatusText'] = appointmentStatusText; + data['serviceProviderID'] = serviceProviderID; + data['customerID'] = customerID; + return data; + } +} diff --git a/lib/models/provider_branches_models/profile/categroy.dart b/lib/models/provider_branches_models/profile/categroy.dart index 1d0a0c1..8945da0 100644 --- a/lib/models/provider_branches_models/profile/categroy.dart +++ b/lib/models/provider_branches_models/profile/categroy.dart @@ -51,15 +51,17 @@ class CategoryData extends Equatable { this.services, this.branchId, this.branchName, + this.isDeactivated, }); int? id; String? categoryName; String? categoryNameN; - dynamic? serviceCategoryIconUrl; - dynamic? serviceCategoryImageUrl; + String? serviceCategoryIconUrl; + String? serviceCategoryImageUrl; String? branchId; String? branchName; + bool? isDeactivated; List? services; factory CategoryData.fromJson(Map json) => CategoryData( @@ -69,6 +71,7 @@ class CategoryData extends Equatable { serviceCategoryIconUrl: json["serviceCategoryIconUrl"], serviceCategoryImageUrl: json["serviceCategoryImageUrl"], services: [], + isDeactivated: false, ); Map toJson() => { diff --git a/lib/repositories/branch_repo.dart b/lib/repositories/branch_repo.dart index 75fec0a..89f85ff 100644 --- a/lib/repositories/branch_repo.dart +++ b/lib/repositories/branch_repo.dart @@ -5,6 +5,8 @@ import 'package:mc_common_app/api/api_client.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/dependency_injection.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/models/appointments_models/appointment_basic_detail_model.dart'; import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/models/provider_branches_models/branch_detail_model.dart'; import 'package:mc_common_app/models/provider_branches_models/branch_review_model.dart'; @@ -16,6 +18,7 @@ import 'package:mc_common_app/models/provider_branches_models/provider_model.dar import 'package:mc_common_app/models/provider_branches_models/provider_profile_model.dart'; import 'package:mc_common_app/models/services_models/item_model.dart'; import 'package:mc_common_app/models/services_models/service_model.dart'; +import 'package:mc_common_app/utils/enums.dart'; abstract class BranchRepo { Future createBranch({ @@ -70,6 +73,10 @@ abstract class BranchRepo { Future updateService(List> map); + Future updateServiceStatus({required int branchId, required List serviceIds, required ServiceStatusEnum serviceStatusEnum}); + + Future> getAppointmentsByCategoryOrService({required int branchId, required int serviceId}); + Future getMatchedServices(int oldBranchId, int newBranchId, int categoryId); Future duplicateItems({required String providerBranchID, required List items}); @@ -305,6 +312,45 @@ class BranchRepoImp implements BranchRepo { return await apiClient.postJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.serviceProviderServiceUpdate, map, token: t); } + @override + Future updateServiceStatus({required int branchId, required List serviceIds, required ServiceStatusEnum serviceStatusEnum}) async { + List ids = []; + + for (var id in serviceIds) { + ids.add(id.toString()); + } + + int providerID = AppState().getUser.data!.userInfo!.providerId; + var map = { + "ServiceProviderID": providerID.toString(), + "ProviderBranchID": branchId.toString(), + "ProviderServiceIDs": ids, + "Status": serviceStatusEnum.getIdFromServiceStatusEnum().toString(), + }; + String t = AppState().getUser.data!.accessToken ?? ""; + return await apiClient.getJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.serviceProviderServiceStatusUpdate, queryParameters: map, token: t); + } + + @override + Future> getAppointmentsByCategoryOrService({required int branchId, required int serviceId}) async { + var map = { + "ProviderBranchID": branchId.toString(), + "ServiceProviderServiceID": serviceId.toString(), + }; + + String t = AppState().getUser.data!.accessToken ?? ""; + GenericRespModel genericRespModel = await apiClient.getJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.serviceProviderAppointmentGetByCategoryOrService, + queryParameters: map, + token: t, + ); + + List basicAppointmentList = List.generate(genericRespModel.data.length, (index) => AppointmentBasicDetailsModel.fromJson(genericRespModel.data[index])); + + return basicAppointmentList; + } + @override Future getMatchedServices(int oldBranchId, int newBranchId, int categoryId) async { var postParams = { diff --git a/lib/repositories/common_repo.dart b/lib/repositories/common_repo.dart index 24d07f2..d823a16 100644 --- a/lib/repositories/common_repo.dart +++ b/lib/repositories/common_repo.dart @@ -48,7 +48,7 @@ abstract class CommonRepo { Future getCarCheckServiceScheduleDetails({required double lat, required double long}); - Future> getPhotographyServiceScheduleListByOffices({required double lat, required double long}); + Future> getPhotographyServiceScheduleListByOffices({required double lat, required double long, required int cityID}); //TODO: Needs to remove common methods from AD's repo and delete all repeated methods. Future getVehicleDetails({int? vehicleTypeId, int? vehicleBrandId}); @@ -97,10 +97,11 @@ class CommonRepoImp implements CommonRepo { } @override - Future> getPhotographyServiceScheduleListByOffices({required double lat, required double long}) async { + Future> getPhotographyServiceScheduleListByOffices({required double lat, required double long, required int cityID}) async { var params = { "Latitude": lat.toString(), "Longitude": long.toString(), + "cityID": cityID.toString(), }; GenericRespModel genericRespModel = await apiClient.getJsonForObject( token: appState.getUser.data!.accessToken, diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index 947df6d..3d5dfa7 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -1395,8 +1395,9 @@ class AdVM extends BaseVM { Future getPhotographyServiceScheduleListByOffices({required double latitude, required double longitude, bool isNeedToRebuild = false}) async { if (isNeedToRebuild) setState(ViewState.busy); + int cityID = AppState().getUser.data!.userInfo!.cityId ?? 1; try { - photoSSSchedulesByOffices = await commonRepo.getPhotographyServiceScheduleListByOffices(lat: latitude, long: longitude); + photoSSSchedulesByOffices = await commonRepo.getPhotographyServiceScheduleListByOffices(lat: latitude, long: longitude, cityID: cityID); if (isNeedToRebuild) setState(ViewState.idle); } catch (e) { if (isNeedToRebuild) setState(ViewState.idle); diff --git a/lib/view_models/appointments_view_model.dart b/lib/view_models/appointments_view_model.dart index a908f56..010a5cd 100644 --- a/lib/view_models/appointments_view_model.dart +++ b/lib/view_models/appointments_view_model.dart @@ -251,17 +251,6 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - // String pickedHomeLocation = ""; - // - // void updatePickedHomeLocation(String value) { - // pickedHomeLocation = value; - // pickHomeLocationError = ""; - // if (currentServiceSelection != null) { - // currentServiceSelection!.homeLocation = value; - // } - // notifyListeners(); - // } - SelectionModel branchSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); void updateProviderCategoryId(SelectionModel id) { @@ -304,9 +293,6 @@ class AppointmentsVM extends BaseVM { return; } - servicesInCurrentAppointment.forEach((i) { - log("hlo1: ${i.serviceDescription} ${i.servicelocationInfo.homeChargesInCurrentService}"); - }); if (!(currentServiceSelection!.isAllowAppointmentHome ?? false)) { updateIsHomeTapped(false); return; @@ -320,19 +306,8 @@ class AppointmentsVM extends BaseVM { distanceToBranch: currentLocationInfoModel!.distanceToBranch, homeChargesInCurrentService: double.parse(currentServiceSelection!.rangePricePerKm ?? "0.0"), ); - - // for (var service in servicesInCurrentAppointment) { - // service.servicelocationInfo.address = currentLocationInfoModel!.address; - // service.servicelocationInfo.latitude = currentLocationInfoModel!.latitude; - // service.servicelocationInfo.longitude = currentLocationInfoModel!.longitude; - // service.servicelocationInfo.distanceToBranch = currentLocationInfoModel!.distanceToBranch; - // } } - servicesInCurrentAppointment.forEach((i) { - log("hlo2: ${i.serviceDescription} ${i.servicelocationInfo.homeChargesInCurrentService}"); - }); - notifyListeners(); } @@ -383,7 +358,7 @@ class AppointmentsVM extends BaseVM { } applyFilterOnAppointmentsVM({required AppointmentStatusEnum appointmentStatusEnum, bool isNeedCustomerFilter = false}) { - log("appointmentStatusEnum: ${appointmentStatusEnum}"); + log("appointmentStatusEnum: $appointmentStatusEnum"); // isNeedCustomerFilter IS ONLY FOR THE PROVIDER APP if (appointmentsFilterOptions.isEmpty) return; for (var value in appointmentsFilterOptions) { @@ -466,13 +441,15 @@ class AppointmentsVM extends BaseVM { myAppointments = await appointmentRepo.getMyAppointmentsForCustomersByFilters(); // myFilteredAppointments = myAppointments; + + setState(ViewState.idle); + applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments); + myUpComingAppointments = myAppointments .where((element) => (element.appointmentStatusEnum == AppointmentStatusEnum.booked || element.appointmentStatusEnum == AppointmentStatusEnum.confirmed) && (DateHelper.parseStringToDate(element.appointmentDate!).isAfter(DateTime.now()))) .toList(); - setState(ViewState.idle); - applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments); notifyListeners(); } diff --git a/lib/view_models/chat_view_model.dart b/lib/view_models/chat_view_model.dart index 0a9495d..f2c8bf2 100644 --- a/lib/view_models/chat_view_model.dart +++ b/lib/view_models/chat_view_model.dart @@ -97,9 +97,9 @@ class ChatVM extends BaseVM { notifyListeners(); } - List indexesForCancelSpecialCarOffer = [0, 1, 6, 10]; - List indexesForCancelSparePartOffer = [2, 10]; - List indexesForRejectOffer = [3, 4, 5, 10]; + List indexesForCancelSpecialCarOffer = [0, 1, 5, 6, 10]; + List indexesForCancelSparePartOffer = [2, 5, 10]; + List indexesForRejectOffer = [3, 4, 10]; List indexesForDealNotCompleted = [7, 4, 8, 9, 10]; List offerRejectModelList = [ diff --git a/lib/view_models/requests_view_model.dart b/lib/view_models/requests_view_model.dart index dcfebf1..9a1e7f1 100644 --- a/lib/view_models/requests_view_model.dart +++ b/lib/view_models/requests_view_model.dart @@ -950,6 +950,14 @@ class RequestsVM extends BaseVM { notifyListeners(); } + bool isBase64String(String input) { + // This regex checks if the string is a valid Base64 string + final base64Regex = RegExp(r'^[A-Za-z0-9+/=]+$'); + + // Check if the string matches the Base64 pattern + return base64Regex.hasMatch(input) && (input.length % 4 == 0); + } + MessageImageModel convertFileToMessageImageModel({required ImageModel imageModel, required int offerId}) { MessageImageModel offerImages; @@ -961,15 +969,23 @@ class RequestsVM extends BaseVM { reqOfferID: offerId, ); } else { - File file = File(imageModel.filePath!); - List imageBytes = file.readAsBytesSync(); - String image = base64Encode(imageBytes); + File? file; + List imageBytes = []; + String image = ''; + if (!isBase64String(imageModel.filePath!)) { + file = File(imageModel.filePath!); + imageBytes = file.readAsBytesSync(); + image = base64Encode(imageBytes); + } else { + image = imageModel.filePath!; + } + offerImages = MessageImageModel( id: 0, imageStr: image, isFromNetwork: false, reqOfferID: offerId, - imagePath: file.path, + imagePath: file?.path, ); } diff --git a/lib/view_models/service_view_model.dart b/lib/view_models/service_view_model.dart index 1068494..a6d1172 100644 --- a/lib/view_models/service_view_model.dart +++ b/lib/view_models/service_view_model.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; @@ -6,8 +7,10 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.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/models/appointments_models/appointment_basic_detail_model.dart'; import 'package:mc_common_app/models/general_models/enums_model.dart'; import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/models/general_models/widgets_models.dart'; @@ -24,12 +27,17 @@ import 'package:mc_common_app/models/user_models/country.dart'; import 'package:mc_common_app/repositories/branch_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/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/ad_view_model.dart'; import 'package:mc_common_app/view_models/base_view_model.dart'; +import 'package:mc_common_app/widgets/button/show_fill_button.dart'; +import 'package:mc_common_app/widgets/common_widgets/info_bottom_sheet.dart'; import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; class ServiceVM extends BaseVM { final BranchRepo branchRepo; @@ -156,6 +164,7 @@ class ServiceVM extends BaseVM { final BranchDetailModel currentBranch = branches!.data!.serviceProviderBranch!.firstWhere((element) => element.id == selectedBranchId); for (var element in currentBranch.branchServices!) { + // TODO: Here , we need to add the category deactivated status. categories.add( CategoryData( id: element.categoryId, @@ -201,7 +210,6 @@ class ServiceVM extends BaseVM { } branchServicesFilterOptions[serviceStatusEnum.getIdFromServiceStatusEnum() - 1].isSelected = true; // -1 to match with the index - } // Future selectFile(BuildContext context, int index) async { @@ -264,11 +272,7 @@ class ServiceVM extends BaseVM { context, allowMultiple: false, ); - if (files != null && files.any((element) => - element.path - .split('.') - .last - .toLowerCase() != 'pdf')) { + if (files != null && files.any((element) => element.path.split('.').last.toLowerCase() != 'pdf')) { Utils.showToast("Only PDF Files are allowed"); return; } @@ -282,8 +286,8 @@ class ServiceVM extends BaseVM { documentID == 1 ? commerceCertificates.addAll(imageModels) : documentID == 2 - ? commercialCertificates.addAll(imageModels) - : vatCertificates.addAll(imageModels); + ? commercialCertificates.addAll(imageModels) + : vatCertificates.addAll(imageModels); document!.data![index].document = Utils.convertFileToBase64(files.first); document!.data![index].fileExt = Utils.checkFileExt(files.first.path); document!.data![index].documentUrl = files.first.path; @@ -459,10 +463,10 @@ class ServiceVM extends BaseVM { DropValue( element.id ?? 0, ((element.categoryName!.isEmpty - ? "N/A" - : countryCode == "SA" - ? element.categoryNameN - : element.categoryName) ?? + ? "N/A" + : countryCode == "SA" + ? element.categoryNameN + : element.categoryName) ?? "N/A"), "", ), @@ -528,6 +532,92 @@ class ServiceVM extends BaseVM { return await branchRepo.updateService(map); } + Future> getAppointmentsByServiceID({required BuildContext context, required int branchId, required int serviceId}) async { + try { + Utils.showLoading(context); + List appointmentList = await branchRepo.getAppointmentsByCategoryOrService(branchId: branchId, serviceId: serviceId); + // log("list: ${mResponse.}"); + Utils.hideLoading(context); + + return appointmentList; + } catch (e) { + Utils.hideLoading(context); + log(e.toString()); + Utils.showToast(e.toString() ?? ""); + return []; + } + } + + Future updateServiceStatus({required BuildContext context, required ServiceStatusEnum serviceStatusEnum, required int branchId, required List providerServiceIds}) async { + try { + Utils.showLoading(context); + GenericRespModel genericRespModel = await branchRepo.updateServiceStatus(branchId: branchId, serviceIds: providerServiceIds, serviceStatusEnum: serviceStatusEnum); + Utils.hideLoading(context); + Utils.showToast(genericRespModel.message ?? ""); + return genericRespModel.messageStatus == 1; + } catch (e) { + Utils.hideLoading(context); + log(e.toString()); + Utils.showToast(e.toString()); + return false; + } + } + + Future buildDealNotCompletedBottomSheetOptions({required BuildContext mainContext, required List appointments, required String branchName}) async { + return actionConfirmationBottomSheet( + isOnlyOneButton: true, + context: mainContext, + title: LocaleKeys.notice.tr().toText(fontSize: 26, isBold: true, letterSpacing: -1.44), + subtitle: LocaleKeys.youCannotDeactivateThisServiceRightNow.tr(), + checkBoxConfirmationWidget: ListView.builder( + shrinkWrap: true, + itemCount: appointments.length, + itemBuilder: (BuildContext context, int index) { + AppointmentBasicDetailsModel appointmentBasicDetailsModel = appointments[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (appointmentBasicDetailsModel.appointmentStatusText != null && appointmentBasicDetailsModel.appointmentStatusText!.isNotEmpty) ...[ + Utils.statusContainerChip(text: appointmentBasicDetailsModel.appointmentStatusText!, chipColor: MyColors.black), + ], + 6.height, + "${appointmentBasicDetailsModel.customerID}".toText(fontSize: 16, letterSpacing: -0.64), + showItem("${LocaleKeys.branchName.tr()}:", "${appointmentBasicDetailsModel.slotDate}"), + showItem("${LocaleKeys.date.tr()}:", "${appointmentBasicDetailsModel.slotDate}"), + showItem("${LocaleKeys.time.tr()}:", "${appointmentBasicDetailsModel.startTime} - ${appointmentBasicDetailsModel.startTime}"), + ], + ).toContainer(isShadowEnabled: true); + }), + actionButtonYes: Expanded( + child: ShowFillButton( + maxHeight: 55, + title: LocaleKeys.done.tr(), + fontSize: 15, + onPressed: () => Navigator.pop(mainContext), + ), + ), + actionButtonNo: Expanded( + child: ShowFillButton( + maxHeight: 55, + title: LocaleKeys.done.tr(), + fontSize: 15, + onPressed: () => Navigator.pop(mainContext), + ), + ), + ); + } + + Widget showItem(String title, String value) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText(color: MyColors.lightTextColor, letterSpacing: -0.48), + 3.width, + Flexible(child: value.toText(isBold: true, overflow: TextOverflow.ellipsis)), + ], + ); + } + void updateSelectedBranchType(int status) { selectedBranchStatus = status; notifyListeners(); @@ -657,9 +747,7 @@ class ServiceVM extends BaseVM { File file = File(imageModel.filePath!); List imageBytes = await file.readAsBytes(); String image = base64Encode(imageBytes); - String fileName = file.path - .split('/') - .last; + String fileName = file.path.split('/').last; branchPostingImages = BranchPostingImages( imageName: fileName, imageStr: image, diff --git a/lib/views/advertisement/ads_detail_view/components.dart b/lib/views/advertisement/ads_detail_view/components.dart index a2eddec..fd3be94 100644 --- a/lib/views/advertisement/ads_detail_view/components.dart +++ b/lib/views/advertisement/ads_detail_view/components.dart @@ -293,24 +293,24 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { 20.height, adVM.state == ViewState.busy ? const Center(child: CircularProgressIndicator()) - : Builder( - builder: (context) { - List vehicleCitiesDrop = []; - 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())); - } - - return DropdownField( - (DropValue value) => adVM.updatePhotoOfficeSelectedId(SelectionModel(selectedId: value.id, selectedOption: value.value, itemPrice: value.subValue)), - // here the item price is the index of the selected option - list: vehicleCitiesDrop, - dropdownValue: adVM.photoOfficeSelectedId.selectedId != -1 ? DropValue(adVM.photoOfficeSelectedId.selectedId, adVM.photoOfficeSelectedId.selectedOption, "") : null, - hint: LocaleKeys.selectOffice.tr(), - errorValue: adVM.photoOfficeSelectedId.errorValue, - ); - }, - ), + : adVM.photoSSSchedulesByOffices.isEmpty + ? LocaleKeys.noAvailableOfficesInCity.tr().toText(fontSize: 14, height: 1.2) + : Builder( + builder: (context) { + List vehicleCitiesDrop = []; + 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())); + } + 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, + hint: LocaleKeys.selectOffice.tr(), + errorValue: adVM.photoOfficeSelectedId.errorValue, + ); + }, + ), if (adVM.photoOfficeSelectedId.selectedId != -1) ...[ 9.height, CustomCalenderAppointmentWidget( diff --git a/lib/views/advertisement/components/picked_images_container_widget.dart b/lib/views/advertisement/components/picked_images_container_widget.dart index dff6f3f..91e3454 100644 --- a/lib/views/advertisement/components/picked_images_container_widget.dart +++ b/lib/views/advertisement/components/picked_images_container_widget.dart @@ -1,5 +1,5 @@ -import 'dart:developer'; -import 'dart:io'; +import 'dart:convert'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -33,9 +33,6 @@ class PickedFilesContainer extends StatelessWidget { @override Widget build(BuildContext context) { - pickedFiles.forEach((i) { - log("isFromNetwork: ${i.isFromNetwork}"); - }); return GridView.count( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, @@ -95,8 +92,20 @@ class BuildFilesContainer extends StatelessWidget { this.isPdf = false, }); + bool isBase64String(String input) { + // This regex checks if the string is a valid Base64 string + final base64Regex = RegExp(r'^[A-Za-z0-9+/=]+$'); + + // Check if the string matches the Base64 pattern + return base64Regex.hasMatch(input) && (input.length % 4 == 0); + } + @override Widget build(BuildContext context) { + Uint8List? bytes; + if ((image.isFromNetwork == null || image.isFromNetwork == false) && isBase64String(image.filePath ?? "")) { + bytes = (base64Decode(image.filePath!)) as Uint8List?; + } return Stack( children: [ SizedBox( @@ -124,13 +133,20 @@ class BuildFilesContainer extends StatelessWidget { width: 73, ) .paddingAll(8) - : image.filePath! - .buildFileImage( - fit: BoxFit.fill, - height: 72, - width: 70, - ) - .paddingAll(8), + : isBase64String(image.filePath!) + ? Image.memory( + bytes!, + fit: BoxFit.fill, + height: 72, + width: 70, + ).paddingAll(8) + : image.filePath! + .buildFileImage( + fit: BoxFit.fill, + height: 72, + width: 70, + ) + .paddingAll(8), if (!isReview) ...[ Align( alignment: Alignment.topRight, diff --git a/lib/views/chat/chat_view.dart b/lib/views/chat/chat_view.dart index 94c26ac..60861cc 100644 --- a/lib/views/chat/chat_view.dart +++ b/lib/views/chat/chat_view.dart @@ -221,6 +221,7 @@ class _ChatViewState extends State { size: 30, ).onPress( () { + requestVM.resetSendOfferBottomSheet(); RequestDetailPageArguments requestDetailArguments = RequestDetailPageArguments( requestIndex: chatViewArgumentsForRequest!.requestIndex, requestModel: chatViewArgumentsForRequest!.requestModel!, diff --git a/lib/views/chat/widgets/chat_message_widget.dart b/lib/views/chat/widgets/chat_message_widget.dart index 62a1117..5ede78f 100644 --- a/lib/views/chat/widgets/chat_message_widget.dart +++ b/lib/views/chat/widgets/chat_message_widget.dart @@ -742,13 +742,13 @@ class _ChatMessageCustomWidgetState extends State { requestVM.updateIsDeliveryAvailableStatus((offer.isDeliveryAvailable ?? false)); if (offer.reqOfferImages != null && offer.reqOfferImages!.isNotEmpty) { for (var element in offer.reqOfferImages!) { - log("element: ${element.imageStr != null && element.imageStr!.isNotEmpty}"); - if (element.imageUrl != null || element.imageStr != null) { ImageModel imageModel = ImageModel( id: element.id, filePath: element.imageStr != null && element.imageStr!.isNotEmpty ? element.imageStr : element.imageUrl, isFromNetwork: element.imageStr != null && element.imageStr!.isNotEmpty ? false : true); + + log("imageModelimageModel: ${imageModel.toString()}"); requestVM.addImageToPickedVehicleImages(imageModel); } } @@ -769,8 +769,8 @@ class _ChatMessageCustomWidgetState extends State { ], ), ] else if (AppState().currentAppType == AppType.provider && - widget.chatMessageModel.reqOffer!.requestOfferStatusEnum == RequestOfferStatusEnum.offer && chatMessageTypeEnum == ChatMessageTypeEnum.offer && + widget.chatMessageModel.reqOffer!.requestOfferStatusEnum == RequestOfferStatusEnum.offer && (widget.chatMessageModel.isMyMessage == true)) ...[ MyAssets.icEdit.buildSvg(color: MyColors.white, height: 15).onPress(() => onOfferEditIconPressed()), ], diff --git a/lib/views/common_fragments/requests_fragment.dart b/lib/views/common_fragments/requests_fragment.dart index 7ff9a4b..3d7544c 100644 --- a/lib/views/common_fragments/requests_fragment.dart +++ b/lib/views/common_fragments/requests_fragment.dart @@ -3,6 +3,7 @@ import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/requests_models/request_model.dart'; import 'package:mc_common_app/utils/dialogs_and_bottomsheets.dart'; import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; @@ -156,11 +157,25 @@ class MyRequestsFragment extends StatelessWidget { return; }, child: RequestItem( - request: requestsVM.myFilteredRequests[index], - appType: AppState().currentAppType, - requestIndex: index, - shouldShowStatuses: AppState().currentAppType == AppType.customer, - ), + request: requestsVM.myFilteredRequests[index], + shouldShowStatuses: AppState().currentAppType == AppType.customer, + onTap: () async { + RequestModel request = requestsVM.myFilteredRequests[index]; + requestsVM.updateCurrentSelectedRequest(request); + if (request.requestStatus == RequestStatusEnum.pending || + request.requestStatus == RequestStatusEnum.cancelled || + request.requestStatus == RequestStatusEnum.expired) { + return; + } + if (AppState().currentAppType == AppType.provider) { + RequestDetailPageArguments requestDetailPageArguments = RequestDetailPageArguments(requestIndex: index, requestModel: request); + navigateWithName(context, AppRoutes.requestsDetailPage, arguments: requestDetailPageArguments); + } else { + requestsVM.myFilteredRequests[index].offerCount = 0; + requestsVM.notifyListeners(); + navigateWithName(context, AppRoutes.offersListPage, arguments: request.id); + } + }), ); }, separatorBuilder: (context, index) { diff --git a/lib/views/requests/offer_list_page.dart b/lib/views/requests/offer_list_page.dart index 785d3f3..dab5a41 100644 --- a/lib/views/requests/offer_list_page.dart +++ b/lib/views/requests/offer_list_page.dart @@ -80,19 +80,21 @@ class _OfferListPageState extends State { fontSize: 16, isBold: true, ), - Center( - child: "${offersModel.offerCount}".toText( - color: Colors.white, - isBold: true, - fontSize: 10, + if (offersModel.offerCount != null && offersModel.offerCount! > 0) ...[ + Center( + child: "${offersModel.offerCount}".toText( + color: Colors.white, + isBold: true, + fontSize: 10, + ), + ).toContainer( + backgroundColor: MyColors.cancelledColor, + borderRadius: 100, + paddingAll: 1, + width: 22, + height: 22, ), - ).toContainer( - backgroundColor: MyColors.cancelledColor, - borderRadius: 100, - paddingAll: 1, - width: 22, - height: 22, - ), + ], ], ), 8.height, @@ -131,13 +133,16 @@ class _OfferListPageState extends State { await chatVM .getRequestsChatMessagesForCustomer( - context: context, - providerId: offersModel.providerId ?? 0, - requestOfferId: 0, - requestId: widget.requestId, - providerOfferIndex: index, - ) - .whenComplete(() => navigateWithName(context, AppRoutes.chatView, arguments: chatViewArguments)); + context: context, + providerId: offersModel.providerId ?? 0, + requestOfferId: 0, + requestId: widget.requestId, + providerOfferIndex: index, + ) + .whenComplete(() { + chatVM.serviceProviderOffersList[index].offerCount = 0; + navigateWithName(context, AppRoutes.chatView, arguments: chatViewArguments); + }); }).toContainer(isShadowEnabled: true); }, separatorBuilder: (context, index) => 16.height, diff --git a/lib/views/requests/widget/request_item.dart b/lib/views/requests/widget/request_item.dart index 57e3c4d..724abda 100644 --- a/lib/views/requests/widget/request_item.dart +++ b/lib/views/requests/widget/request_item.dart @@ -2,27 +2,22 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:mc_common_app/config/routes.dart'; +import 'package:mc_common_app/classes/app_state.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/models/requests_models/request_model.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/chat_view_model.dart'; -import 'package:mc_common_app/view_models/requests_view_model.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; -import 'package:provider/provider.dart'; class RequestItem extends StatelessWidget { final RequestModel request; - final AppType appType; - final int requestIndex; final bool shouldShowStatuses; + final Function() onTap; - const RequestItem({super.key, required this.request, required this.appType, required this.requestIndex, this.shouldShowStatuses = true}); + const RequestItem({super.key, required this.request, this.shouldShowStatuses = true, required this.onTap}); @override Widget build(BuildContext context) { @@ -65,7 +60,7 @@ class RequestItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (request.offerCount > 0 && appType == AppType.customer && request.requestStatus == RequestStatusEnum.submitted) ...[ + if (request.offerCount > 0 && AppState().currentAppType == AppType.customer && request.requestStatus == RequestStatusEnum.submitted) ...[ Center( child: "${request.offerCount}".toText( color: Colors.white, @@ -112,17 +107,7 @@ class RequestItem extends StatelessWidget { // ), ], ).toContainer(isShadowEnabled: true).onPress(() async { - RequestsVM requestsVM = context.read(); - requestsVM.updateCurrentSelectedRequest(request); - if (request.requestStatus == RequestStatusEnum.pending || request.requestStatus == RequestStatusEnum.cancelled || request.requestStatus == RequestStatusEnum.expired) { - return; - } - if (appType == AppType.provider) { - RequestDetailPageArguments requestDetailPageArguments = RequestDetailPageArguments(requestIndex: requestIndex, requestModel: request); - navigateWithName(context, AppRoutes.requestsDetailPage, arguments: requestDetailPageArguments); - } else { - navigateWithName(context, AppRoutes.offersListPage, arguments: request.id); - } + onTap(); }); } diff --git a/lib/views/setting_options/provider_accepted_requests_view.dart b/lib/views/setting_options/provider_accepted_requests_view.dart index b6c8b51..ce38770 100644 --- a/lib/views/setting_options/provider_accepted_requests_view.dart +++ b/lib/views/setting_options/provider_accepted_requests_view.dart @@ -5,6 +5,7 @@ import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/requests_models/request_model.dart'; import 'package:mc_common_app/utils/dialogs_and_bottomsheets.dart'; import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; @@ -80,7 +81,24 @@ class _ProviderAcceptedRequestsViewState extends State