Merge branch 'faiz_development_common' into aamir_dev

# Conflicts:
#	assets/langs/ar-SA.json
#	assets/langs/en-US.json
#	lib/generated/codegen_loader.g.dart
#	lib/generated/locale_keys.g.dart
aamir_dev
Aamir 11 months ago
commit 1fd69dc2e4

@ -745,6 +745,26 @@
"active": "نشط",
"paymentType": "نوع الدفع",
"searchByCreatedDate": "البحث حسب تاريخ الإنشاء",
"dealCompleted": "تم إتمام الصفقة",
"theDealNotCompleted": "لم تكتمل الصفقة",
"cancelRequest": "أريد إلغاء الطلب.",
"highPrice": "سعر مرتفع",
"offerNotMatched": "العرض لم يتطابق مع الطلب.",
"offerRejected": "تم رفض العرض.",
"offerAccepted": "تم قبول العرض.",
"you": "أنتم",
"youCannotDeactivateThisServiceRightNow": "لا يمكنك تعطيل هذه الخدمة الآن لأن لديك مواعيد معلقة لهذه الخدمة. تفاصيل آخر موعد هي:",
"done": "تم",
"notice": "إشعار",
"serviceDeactivated": "تم تعطيل الخدمة",
"totalNumberOfServices": "عدد الخدمات:",
"noAvailableOfficesInCity": "لا توجد مكاتب متاحة في مدينتك لخدمتك.",
"searchByItem": "البحث حسب العنصر",
"selectBranchToCopyServices": "حدد الفرع لنسخ العناصر الخاصة بهم إلى هذا الفرع. يمكنك تعديل الاختيار في أي وقت.",
"tapToSeeItems": "اضغط لرؤية العناصر",
"tapToSelect": "اضغط للاختيار",
"noteCopyItemsExplanation": "ملاحظة: ستتمكن من نسخ العناصر من خدمة إلى أخرى في الفئة المحددة. يجب عليك إنشاء الخدمات أولاً ويجب أن تكون معتمدة. ثم ستتمكن من الحصول على الخدمات المتاحة التي يمكنك نسخ جميع العناصر منها أو تحديد العناصر التي تريد نسخها.",
"requestCreatedOn": "تم إنشاء الطلب في",
"cityNameMandatory": "المدينة إلزامية",
"genderMandatory": "الجنس إلزامي",
"updateCity": "تحديث المدينة",

@ -739,6 +739,26 @@
"active": "Active",
"paymentType": "Payment Type",
"searchByCreatedDate": "Search By Created Date",
"dealCompleted": "The Deal Completed",
"theDealNotCompleted": "The Deal Not Completed",
"cancelRequest": "I want to cancel the request.",
"highPrice": "High Price",
"offerNotMatched": "The offer did not match the request.",
"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": "Number of services:",
"noAvailableOfficesInCity": "There are no available offices in your city for your service.",
"searchByItem": "Search By Item",
"selectBranchToCopyServices": "Select the branch to copy their items to this branch. You can modify the selection at any time.",
"tapToSelect": "Tap to select",
"tapToSeeItems": "Tap to see items",
"noteCopyItemsExplanation": "Note: You will be able to copy items from one service to another in a selected category. You must create the services first and they should be approved. Then you will be able to get the available services from which you can copy all or selected items.",
"requestCreatedOn": "Request created on",
"updateUserDetails": "Update User Details",
"enterNewFirstName": "Enter First Name",
"enterNewLastName": "Enter Last Name",

@ -56,8 +56,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";
@ -164,7 +166,8 @@ class ApiConsts {
static String createRequest = "${baseUrlServices}api/RequestManagement/Request_Create";
static String getRequest = "${baseUrlServices}api/RequestManagement/Request_Get";
static String getRequestOffers = "${baseUrlServices}api/RequestManagement/ReqOffer_Get";
static String updateRequestOffer = "${baseUrlServices}api/RequestManagement/RequestOffer_UpdateStatus";
static String updateRequestOfferStatus = "${baseUrlServices}api/RequestManagement/RequestOffer_UpdateStatus";
static String updateRequestOffer = "${baseUrlServices}api/RequestManagement/ReqOffer_Update";
static String updateRequestCustomer = "${baseUrlServices}api/RequestManagement/Request_StatusUpdate_Customer";
static String updateRequestProvider = "${baseUrlServices}api/RequestManagement/Request_StatusUpdate_Provider";
static String requestOffersSpsGet = "${baseUrlServices}api/RequestManagement/Request_OfferSPs_Get";

@ -796,7 +796,7 @@ extension FormatMonthByNumber on String {
extension NameExtensions on String {
String getInitials() {
final name = trim();
String initials = "U";
String initials = "";
if (name.isEmpty) {
return initials;
}

@ -761,6 +761,27 @@ class CodegenLoader extends AssetLoader{
"active": "نشط",
"paymentType": "نوع الدفع",
"searchByCreatedDate": "البحث حسب تاريخ الإنشاء",
"dealCompleted": "تم إتمام الصفقة",
"theDealNotCompleted": "لم تكتمل الصفقة",
"cancelRequest": "أريد إلغاء الطلب.",
"highPrice": "سعر مرتفع",
"offerNotMatched": "العرض لم يتطابق مع الطلب.",
"offerRejected": "تم رفض العرض.",
"offerAccepted": "تم قبول العرض.",
"you": "أنتم",
"youCannotDeactivateThisServiceRightNow": "لا يمكنك تعطيل هذه الخدمة الآن لأن لديك مواعيد معلقة لهذه الخدمة. تفاصيل آخر موعد هي:",
"done": "تم",
"notice": "إشعار",
"serviceDeactivated": "تم تعطيل الخدمة",
"totalNumberOfServices": "عدد الخدمات:",
"noAvailableOfficesInCity": "لا توجد مكاتب متاحة في مدينتك لخدمتك.",
"searchByItem": "البحث حسب العنصر",
"selectBranchToCopyServices": "حدد الفرع لنسخ العناصر الخاصة بهم إلى هذا الفرع. يمكنك تعديل الاختيار في أي وقت.",
"tapToSeeItems": "اضغط لرؤية العناصر",
"tapToSelect": "اضغط للاختيار",
"noteCopyItemsExplanation": "ملاحظة: ستتمكن من نسخ العناصر من خدمة إلى أخرى في الفئة المحددة. يجب عليك إنشاء الخدمات أولاً ويجب أن تكون معتمدة. ثم ستتمكن من الحصول على الخدمات المتاحة التي يمكنك نسخ جميع العناصر منها أو تحديد العناصر التي تريد نسخها.",
"requestCreatedOn": "تم إنشاء الطلب في"
"searchByCreatedDate": "البحث حسب تاريخ الإنشاء",
"cityNameMandatory": "المدينة إلزامية",
"genderMandatory": "الجنس إلزامي",
"updateCity": "تحديث المدينة",
@ -1513,6 +1534,26 @@ static const Map<String,dynamic> en_US = {
"active": "Active",
"paymentType": "Payment Type",
"searchByCreatedDate": "Search By Created Date",
"dealCompleted": "The Deal Completed",
"theDealNotCompleted": "The Deal Not Completed",
"cancelRequest": "I want to cancel the request.",
"highPrice": "High Price",
"offerNotMatched": "The offer did not match the request.",
"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": "Number of services:",
"noAvailableOfficesInCity": "There are no available offices in your city for your service.",
"searchByItem": "Search By Item",
"selectBranchToCopyServices": "Select the branch to copy their items to this branch. You can modify the selection at any time.",
"tapToSelect": "Tap to select",
"tapToSeeItems": "Tap to see items",
"noteCopyItemsExplanation": "Note: You will be able to copy items from one service to another in a selected category. You must create the services first and they should be approved. Then you will be able to get the available services from which you can copy all or selected items.",
"requestCreatedOn": "Request created on",
"updateUserDetails": "Update User Details",
"enterNewFirstName": "Enter First Name",
"enterNewLastName": "Enter Last Name",

@ -724,6 +724,26 @@ abstract class LocaleKeys {
static const active = 'active';
static const paymentType = 'paymentType';
static const searchByCreatedDate = 'searchByCreatedDate';
static const dealCompleted = 'dealCompleted';
static const theDealNotCompleted = 'theDealNotCompleted';
static const cancelRequest = 'cancelRequest';
static const highPrice = 'highPrice';
static const offerNotMatched = 'offerNotMatched';
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';
static const selectBranchToCopyServices = 'selectBranchToCopyServices';
static const tapToSeeItems = 'tapToSeeItems';
static const tapToSelect = 'tapToSelect';
static const noteCopyItemsExplanation = 'noteCopyItemsExplanation';
static const requestCreatedOn = 'requestCreatedOn';
static const cityNameMandatory = 'cityNameMandatory';
static const genderMandatory = 'genderMandatory';
static const updateCity = 'updateCity';

@ -7,6 +7,10 @@ class AdsDurationModel {
bool? isActive;
String? countryName;
String? currency;
double? discountValue;
double? discountPercent;
double? priceAfterDiscount;
int? roleID;
bool? isSelected;
AdsDurationModel({
@ -18,6 +22,10 @@ class AdsDurationModel {
this.isActive,
this.countryName,
this.currency,
this.discountValue,
this.discountPercent,
this.priceAfterDiscount,
this.roleID,
this.isSelected,
});
@ -30,6 +38,10 @@ class AdsDurationModel {
isActive = json['isActive'];
countryName = json['countryName'];
currency = json['currency'];
discountValue = json['discountValue'];
discountPercent = json['discountPercent'];
priceAfterDiscount = json['priceAfterDiscount'];
roleID = json['roleID'];
isSelected = false;
}
@ -43,6 +55,10 @@ class AdsDurationModel {
data['isActive'] = isActive;
data['countryName'] = countryName;
data['currency'] = currency;
data['discountValue'] = discountValue;
data['discountPercent'] = discountPercent;
data['priceAfterDiscount'] = priceAfterDiscount;
data['roleID'] = roleID;
return data;
}
}

@ -12,7 +12,7 @@ class SSPhotoOfficeScheduleModel {
String? areaName;
String? latitude;
String? longitude;
int? distanceKM;
double? distanceKM;
int? totalItemsCount;
List<PhotoOfficeScheduleSlots>? photoOfficeScheduleSlots;
List<CustomTimeDateSlotModel>? customTimeDateSlotList;

@ -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<String, dynamic> 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<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
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;
}
}

@ -1,9 +1,10 @@
class FilterListModel {
String title;
String iconUrl;
int id;
bool isSelected;
FilterListModel({required this.id, required this.isSelected, required this.title});
FilterListModel({required this.id, required this.isSelected, this.iconUrl = "", required this.title});
}
class SelectionModel {

@ -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<ServiceModel>? services;
factory CategoryData.fromJson(Map<String, dynamic> json) => CategoryData(
@ -69,6 +71,7 @@ class CategoryData extends Equatable {
serviceCategoryIconUrl: json["serviceCategoryIconUrl"],
serviceCategoryImageUrl: json["serviceCategoryImageUrl"],
services: [],
isDeactivated: false,
);
Map<String, dynamic> toJson() => {

@ -62,8 +62,10 @@ class AdsRepoImp implements AdsRepo {
@override
Future<List<AdsDurationModel>> getAdsDuration({required int? countryId}) async {
int roleID = appState.getUser!.data!.userInfo!.roleId ?? 0;
var param = {
"CountryID": countryId.toString() ?? "0",
"roleID": roleID.toString() ?? "0",
};
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(
token: appState.getUser.data!.accessToken,
@ -213,9 +215,7 @@ class AdsRepoImp implements AdsRepo {
"VehicleModelYearIDs": vehicleModelYearIdsList ?? [],
"CreatedByRoles": createdByRolesIdsList ?? [],
"AdsStatuses": ["${AdPostStatus.active.getIdFromAdPostStatusEnum()}"], //only Active ADS
// TODO: This has to be converted into list
"VehicleNew": (vehicleAdConditionIdsList != null && vehicleAdConditionIdsList.isNotEmpty) ? vehicleAdConditionIdsList.first.toString() : "",
"VehicleNew": vehicleAdConditionIdsList ?? [],
"CreatedOn": (vehicleAdCreatedDateList != null && vehicleAdCreatedDateList.isNotEmpty) ? vehicleAdCreatedDateList.first.toString() : "",
"isActive": "true", //only Active ADS
"isExplore": "true",

@ -19,6 +19,7 @@ abstract class AppointmentRepo {
List<String>? providerIdsList,
List<String>? categoryIdsList,
List<String>? serviceIdsList,
List<String>? itemIdsList,
List<String>? branchIdsList,
});
@ -223,6 +224,7 @@ class AppointmentRepoImp implements AppointmentRepo {
List<String>? providerIdsList,
List<String>? categoryIdsList,
List<String>? serviceIdsList,
List<String>? itemIdsList,
List<String>? branchIdsList,
}) async {
var params = {
@ -230,6 +232,7 @@ class AppointmentRepoImp implements AppointmentRepo {
"ServiceProviderIDs": providerIdsList ?? [],
"ProviderBranchIDs": branchIdsList ?? [],
"ServiceIDs": serviceIdsList ?? [],
"ServiceItemIDs": itemIdsList ?? [],
"CategoryIDs": categoryIdsList ?? [],
};
GenericRespModel genericRespModel = await apiClient.getJsonForObject(

@ -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<GenericRespModel> createBranch({
@ -70,6 +73,10 @@ abstract class BranchRepo {
Future<GenericRespModel> updateService(List<Map<String, dynamic>> map);
Future<GenericRespModel> updateServiceStatus({required int branchId, required List<int> serviceIds, required ServiceStatusEnum serviceStatusEnum});
Future<List<AppointmentBasicDetailsModel>> getAppointmentsByCategoryOrService({required int branchId, required int serviceId});
Future<GenericRespModel> getMatchedServices(int oldBranchId, int newBranchId, int categoryId);
Future<GenericRespModel> duplicateItems({required String providerBranchID, required List<int> items});
@ -86,7 +93,7 @@ abstract class BranchRepo {
Future<List<BranchDetailModel>> getAllNearBranchAndServices({required double latitude, required double longitude});
Future<List<ItemData>> getServiceItems(int serviceId);
Future<List<ItemData>> getServiceItems({required int serviceId});
Future<ProviderProfileModel> getBranchAndServicesByProviderId(int providerId, {double? latitude, double? longitude});
@ -230,7 +237,7 @@ class BranchRepoImp implements BranchRepo {
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(
token: t,
(json) => GenericRespModel.fromJson(json),
(json) => GenericRespModel.fromJson(json),
ApiConsts.serviceProviderDDLGet,
);
List<ProviderBasicDataModel> providersList = List.generate(adsGenericModel.data.length, (index) => ProviderBasicDataModel.fromJson(adsGenericModel.data[index]));
@ -262,7 +269,8 @@ class BranchRepoImp implements BranchRepo {
serviceProviderBranchImages.add(imageMap);
}
String lat = "0", long = "0";
String lat = "0",
long = "0";
try {
lat = latitude.toString().substring(0, 9);
long = longitude.toString().substring(0, 9);
@ -306,6 +314,45 @@ class BranchRepoImp implements BranchRepo {
return await apiClient.postJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.serviceProviderServiceUpdate, map, token: t);
}
@override
Future<GenericRespModel> updateServiceStatus({required int branchId, required List<int> serviceIds, required ServiceStatusEnum serviceStatusEnum}) async {
List<String> 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<List<AppointmentBasicDetailsModel>> 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<AppointmentBasicDetailsModel> basicAppointmentList = List.generate(genericRespModel.data.length, (index) => AppointmentBasicDetailsModel.fromJson(genericRespModel.data[index]));
return basicAppointmentList;
}
@override
Future<GenericRespModel> getMatchedServices(int oldBranchId, int newBranchId, int categoryId) async {
var postParams = {
@ -370,7 +417,7 @@ class BranchRepoImp implements BranchRepo {
String t = AppState().getUser.data!.accessToken ?? "";
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(
(json) => GenericRespModel.fromJson(json),
(json) => GenericRespModel.fromJson(json),
ApiConsts.getAllNearBranches,
token: appState.getUser.data!.accessToken,
queryParameters: queryParameters,
@ -398,7 +445,7 @@ class BranchRepoImp implements BranchRepo {
"longitude": longitude.toString(),
};
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(
(json) => GenericRespModel.fromJson(json),
(json) => GenericRespModel.fromJson(json),
ApiConsts.getAllNearBranches,
token: appState.getUser.data!.accessToken,
queryParameters: queryParameters,
@ -410,7 +457,7 @@ class BranchRepoImp implements BranchRepo {
@override
Future<List<BranchDetailModel>> getMyRecentBranchesWithServices() async {
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(
(json) => GenericRespModel.fromJson(json),
(json) => GenericRespModel.fromJson(json),
ApiConsts.getMyRecentBranches,
token: appState.getUser.data!.accessToken,
);
@ -419,16 +466,16 @@ class BranchRepoImp implements BranchRepo {
}
@override
Future<List<ItemData>> getServiceItems(int serviceId) async {
Future<List<ItemData>> getServiceItems({required int serviceId}) async {
var queryParameters = {
"ServiceProviderServiceID": serviceId.toString(),
};
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(
(json) => GenericRespModel.fromJson(json),
(json) => GenericRespModel.fromJson(json),
ApiConsts.getServiceItems,
token: appState.getUser.data!.accessToken,
queryParameters: queryParameters,
queryParameters: serviceId.toString() != "-1" ? queryParameters : null,
);
List<ItemData> serviceItems = List.generate(adsGenericModel.data.length, (index) => ItemData.fromJson(adsGenericModel.data[index]));
return serviceItems;
@ -445,7 +492,7 @@ class BranchRepoImp implements BranchRepo {
postParams.addAll({"Latitude": "$latitude"});
}
GenericRespModel adsGenericModel =
await apiClient.getJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.branchesAndServices, token: appState.getUser.data!.accessToken, queryParameters: postParams);
await apiClient.getJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.branchesAndServices, token: appState.getUser.data!.accessToken, queryParameters: postParams);
return ProviderProfileModel.fromJson(adsGenericModel.data);
}
@ -490,7 +537,7 @@ class BranchRepoImp implements BranchRepo {
};
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(
(json) => GenericRespModel.fromJson(json),
(json) => GenericRespModel.fromJson(json),
ApiConsts.getAllNearBranches,
token: appState.getUser.data!.accessToken,
queryParameters: postParams,
@ -504,7 +551,7 @@ class BranchRepoImp implements BranchRepo {
var postParams = {"ServiceProviderBranchID": serviceProviderBranchID.toString()};
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(
(json) => GenericRespModel.fromJson(json),
(json) => GenericRespModel.fromJson(json),
ApiConsts.getBranchRatings,
token: appState.getUser.data!.accessToken,
queryParameters: postParams,
@ -518,7 +565,7 @@ class BranchRepoImp implements BranchRepo {
final customerID = appState.getUser.data!.userInfo!.customerId;
final parameters = {"title": title, "review": review, "ratNo": ratingNo, "serviceProviderBranchID": serviceProviderBranchID, "customerID": "$customerID"};
GenericRespModel adsGenericModel = await apiClient.postJsonForObject(
(json) => GenericRespModel.fromJson(json),
(json) => GenericRespModel.fromJson(json),
ApiConsts.createBranchRatings,
parameters,
token: appState.getUser.data!.accessToken,
@ -534,7 +581,7 @@ class BranchRepoImp implements BranchRepo {
"customerID": customerID.toString(),
};
GenericRespModel adsGenericModel = await apiClient.postJsonForObject(
(json) => GenericRespModel.fromJson(json),
(json) => GenericRespModel.fromJson(json),
ApiConsts.favouriteServiceProviderCreate,
parameters,
token: appState.getUser.data!.accessToken,
@ -546,7 +593,7 @@ class BranchRepoImp implements BranchRepo {
Future<GenericRespModel> removeProviderFromFavourite({required int providerID}) async {
final parameters = {"id": providerID.toString()};
GenericRespModel adsGenericModel = await apiClient.postJsonForObject(
(json) => GenericRespModel.fromJson(json),
(json) => GenericRespModel.fromJson(json),
ApiConsts.unFavouriteServiceProvider,
parameters,
token: appState.getUser.data!.accessToken,
@ -561,7 +608,7 @@ class BranchRepoImp implements BranchRepo {
var postParams = {"customerID": customerID.toString()};
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(
(json) => GenericRespModel.fromJson(json),
(json) => GenericRespModel.fromJson(json),
ApiConsts.favouriteServiceProviderGet,
token: appState.getUser.data!.accessToken,
queryParameters: postParams,

@ -48,7 +48,7 @@ abstract class CommonRepo {
Future<SSCarCheckScheduleModel> getCarCheckServiceScheduleDetails({required double lat, required double long});
Future<List<SSPhotoOfficeScheduleModel>> getPhotographyServiceScheduleListByOffices({required double lat, required double long});
Future<List<SSPhotoOfficeScheduleModel>> 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<VehicleDetailsModel> getVehicleDetails({int? vehicleTypeId, int? vehicleBrandId});
@ -97,10 +97,11 @@ class CommonRepoImp implements CommonRepo {
}
@override
Future<List<SSPhotoOfficeScheduleModel>> getPhotographyServiceScheduleListByOffices({required double lat, required double long}) async {
Future<List<SSPhotoOfficeScheduleModel>> 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,

@ -6,6 +6,7 @@ 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/main.dart';
import 'package:mc_common_app/models/chat_models/chat_message_model.dart';
import 'package:mc_common_app/models/general_models/generic_resp_model.dart';
import 'package:mc_common_app/models/requests_models/offers_model.dart';
import 'package:mc_common_app/models/requests_models/provider_offers_model.dart';
@ -54,6 +55,19 @@ abstract class RequestRepo {
Future<GenericRespModel> updateRequestStatus({required RequestStatusEnum requestStatusEnum, required int requestId});
Future<GenericRespModel> updateRequestOfferStatus({required RequestOfferStatusEnum requestOfferStatusEnum, required int requestOfferId, required String comments});
Future<GenericRespModel> updateRequestOfferFromProvider({
required String message,
required int requestId,
required int offerId,
required String offerPrice,
required bool isDeliveryAvailable,
required String serviceItemName,
required int manufacturedById,
required String manufacturedOn,
required List requestImages,
required RequestOfferStatusEnum requestOfferStatusEnum,
});
}
class RequestRepoImp implements RequestRepo {
@ -322,6 +336,44 @@ class RequestRepoImp implements RequestRepo {
"offerStatus": requestOfferStatusEnum.getIdFromRequestOfferStatusEnum().toString(),
"comments": comments,
};
GenericRespModel genericRespModel = await apiClient.postJsonForObject(
(json) => GenericRespModel.fromJson(json),
ApiConsts.updateRequestOfferStatus,
queryParameters,
token: appState.getUser.data!.accessToken,
);
return genericRespModel;
}
@override
Future<GenericRespModel> updateRequestOfferFromProvider({
required String message,
required int requestId,
required int offerId,
required String offerPrice,
required bool isDeliveryAvailable,
required String serviceItemName,
required int manufacturedById,
required String manufacturedOn,
required List requestImages,
required RequestOfferStatusEnum requestOfferStatusEnum,
}) async {
final providerId = appState.getUser.data!.userInfo!.providerId;
var queryParameters = {
"id": offerId,
"requestID": requestId,
"serviceProviderID": providerId,
"offerStatus": requestOfferStatusEnum.getIdFromRequestOfferStatusEnum(),
"serviceItem": serviceItemName,
"comment": message,
"price": offerPrice,
"offeredItemCreatedBy": manufacturedById.toString(),
"offeredItemCreatedOn": manufacturedOn.toString(),
"reqOfferImages": requestImages,
"isDeliveryAvailable": isDeliveryAvailable
};
GenericRespModel genericRespModel = await apiClient.postJsonForObject(
(json) => GenericRespModel.fromJson(json),
ApiConsts.updateRequestOffer,

@ -1,6 +1,8 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:mc_common_app/classes/app_state.dart';
import 'package:mc_common_app/classes/consts.dart';
@ -32,7 +34,6 @@ 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/dropdown/dropdow_field.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:provider/provider.dart';
class AdVM extends BaseVM {
@ -218,6 +219,13 @@ class AdVM extends BaseVM {
notifyListeners();
}
int currentPageForExploreAds = 1;
int currentPageForMyAds = 1;
bool isLoadingMore = false;
bool hasMoreData = true;
fetchMoreAds() async {}
Future<List<AdDetailsModel>> getAdsByFilter({AdPostStatus? adPostStatus, required bool isMyAds, CreatedByRoleEnum? createdByRoleEnum}) async {
return await adsRepo.getAllAds(isMyAds: isMyAds, adPostStatus: adPostStatus, createdByRoleEnum: createdByRoleEnum);
}
@ -1406,8 +1414,9 @@ class AdVM extends BaseVM {
Future<void> 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);
@ -1751,11 +1760,16 @@ class AdVM extends BaseVM {
if (vehicleAdCreatedDateSearchHistory.isEmpty) {
updateAdsFiltersCounter(adsFiltersCounter + 1);
}
vehicleAdCreatedDateSearchHistory.add(value);
if (vehicleAdCreatedDateSearchHistory.isEmpty) {
vehicleAdCreatedDateSearchHistory.add(value);
} else {
vehicleAdCreatedDateSearchHistory.first = value;
}
notifyListeners();
}
// Ad Condition
// Ad Condition
List<DropValue> vehicleAdConditionSearchHistory = [];
void removeVehicleAdConditionSearchHistory({bool isClear = false, required int index}) {

@ -1,6 +1,6 @@
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/app_state.dart';
import 'package:mc_common_app/classes/consts.dart';
@ -44,7 +44,6 @@ import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:mc_common_app/widgets/txt_field.dart';
import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart';
class AppointmentsVM extends BaseVM {
final CommonRepo commonRepo;
@ -252,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) {
@ -271,7 +259,7 @@ class AppointmentsVM extends BaseVM {
notifyListeners();
}
List<FilterListModel> branchesFilterOptions = [];
List<FilterListModel> branchesCategoriesFilterOptions = [];
List<ServiceModel> servicesInCurrentAppointment = [];
ServiceModel? currentServiceSelection;
@ -305,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;
@ -321,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();
}
@ -384,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) {
@ -424,10 +398,14 @@ class AppointmentsVM extends BaseVM {
// Create a list of CustomerData instances
myFilteredAppointments2 = uniqueCustomerIDs.map((id) {
List<AppointmentListModel> list = myFilteredAppointments.where((item) => item.customerID == id).toList();
list.sort((a, b) => DateHelper.parseStringToDate(DateHelper.formatDateT(b.appointmentDate!))
.millisecondsSinceEpoch
.compareTo(DateHelper.parseStringToDate(DateHelper.formatDateT(a.appointmentDate!)).millisecondsSinceEpoch));
AppointmentListModel model = list.first;
model.customerAppointmentList = list;
return model;
}).toList();
// customersAppointments = uniqueCustomerIDs.map((id) {
// List<AppointmentListModel> list = myFilteredAppointments
// .where((item) => item.customerID == id)
@ -467,13 +445,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();
}
@ -502,6 +482,7 @@ class AppointmentsVM extends BaseVM {
(element.appointmentStatusEnum == AppointmentStatusEnum.confirmed || element.appointmentStatusEnum == AppointmentStatusEnum.booked) &&
(DateHelper.parseStringToDate(element.appointmentDate!).isAfter(DateTime.now())))
.toList();
notifyListeners();
applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments, isNeedCustomerFilter: true);
setState(ViewState.idle);
}
@ -650,20 +631,20 @@ class AppointmentsVM extends BaseVM {
}
Future<void> applyFilterOnBranches({required int index}) async {
if (branchesFilterOptions.isEmpty) return;
for (var value in branchesFilterOptions) {
if (branchesCategoriesFilterOptions.isEmpty) return;
for (var value in branchesCategoriesFilterOptions) {
value.isSelected = false;
}
branchesFilterOptions[index].isSelected = true;
branchesCategoriesFilterOptions[index].isSelected = true;
await getBranchesBasedOnCategoryFilters(categoryId: branchesFilterOptions[index].id);
await getBranchesBasedOnCategoryFilters(categoryId: branchesCategoriesFilterOptions[index].id);
notifyListeners();
}
Future<List<ItemData>> getServiceItems(int serviceId) async {
Future<List<ItemData>> getServiceItems({required int serviceId}) async {
setState(ViewState.busy);
serviceItemsFromApi.clear();
serviceItemsFromApi = await branchRepo.getServiceItems(serviceId);
serviceItemsFromApi = await branchRepo.getServiceItems(serviceId: serviceId);
selectedSubServicesCounter = 0;
for (var item in serviceItemsFromApi) {
if (ifItemAlreadySelected(item.id!)) {
@ -1043,14 +1024,14 @@ class AppointmentsVM extends BaseVM {
List<ServiceModel> branchServices = [];
Future<void> populateBranchesFilterList() async {
if (branchesFilterOptions.isNotEmpty) return;
branchesFilterOptions.clear();
if (branchesCategoriesFilterOptions.isNotEmpty) return;
branchesCategoriesFilterOptions.clear();
setOnlyState(ViewState.busy);
Category category = await branchRepo.fetchBranchCategory();
category.data?.forEach((element) {
branchesFilterOptions.add(FilterListModel(id: element.id ?? 0, isSelected: false, title: element.categoryName ?? "N/A"));
branchesCategoriesFilterOptions.add(FilterListModel(id: element.id ?? 0, isSelected: false, iconUrl: element.serviceCategoryIconUrl ?? "", title: element.categoryName ?? "N/A"));
});
branchesFilterOptions.insert(0, FilterListModel(id: 0, isSelected: true, title: "All Branches"));
branchesCategoriesFilterOptions.insert(0, FilterListModel(id: 0, isSelected: true, title: "All Branches"));
notifyListeners();
setState(ViewState.idle);
}
@ -1060,7 +1041,7 @@ class AppointmentsVM extends BaseVM {
if (isNeedToRebuild) setState(ViewState.busy);
if (isFromRefresh) {
var selectedFilter = branchesFilterOptions.firstWhere((element) => element.isSelected);
var selectedFilter = branchesCategoriesFilterOptions.firstWhere((element) => element.isSelected);
log("selectedFilter: ${selectedFilter.id}");
if (selectedFilter.id == 0) {
@ -1301,8 +1282,9 @@ class AppointmentsVM extends BaseVM {
}
List<DropValue> categoryDropList = [];
List<DropValue> servicesDropList = [];
List<DropValue> providersDropList = [];
List<DropValue> servicesDropList = [];
List<DropValue> itemsDropList = [];
Future<void> fetchAllProviders() async {
if (providersDropList.isNotEmpty) return;
@ -1349,10 +1331,30 @@ class AppointmentsVM extends BaseVM {
setState(ViewState.idle);
}
Future<void> fetchAllItems() async {
if (itemsDropList.isNotEmpty) return;
itemsDropList.clear();
setState(ViewState.busy);
List<ItemData> itemsList = await branchRepo.getServiceItems(serviceId: -1); // to get all services
for (ItemData element in itemsList) {
if (element.name != null && element.name!.isNotEmpty) {
itemsDropList.add(
DropValue(
element.id ?? 0,
element.name ?? "",
"",
),
);
}
}
setState(ViewState.idle);
}
Future<void> populateDataForBranchesFilter() async {
await fetchAllProviders(); // saudi arabia
await fetchAllCategories(); // saudi arabia
await fetchAllServices(); // saudi arabia
await fetchAllProviders();
await fetchAllCategories();
await fetchAllServices();
updateBranchFilterCurrentDistance(25.0);
}
@ -1446,6 +1448,7 @@ class AppointmentsVM extends BaseVM {
await fetchAllProviders();
await fetchAllCategories();
await fetchAllServices();
await fetchAllItems();
}
int appointmentFiltersCounter = 0;
@ -1621,14 +1624,53 @@ class AppointmentsVM extends BaseVM {
notifyListeners();
}
List<DropValue> appointmentFilterItemsSearchHistory = [];
void removeAppointmentFilterItemsSearchHistory({bool isClear = false, required int index}) {
if (isClear) {
appointmentFilterItemsSearchHistory.clear();
notifyListeners();
return;
}
appointmentFilterItemsSearchHistory.removeAt(index);
if (appointmentFilterItemsSearchHistory.isEmpty) {
updateAppointmentFiltersCounter(appointmentFiltersCounter - 1);
// appointmentFilterSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
}
notifyListeners();
}
void addAppointmentFilterItemsSearchHistory({required DropValue value}) {
if (appointmentFilterItemsSearchHistory.isEmpty) {
updateAppointmentFiltersCounter(appointmentFiltersCounter + 1);
}
appointmentFilterItemsSearchHistory.add(value);
notifyListeners();
}
SelectionModel appointmentFilterSelectedItemId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
void updateAppointmentFilterSelectedItemId(SelectionModel id, {bool isForSearch = false}) async {
if (isForSearch) {
DropValue itemsDrop = itemsDropList.firstWhere((element) => element.id == id.selectedId);
if (!ifAlreadyExist(list: appointmentFilterItemsSearchHistory, value: itemsDrop)) {
addAppointmentFilterItemsSearchHistory(value: itemsDrop);
}
}
// appointmentFilterSelectedServiceId = id;
notifyListeners();
}
void clearAppointmentFilterSelections() {
appointmentFilterSelectedProviderId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
appointmentFilterSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
appointmentFilterSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
appointmentFilterSelectedItemId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
appointmentFilterSelectedBranchId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
}
void clearAppointmentFilters() {
appointmentFilterItemsSearchHistory.clear();
appointmentFilterServicesSearchHistory.clear();
appointmentFilterCategorySearchHistory.clear();
appointmentFilterProviderSearchHistory.clear();
@ -1659,6 +1701,12 @@ class AppointmentsVM extends BaseVM {
servicesIdsList.add(element.id.toString());
}
}
List<String> itemIdsList = [];
if (appointmentFilterItemsSearchHistory.isNotEmpty) {
for (var element in appointmentFilterItemsSearchHistory) {
itemIdsList.add(element.id.toString());
}
}
List<String> branchesIdsList = [];
if (appointmentFilterBranchSearchHistory.isNotEmpty) {
for (var element in appointmentFilterBranchSearchHistory) {
@ -1670,6 +1718,7 @@ class AppointmentsVM extends BaseVM {
providerIdsList: providersIdsList,
categoryIdsList: categoryIdsList,
serviceIdsList: servicesIdsList,
itemIdsList: itemIdsList,
branchIdsList: branchesIdsList,
);
applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments);

@ -4,6 +4,7 @@ import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:mc_common_app/classes/app_state.dart';
import 'package:mc_common_app/classes/consts.dart';
@ -23,7 +24,6 @@ import 'package:mc_common_app/view_models/base_view_model.dart';
import 'package:mc_common_app/view_models/requests_view_model.dart';
import 'package:provider/provider.dart';
import 'package:signalr_core/signalr_core.dart';
import 'package:easy_localization/easy_localization.dart';
class ChatVM extends BaseVM {
final ChatRepo chatRepo;
@ -76,9 +76,31 @@ class ChatVM extends BaseVM {
}
}
List<int> indexesForCancelSpecialCarOffer = [0, 1, 6, 7];
List<int> indexesForCancelSparePartOffer = [2, 7];
List<int> indexesForRejectOffer = [3, 4, 5, 7];
List<OfferRequestCommentModel> dealOptionsModelList = [
OfferRequestCommentModel(
index: 0,
isSelected: false,
title: LocaleKeys.dealCompleted.tr(),
),
OfferRequestCommentModel(
index: 1,
isSelected: false,
title: LocaleKeys.theDealNotCompleted.tr(),
),
];
updateIsSelectedInDealOptionsModelList(int index, bool value) {
for (var element in dealOptionsModelList) {
element.isSelected = false;
}
dealOptionsModelList[index].isSelected = true;
notifyListeners();
}
List<int> indexesForCancelSpecialCarOffer = [0, 1, 5, 6, 10];
List<int> indexesForCancelSparePartOffer = [2, 5, 6, 10];
List<int> indexesForRejectOffer = [3, 4, 10];
List<int> indexesForDealNotCompleted = [7, 4, 8, 9, 10];
List<OfferRequestCommentModel> offerRejectModelList = [
OfferRequestCommentModel(
@ -119,6 +141,21 @@ class ChatVM extends BaseVM {
OfferRequestCommentModel(
index: 7,
isSelected: false,
title: LocaleKeys.cancelRequest.tr(),
),
OfferRequestCommentModel(
index: 8,
isSelected: false,
title: LocaleKeys.offerNotMatched.tr(),
),
OfferRequestCommentModel(
index: 9,
isSelected: false,
title: LocaleKeys.testTheService.tr(),
),
OfferRequestCommentModel(
index: 10,
isSelected: false,
title: LocaleKeys.otherVar.tr(),
),
];

@ -3,6 +3,7 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:mc_common_app/classes/app_state.dart';
@ -215,6 +216,11 @@ class RequestsVM extends BaseVM {
List<ImageModel> pickedVehicleImages = [];
String vehicleImageError = "";
void addImageToPickedVehicleImages(ImageModel imageModel) {
pickedVehicleImages.add(imageModel);
notifyListeners();
}
void removeImageFromList(String filePath) {
int index = pickedVehicleImages.indexWhere((element) => element.filePath == filePath);
if (index == -1) {
@ -714,10 +720,17 @@ class RequestsVM extends BaseVM {
brandForSearch = v;
}
Future<RequestPostingImages> convertFileToRequestPostingImages({required File file}) async {
List<int> imageBytes = await file.readAsBytes();
String image = base64Encode(imageBytes);
RequestPostingImages vehiclePostingImages = RequestPostingImages(requestImage: image);
Future<RequestPostingImages> convertFileToRequestPostingImages({required ImageModel imageModel}) async {
RequestPostingImages vehiclePostingImages;
log("imageModelxx: ${imageModel.isFromNetwork}");
if (imageModel.isFromNetwork == true) {
vehiclePostingImages = RequestPostingImages(id: imageModel.id, requestImage: imageModel.filePath);
} else {
File file = File(imageModel.filePath!);
List<int> imageBytes = await file.readAsBytes();
String image = base64Encode(imageBytes);
vehiclePostingImages = RequestPostingImages(requestImage: image);
}
return vehiclePostingImages;
}
@ -725,7 +738,7 @@ class RequestsVM extends BaseVM {
List<RequestPostingImages> requestImages = [];
for (var image in pickedVehicleImages) {
var value = await convertFileToRequestPostingImages(file: File(image.filePath!));
var value = await convertFileToRequestPostingImages(imageModel: image);
requestImages.add(value);
}
@ -742,37 +755,37 @@ class RequestsVM extends BaseVM {
requestImages.add(element.toJson());
});
try {
GenericRespModel respModel = await requestRepo.createRequest(
requestTypeId: requestTypeId.selectedId,
vehicleTypeId: vehicleTypeId.selectedId,
brand: brand,
model: model,
year: vehicleYearId.selectedOption,
isNew: vehicleConditionId.selectedId == 1,
// 1 for new, 2 for Used
countryID: vehicleCountryId.selectedId,
cityID: vehicleCityId.selectedId,
price: price.isEmpty ? "1.0" : price,
description: description,
address: address,
isSpecialServiceNeeded: false,
requestImages: requestImages,
);
Utils.hideLoading(context);
if (respModel.messageStatus == 1) {
Utils.showToast(LocaleKeys.requestSuccessfullyCreated.tr());
Navigator.pop(context);
resetRequestCreationForm();
await applyFilterOnRequestsVM(requestsTypeEnum: requestTypeId.selectedId == 1 ? RequestsTypeEnum.specialCarRequest : RequestsTypeEnum.serviceRequest);
} else {
Utils.showToast(respModel.message.toString());
}
} catch (e, s) {
Utils.hideLoading(context);
log(e.toString());
Utils.showToast(e.toString());
// try {
GenericRespModel respModel = await requestRepo.createRequest(
requestTypeId: requestTypeId.selectedId,
vehicleTypeId: vehicleTypeId.selectedId,
brand: brand,
model: model,
year: vehicleYearId.selectedOption,
isNew: vehicleConditionId.selectedId == 1,
// 1 for new, 2 for Used
countryID: vehicleCountryId.selectedId,
cityID: vehicleCityId.selectedId,
price: price.isEmpty ? "1.0" : price,
description: description,
address: address,
isSpecialServiceNeeded: false,
requestImages: requestImages,
);
Utils.hideLoading(context);
if (respModel.messageStatus == 1) {
Utils.showToast(LocaleKeys.requestSuccessfullyCreated.tr());
Navigator.pop(context);
await applyFilterOnRequestsVM(requestsTypeEnum: requestTypeId.selectedId.toRequestTypeEnum());
resetRequestCreationForm();
} else {
Utils.showToast(respModel.message.toString());
}
// } catch (e, s) {
// Utils.hideLoading(context);
// log(e.toString());
// Utils.showToast(e.toString());
// }
}
}
@ -888,17 +901,23 @@ class RequestsVM extends BaseVM {
notifyListeners();
}
Future<bool> onActionRequestTapped({required BuildContext context, required RequestStatusEnum requestStatusEnum, required int requestId}) async {
Utils.showLoading(context);
Future<bool> onActionRequestTapped({required BuildContext context, required RequestStatusEnum requestStatusEnum, required int requestId, bool needLoading = true}) async {
if (needLoading) {
Utils.showLoading(context);
}
try {
GenericRespModel genericRespModel = await requestRepo.updateRequestStatus(requestStatusEnum: requestStatusEnum, requestId: requestId);
Utils.showToast(genericRespModel.message.toString());
Utils.hideLoading(context);
if (needLoading) {
Utils.hideLoading(context);
}
return genericRespModel.messageStatus == 1;
} catch (e) {
logger.i(e.toString());
Utils.showToast(e.toString());
Utils.hideLoading(context);
// Utils.showToast(e.toString());
if (needLoading) {
Utils.hideLoading(context);
}
return false;
}
}
@ -938,17 +957,52 @@ class RequestsVM extends BaseVM {
notifyListeners();
}
MessageImageModel convertFileToMessageImageModel({required File file, required int latestOfferId}) {
List<int> imageBytes = file.readAsBytesSync();
String image = base64Encode(imageBytes);
MessageImageModel vehiclePostingImages = MessageImageModel(imageStr: image, id: 0, isFromNetwork: false, reqOfferID: latestOfferId, imagePath: file.path);
return vehiclePostingImages;
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;
if (imageModel.isFromNetwork ?? false) {
offerImages = MessageImageModel(
id: imageModel.id,
imageStr: imageModel.filePath,
isFromNetwork: true,
reqOfferID: offerId,
);
} else {
File? file;
List<int> 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,
);
}
return offerImages;
}
List<MessageImageModel> getMessagesImageList({required int latestOfferId}) {
List<MessageImageModel> getMessagesImageList({required int offerId}) {
List<MessageImageModel> requestImages = [];
for (var image in pickedVehicleImages) {
var value = convertFileToMessageImageModel(file: File(image.filePath!), latestOfferId: latestOfferId);
var value = convertFileToMessageImageModel(imageModel: image, offerId: offerId);
requestImages.add(value);
}
return requestImages;
@ -961,6 +1015,66 @@ class RequestsVM extends BaseVM {
notifyListeners();
}
Future<void> updateOfferFromProvider({
required String message,
required int requestId,
required int offerId,
required String offerPrice,
required bool isDeliveryAvailable,
required String serviceItemName,
required int manufacturedById,
required String manufacturedOn,
required int receiverId,
required int customerRequestIndex,
required BuildContext context,
}) async {
try {
if (isSendOfferValidated()) {
Utils.showLoading(context);
List<MessageImageModel> images = getMessagesImageList(offerId: offerId);
List offerImages = [];
images.forEach((element) {
offerImages.add(element.toJson());
});
GenericRespModel genericRespModel = await requestRepo.updateRequestOfferFromProvider(
message: message,
requestId: requestId,
offerId: offerId,
offerPrice: offerPrice,
isDeliveryAvailable: isDeliveryAvailable,
serviceItemName: serviceItemName,
manufacturedById: manufacturedById,
manufacturedOn: manufacturedOn,
requestImages: offerImages,
requestOfferStatusEnum: RequestOfferStatusEnum.offer,
);
Utils.hideLoading(context);
if (genericRespModel.messageStatus == 1) {
Utils.showToast(genericRespModel.message ?? "");
final chatVM = context.read<ChatVM>();
await chatVM.getRequestsChatMessagesForProvider(
context: context,
customerId: receiverId,
requestOfferId: 0,
// to get all the messages
requestId: requestId,
customerRequestIndex: customerRequestIndex,
);
resetSendOfferBottomSheet();
pop(context);
}
}
} catch (e, s) {
Utils.hideLoading(context);
log(e.toString());
Utils.showToast(e.toString());
}
}
Future<void> onSendOfferPressed({
required BuildContext context,
required String receiverId,
@ -974,10 +1088,11 @@ class RequestsVM extends BaseVM {
required bool isDeliveryAvailable,
required int requestIndex,
required bool isFromChatScreen,
required int? offerId,
}) async {
if (isSendOfferValidated()) {
final chatVM = context.read<ChatVM>();
List<MessageImageModel> images = getMessagesImageList(latestOfferId: context.read<ChatVM>().latestOfferId);
List<MessageImageModel> images = getMessagesImageList(offerId: context.read<ChatVM>().latestOfferId);
List offerImages = [];
images.forEach((element) {
offerImages.add(element.toJson());

@ -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;
@ -160,6 +168,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,
@ -527,6 +536,92 @@ class ServiceVM extends BaseVM {
return await branchRepo.updateService(map);
}
Future<List<AppointmentBasicDetailsModel>> getAppointmentsByServiceID({required BuildContext context, required int branchId, required int serviceId}) async {
try {
Utils.showLoading(context);
List<AppointmentBasicDetailsModel> 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<bool> updateServiceStatus({required BuildContext context, required ServiceStatusEnum serviceStatusEnum, required int branchId, required List<int> 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<AppointmentBasicDetailsModel> 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();
@ -535,7 +630,7 @@ class ServiceVM extends BaseVM {
List<ServiceModel>? matchedServices;
bool isAllSelected = false;
Future<void> getAllMatchedServices(int oldBranchId, int newBranchId, int categoryId) async {
Future<void> getAllMatchedServices({required int oldBranchId, required int newBranchId, required int categoryId}) async {
matchedServices = null;
final GenericRespModel response = await branchRepo.getMatchedServices(oldBranchId, newBranchId, categoryId);
matchedServices = [];

@ -293,24 +293,24 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget {
20.height,
adVM.state == ViewState.busy
? const Center(child: CircularProgressIndicator())
: Builder(
builder: (context) {
List<DropValue> 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<DropValue> 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(

@ -117,19 +117,21 @@ class _AdsFilterViewState extends State<AdsFilterView> {
const Divider(thickness: 1.2).paddingOnly(top: 7, bottom: 7),
SearchEntityWidget(
title: LocaleKeys.searchByVehicleYear.tr(),
actionWidget: Builder(builder: (context) {
List<DropValue> vehicleBrandsDrop = [];
for (var element in adVM.vehicleModelYears) {
vehicleBrandsDrop.add(DropValue(element.id?.toInt() ?? 0, element.modelYear ?? "", ""));
}
return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleModelYearId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true),
list: vehicleBrandsDrop,
dropdownValue: adVM.vehicleModelYearId.selectedId != -1 ? DropValue(adVM.vehicleModelYearId.selectedId, adVM.vehicleModelYearId.selectedOption, "") : null,
hint: LocaleKeys.selectYear.tr(),
errorValue: adVM.vehicleModelYearId.errorValue,
);
}),
actionWidget: Builder(
builder: (context) {
List<DropValue> vehicleBrandsDrop = [];
for (var element in adVM.vehicleModelYears) {
vehicleBrandsDrop.add(DropValue(element.id?.toInt() ?? 0, element.modelYear ?? "", ""));
}
return DropdownField(
(DropValue value) => adVM.updateSelectionVehicleModelYearId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true),
list: vehicleBrandsDrop,
dropdownValue: adVM.vehicleModelYearId.selectedId != -1 ? DropValue(adVM.vehicleModelYearId.selectedId, adVM.vehicleModelYearId.selectedOption, "") : null,
hint: LocaleKeys.selectYear.tr(),
errorValue: adVM.vehicleModelYearId.errorValue,
);
},
),
historyContent: adVM.vehicleYearAdSearchHistory,
onHistoryItemDeleted: (index) => adVM.removeVehicleYearAdSearchHistory(index: index),
onHistoryItemTapped: (DropValue value) => null,
@ -167,7 +169,10 @@ class _AdsFilterViewState extends State<AdsFilterView> {
isNeedClickAll: true,
postFixDataColor: MyColors.darkTextColor,
onTap: () async {
final formattedDate = await Utils.pickDateFromDatePicker(context);
final formattedDate = await Utils.pickDateFromDatePicker(context, firstDate: DateTime(2020), lastDate: DateTime.now());
if (formattedDate.isEmpty) {
return;
}
adVM.updateSelectionDurationStartDate(formattedDate);
DropValue value = DropValue(0, formattedDate, "");
adVM.addToVehicleAdCreatedDateSearchHistory(value: value);

@ -1,11 +1,11 @@
import 'dart:developer';
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/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/main.dart';
import 'package:mc_common_app/models/advertisment_models/ads_duration_model.dart';
import 'package:mc_common_app/models/general_models/widgets_models.dart';
import 'package:mc_common_app/theme/colors.dart';
@ -15,7 +15,6 @@ import 'package:mc_common_app/view_models/ad_view_model.dart';
import 'package:mc_common_app/widgets/button/show_fill_button.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart';
class AdDurationSelectionSheet extends StatelessWidget {
final bool isFromExtendAd;
@ -66,17 +65,54 @@ class AdDurationSelectionSheet extends StatelessWidget {
),
12.height,
LocaleKeys.adCharges.tr().toText(fontSize: 14, color: MyColors.lightTextColor, isBold: true),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
"${adDuration.price}".toText(fontSize: 22, isBold: true),
2.width,
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: (adDuration.currency ?? LocaleKeys.sar.tr()).toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
),
],
),
if (adDuration.discountPercent != null && adDuration.discountPercent! > 0.0) ...[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
CustomPaint(
painter: DiagonalLinePainter(),
child: "${adDuration.price}".toText(
fontSize: 20,
isBold: true,
)),
2.width,
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: (adDuration.currency ?? LocaleKeys.sar.tr()).toText(fontSize: 10, color: MyColors.lightTextColor, isBold: true),
),
14.width,
"${adDuration.priceAfterDiscount}".toText(fontSize: 20, isBold: true),
2.width,
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: (adDuration.currency ?? LocaleKeys.sar.tr()).toText(fontSize: 10, color: MyColors.lightTextColor, isBold: true),
),
],
),
"${adDuration.discountPercent} % OFF".toText(color: MyColors.white, fontSize: 10).toContainer(
marginAll: 5,
paddingAll: 5,
borderRadius: 4,
backgroundColor: MyColors.greenColor,
),
],
),
] else ...[
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
"${adDuration.price}".toText(fontSize: 22, isBold: true),
2.width,
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: (adDuration.currency ?? LocaleKeys.sar.tr()).toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
),
],
),
]
],
),
),
@ -92,7 +128,7 @@ class AdDurationSelectionSheet extends StatelessWidget {
SelectionModel(
selectedId: adDuration.id ?? 0,
selectedOption: ("${adDuration.days} ${LocaleKeys.daysVar.tr()}"),
itemPrice: adDuration.price!.toInt().toString(),
itemPrice: adDuration.priceAfterDiscount!.toInt().toString(),
),
);
return;
@ -101,7 +137,7 @@ class AdDurationSelectionSheet extends StatelessWidget {
SelectionModel(
selectedId: adDuration.id ?? 0,
selectedOption: ("${adDuration.days} ${LocaleKeys.daysVar.tr()}"),
itemPrice: adDuration.price!.toString(),
itemPrice: adDuration.priceAfterDiscount!.toString(),
currency: adDuration.currency!.toString(),
),
);
@ -160,3 +196,20 @@ class AdDurationSelectionSheet extends StatelessWidget {
);
}
}
class DiagonalLinePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..color = MyColors.redColor
..strokeWidth = 2.5;
// Draw the diagonal line from top-left to bottom-right
canvas.drawLine(const Offset(0, 0), Offset(size.width, size.height), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

@ -1,3 +1,6 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mc_common_app/classes/consts.dart';
@ -12,21 +15,22 @@ import 'package:mc_common_app/utils/utils.dart';
import 'package:mc_common_app/view_models/ad_view_model.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart';
class AdsListWidget extends StatelessWidget {
final List<AdDetailsModel> adsList;
final ScrollPhysics? scrollPhysics;
final bool isAdsFragment;
final bool shouldShowAdStatus;
final Function()? onFetchMoreAds;
const AdsListWidget({
Key? key,
super.key,
required this.adsList,
this.scrollPhysics,
required this.shouldShowAdStatus,
this.onFetchMoreAds,
this.scrollPhysics,
this.isAdsFragment = false,
}) : super(key: key);
});
@override
Widget build(BuildContext context) {
@ -38,21 +42,31 @@ class AdsListWidget extends StatelessWidget {
],
);
}
return ListView.separated(
itemCount: adsList.length,
shrinkWrap: true,
physics: scrollPhysics,
itemBuilder: (BuildContext context, int index) {
return AdCard(
adDetails: adsList[index],
isAdsFragment: isAdsFragment,
shouldShowAdStatus: shouldShowAdStatus,
).onPress(() => navigateWithName(context, AppRoutes.adsDetailView, arguments: adsList[index]));
},
separatorBuilder: (BuildContext context, int index) {
return 12.height;
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && context.read<AdVM>().hasMoreData) {
if (onFetchMoreAds != null) {
onFetchMoreAds!();
}
}
return false;
},
padding: const EdgeInsets.symmetric(horizontal: 21),
child: ListView.separated(
itemCount: adsList.length,
shrinkWrap: true,
physics: scrollPhysics,
itemBuilder: (BuildContext context, int index) {
return AdCard(
adDetails: adsList[index],
isAdsFragment: isAdsFragment,
shouldShowAdStatus: shouldShowAdStatus,
).onPress(() => navigateWithName(context, AppRoutes.adsDetailView, arguments: adsList[index]));
},
separatorBuilder: (BuildContext context, int index) {
return 12.height;
},
padding: const EdgeInsets.symmetric(horizontal: 21),
),
);
}
}
@ -62,7 +76,7 @@ class AdCard extends StatelessWidget {
final bool isAdsFragment;
final bool shouldShowAdStatus;
const AdCard({Key? key, required this.adDetails, required this.isAdsFragment, required this.shouldShowAdStatus}) : super(key: key);
const AdCard({super.key, required this.adDetails, required this.isAdsFragment, required this.shouldShowAdStatus});
@override
Widget build(BuildContext context) {

@ -24,7 +24,7 @@ class _DamagePicturesListState extends State<DamagePicturesList> {
@override
void initState() {
super.initState();
context.read<AppointmentsVM>().getServiceItems(widget.serviceId);
context.read<AppointmentsVM>().getServiceItems(serviceId: widget.serviceId);
}
@override

@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/consts.dart';
@ -18,7 +19,7 @@ class PickedFilesContainer extends StatelessWidget {
final bool allowAdButton;
const PickedFilesContainer({
Key? key,
super.key,
required this.pickedFiles,
this.onCrossPressedPrimary,
this.onCrossPressedSecondary,
@ -28,7 +29,7 @@ class PickedFilesContainer extends StatelessWidget {
this.isPdf = false,
this.isFromNetwork = false,
this.allowAdButton = true,
}) : super(key: key);
});
@override
Widget build(BuildContext context) {
@ -82,17 +83,29 @@ class BuildFilesContainer extends StatelessWidget {
final bool isPdf;
const BuildFilesContainer({
Key? key,
super.key,
required this.image,
this.onCrossPressedPrimary,
this.onCrossPressedSecondary,
this.index,
this.isReview = false,
this.isPdf = false,
}) : super(key: key);
});
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(
@ -120,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,

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/consts.dart';
import 'package:mc_common_app/extensions/int_extensions.dart';
@ -13,7 +14,6 @@ import 'package:mc_common_app/view_models/appointments_view_model.dart';
import 'package:mc_common_app/widgets/button/show_fill_button.dart';
import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/common_widgets/search_entity_widget.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart';
@ -33,14 +33,14 @@ class _AppointmentsFilterViewState extends State<AppointmentsFilterView> {
void initState() {
appointmentsVM = context.read<AppointmentsVM>();
scheduleMicrotask(() async {
await appointmentsVM.populateDataForBranchesFilter();
await appointmentsVM.populateDataForAppointmentsFilter();
});
super.initState();
}
@override
void dispose() {
appointmentsVM.clearBranchFilterSelections();
appointmentsVM.clearAppointmentFilterSelections();
super.dispose();
}
@ -96,7 +96,7 @@ class _AppointmentsFilterViewState extends State<AppointmentsFilterView> {
children: [
SizedBox(
height: 5.h,
width: 5.h,
width: 5.h,
child: const CircularProgressIndicator(),
),
],
@ -165,6 +165,23 @@ class _AppointmentsFilterViewState extends State<AppointmentsFilterView> {
onHistoryItemDeleted: (index) => appointmentsVM.removeAppointmentFilterServicesSearchHistory(index: index),
onHistoryItemTapped: (DropValue value) => null,
),
const Divider(thickness: 1.2).paddingOnly(top: 5, bottom: 5),
SearchEntityWidget(
title: LocaleKeys.searchByItem.tr(),
actionWidget: Builder(builder: (context) {
return DropdownField(
(DropValue value) => appointmentsVM.updateAppointmentFilterSelectedItemId(SelectionModel(selectedId: value.id, selectedOption: value.value), isForSearch: true),
list: appointmentsVM.itemsDropList,
dropdownValue: appointmentsVM.appointmentFilterSelectedItemId.selectedId != -1
? DropValue(appointmentsVM.appointmentFilterSelectedItemId.selectedId, appointmentsVM.appointmentFilterSelectedItemId.selectedOption, "")
: null,
hint: LocaleKeys.selectItems.tr(),
);
}),
historyContent: appointmentsVM.appointmentFilterItemsSearchHistory,
onHistoryItemDeleted: (index) => appointmentsVM.removeAppointmentFilterItemsSearchHistory(index: index),
onHistoryItemTapped: (DropValue value) => null,
),
],
).expand(),
Container(
@ -193,10 +210,10 @@ class _AppointmentsFilterViewState extends State<AppointmentsFilterView> {
InkWell(
onTap: () => appointmentsVM.clearAppointmentFilters(),
child: LocaleKeys.clearFilters.tr().toText(
fontSize: 14,
isBold: true,
color: MyColors.darkPrimaryColor,
),
fontSize: 14,
isBold: true,
color: MyColors.darkPrimaryColor,
),
),
10.height,
],

@ -1,5 +1,4 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/consts.dart';
import 'package:mc_common_app/config/routes.dart';
@ -17,7 +16,6 @@ import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/common_widgets/custom_add_button_widget.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart';
class BookAppointmentServicesView extends StatelessWidget {
const BookAppointmentServicesView({super.key});
@ -73,7 +71,7 @@ class BookAppointmentServicesView extends StatelessWidget {
itemPrice: "",
),
);
appointmentsVM.getServiceItems(serviceData.serviceProviderServiceId!);
appointmentsVM.getServiceItems(serviceId: serviceData.serviceProviderServiceId!);
navigateWithName(context, AppRoutes.bookAppointmentsItemView);
}),
10.width,

@ -238,7 +238,7 @@ class AppointmentServicePickBottomSheet extends StatelessWidget {
onPressed: () {
bool isValidated = appointmentsVM.isServiceSelectionValidated();
if (isValidated) {
appointmentsVM.getServiceItems(appointmentsVM.branchSelectedServiceId.selectedId);
appointmentsVM.getServiceItems(serviceId: appointmentsVM.branchSelectedServiceId.selectedId);
Navigator.pop(context);
navigateWithName(context, AppRoutes.bookAppointmentsItemView);
}

@ -26,7 +26,7 @@ import 'package:sizer/sizer.dart';
class CommonAppointmentSliderWidget extends StatelessWidget {
final Function(AppointmentListModel)? onAppointmentClick;
const CommonAppointmentSliderWidget({Key? key, this.onAppointmentClick}) : super(key: key);
const CommonAppointmentSliderWidget({super.key, this.onAppointmentClick});
Widget getCarouselSliderWidget(AppType appType, AppointmentsVM appointmentsVM) {
return CarouselSlider.builder(
@ -95,7 +95,7 @@ class AppointmentHomeTileWidget extends StatelessWidget {
final AppointmentListModel? appointmentListModel;
final Function()? onTapped;
const AppointmentHomeTileWidget({Key? key, this.isForProvider = false, this.onTapped, required this.appointmentListModel, this.isForCustomerHome = false}) : super(key: key);
const AppointmentHomeTileWidget({super.key, this.isForProvider = false, this.onTapped, required this.appointmentListModel, this.isForCustomerHome = false});
Widget showServices(String title, String icon, {bool isMoreText = false}) {
return Row(
@ -227,7 +227,7 @@ class AppointmentFragmentTileWidget extends StatelessWidget {
final AppointmentListModel? appointmentListModel;
final Function()? onTapped;
const AppointmentFragmentTileWidget({Key? key, this.onTapped, required this.appointmentListModel}) : super(key: key);
const AppointmentFragmentTileWidget({super.key, this.onTapped, required this.appointmentListModel});
Widget showServices(String title, String icon, {bool isMoreText = false}) {
return Row(

@ -1,10 +1,6 @@
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:easy_localization/easy_localization.dart' as lcl;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
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/extensions/int_extensions.dart';
import 'package:mc_common_app/extensions/string_extensions.dart';
@ -21,14 +17,11 @@ import 'package:mc_common_app/views/advertisement/components/picked_images_conta
import 'package:mc_common_app/views/chat/widgets/chat_bottom_sheets.dart';
import 'package:mc_common_app/views/chat/widgets/chat_message_widget.dart';
import 'package:mc_common_app/views/requests/request_bottomsheets.dart';
import 'package:mc_common_app/views/setting_options/widgets/custom_setting_options_tile.dart';
import 'package:mc_common_app/widgets/button/show_fill_button.dart';
import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/common_widgets/info_bottom_sheet.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:mc_common_app/widgets/txt_field.dart';
import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart' as lcl;
class ChatView extends StatefulWidget {
final ChatViewArguments chatViewArguments;
@ -165,6 +158,7 @@ class _ChatViewState extends State<ChatView> {
ChatMessageModel chatMessageModel = chatMessages[index];
return ChatMessageCustomWidget(
chatMessageModel: chatMessageModel,
requestModel: chatTypeEnum == ChatTypeEnum.requestOffer ? chatViewArgumentsForRequest!.requestModel! : null,
requestStatusEnum: requestVM.currentSelectedRequest?.requestStatus,
chatTypeEnum: chatTypeEnum,
requestsTypeEnum: chatTypeEnum == ChatTypeEnum.requestOffer ? chatViewArgumentsForRequest!.requestModel!.requestType.toRequestTypeEnum() : RequestsTypeEnum.specialCarRequest,
@ -193,32 +187,32 @@ class _ChatViewState extends State<ChatView> {
},
),
),
// Center(
// child: LocaleKeys.requestAlreadyInProgress.tr().toText(
// textAlign: TextAlign.center,
// color: MyColors.lightTextColor,
// fontSize: 14,
// ),
// ).paddingAll(15)
] else if (chatTypeEnum == ChatTypeEnum.requestOffer &&
requestVM.currentSelectedRequest!.requestType.toRequestTypeEnum() == RequestsTypeEnum.specialCarRequest &&
requestVM.currentSelectedRequest!.requestStatus == RequestStatusEnum.inProgress &&
requestVM.currentSelectedOffer!.requestOfferStatusEnum == RequestOfferStatusEnum.accepted &&
AppState().currentAppType == AppType.customer) ...[
Expanded(
child: ShowFillButton(
maxWidth: double.infinity,
margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
maxHeight: 55,
title: LocaleKeys.completeDeal.tr(),
isBold: false,
onPressed: () {
return dealCompletedConfirmationBottomSheet(mainContext: context, requestStatusEnum: RequestStatusEnum.completed, requestId: requestVM.currentSelectedRequest!.id);
},
),
),
] else ...[
if (AppState().currentAppType == AppType.provider && chatTypeEnum == ChatTypeEnum.requestOffer && requestVM.currentSelectedRequest!.requestStatus == RequestStatusEnum.submitted && chatVM.pickedImagesForMessage.isEmpty) ...[
]
// else if (chatTypeEnum == ChatTypeEnum.requestOffer &&
// requestVM.currentSelectedRequest!.requestType.toRequestTypeEnum() == RequestsTypeEnum.specialCarRequest &&
// requestVM.currentSelectedRequest!.requestStatus == RequestStatusEnum.inProgress &&
// requestVM.currentSelectedOffer!.requestOfferStatusEnum == RequestOfferStatusEnum.accepted &&
// AppState().currentAppType == AppType.customer) ...[
// Expanded(
// child: ShowFillButton(
// maxWidth: double.infinity,
// margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
// maxHeight: 55,
// title: LocaleKeys.completeDeal.tr(),
// isBold: false,
// onPressed: () {
// return dealCompletedConfirmationBottomSheet(mainContext: context, requestStatusEnum: RequestStatusEnum.completed, requestId: requestVM.currentSelectedRequest!.id);
// },
// ),
// ),
// ]
//
else ...[
if (AppState().currentAppType == AppType.provider &&
chatTypeEnum == ChatTypeEnum.requestOffer &&
requestVM.currentSelectedRequest!.requestStatus == RequestStatusEnum.submitted &&
chatVM.pickedImagesForMessage.isEmpty) ...[
Expanded(
flex: 1,
child: const Icon(
@ -227,8 +221,17 @@ class _ChatViewState extends State<ChatView> {
size: 30,
).onPress(
() {
RequestDetailPageArguments requestDetailArguments = RequestDetailPageArguments(requestIndex: chatViewArgumentsForRequest!.requestIndex, requestModel: chatViewArgumentsForRequest!.requestModel!);
buildSendOfferBottomSheet(context: context, requestDetailPageArguments: requestDetailArguments, isFromChatScreen: true);
requestVM.resetSendOfferBottomSheet();
RequestDetailPageArguments requestDetailArguments = RequestDetailPageArguments(
requestIndex: chatViewArgumentsForRequest!.requestIndex,
requestModel: chatViewArgumentsForRequest!.requestModel!,
);
buildSendOfferBottomSheet(
context: context,
requestDetailPageArguments: requestDetailArguments,
isFromChatScreen: true,
offerId: null, // null means creating new offer
);
},
),
),

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
@ -9,7 +11,6 @@ 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/view_models/base_view_model.dart';
import 'package:mc_common_app/view_models/chat_view_model.dart';
import 'package:mc_common_app/view_models/dashboard_view_model_customer.dart';
import 'package:mc_common_app/view_models/requests_view_model.dart';
@ -17,43 +18,79 @@ import 'package:mc_common_app/widgets/button/show_fill_button.dart';
import 'package:mc_common_app/widgets/checkbox_with_title_desc.dart';
import 'package:provider/provider.dart';
void dealCompletedConfirmationBottomSheet({required BuildContext mainContext, required RequestStatusEnum requestStatusEnum, required int requestId}) {
void dealCompletedConsentBottomSheet({required BuildContext mainContext, required RequestStatusEnum requestStatusEnum, required int requestId, Function()? acceptRequestOffer}) {
final requestVM = mainContext.read<RequestsVM>();
return actionConfirmationBottomSheet(
isOnlyOneButton: true,
isOnlyOneButton: AppState().currentAppType == AppType.customer,
context: mainContext,
title: LocaleKeys.doYouWantToCompleteThisDeal.tr().toText(fontSize: 26, isBold: true, letterSpacing: -1.44),
subtitle: AppState().currentAppType == AppType.provider ? LocaleKeys.providerCompletingDealMeansThat.tr() : LocaleKeys.customerCompletingDealMeansThat.tr(),
checkBoxConfirmationWidget: Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) {
return Row(
children: [
Expanded(
child: CircleCheckBoxWithTitle(
isChecked: chatVM.acknowledgePaymentToMowaterStatus,
title: LocaleKeys.acknowledgePaymentToMowater.tr(),
onSelected: () {
chatVM.updateAcknowledgePaymentToMowaterStatus(!chatVM.acknowledgePaymentToMowaterStatus);
},
selectedColor: MyColors.darkPrimaryColor,
),
),
],
);
}),
checkBoxConfirmationWidget: AppState().currentAppType == AppType.customer
? Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) {
return Row(
children: [
Expanded(
child: CircleCheckBoxWithTitle(
isChecked: chatVM.acknowledgePaymentToMowaterStatus,
title: LocaleKeys.acknowledgePaymentToMowater.tr(),
onSelected: () {
chatVM.updateAcknowledgePaymentToMowaterStatus(!chatVM.acknowledgePaymentToMowaterStatus);
},
selectedColor: MyColors.darkPrimaryColor,
),
),
],
);
})
: null,
actionButtonYes: Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) {
return Expanded(
child: ShowFillButton(
maxHeight: 55,
isDisabled: !chatVM.acknowledgePaymentToMowaterStatus,
title: LocaleKeys.submit.tr(),
title: AppState().currentAppType == AppType.customer ? LocaleKeys.submit.tr() : LocaleKeys.yes.tr(),
fontSize: 15,
onPressed: () async {
Navigator.pop(context);
bool status = await requestVM.onActionRequestTapped(context: mainContext, requestStatusEnum: requestStatusEnum, requestId: requestId);
if (status) {
chatVM.updateAcknowledgePaymentToMowaterStatus(false);
mainContext.read<DashboardVmCustomer>().onNavbarTapped(4);
navigateReplaceWithName(mainContext, AppRoutes.dashboard);
pop(context);
bool statusForReqAccept = false;
if (acceptRequestOffer != null) {
statusForReqAccept = await acceptRequestOffer();
if (statusForReqAccept) {
bool status = await requestVM.onActionRequestTapped(
context: mainContext,
requestStatusEnum: requestStatusEnum,
requestId: requestId,
needLoading: false,
);
log("status: $status");
if (status) {
int index = requestVM.myFilteredRequests.indexWhere((element) => element.id == requestId);
if (index != -1) {
requestVM.myFilteredRequests[index].requestStatus = requestStatusEnum;
}
chatVM.updateAcknowledgePaymentToMowaterStatus(false);
mainContext.read<DashboardVmCustomer>().onNavbarTapped(4);
navigateReplaceWithNameUntilRoute(mainContext, AppRoutes.dashboard);
}
}
} else {
bool status = await requestVM.onActionRequestTapped(
context: mainContext,
requestStatusEnum: requestStatusEnum,
requestId: requestId,
needLoading: false,
);
log("status: $status");
if (status) {
int index = requestVM.myFilteredRequests.indexWhere((element) => element.id == requestId);
if (index != -1) {
requestVM.myFilteredRequests[index].requestStatus = requestStatusEnum;
}
chatVM.updateAcknowledgePaymentToMowaterStatus(false);
mainContext.read<DashboardVmCustomer>().onNavbarTapped(4);
navigateReplaceWithNameUntilRoute(mainContext, AppRoutes.dashboard);
}
}
},
),

@ -1,6 +1,7 @@
import 'dart:developer';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:easy_localization/easy_localization.dart' as lcl;
import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/app_state.dart';
import 'package:mc_common_app/classes/consts.dart';
@ -9,34 +10,265 @@ 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/chat_models/chat_message_model.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/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/chat_view_model.dart';
import 'package:mc_common_app/view_models/requests_view_model.dart';
import 'package:mc_common_app/views/chat/widgets/chat_bottom_sheets.dart';
import 'package:mc_common_app/views/requests/request_bottomsheets.dart';
import 'package:mc_common_app/widgets/button/show_fill_button.dart';
import 'package:mc_common_app/widgets/checkbox_with_title_desc.dart';
import 'package:mc_common_app/widgets/common_widgets/info_bottom_sheet.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:mc_common_app/widgets/txt_field.dart';
import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart' as lcl;
class ChatMessageCustomWidget extends StatefulWidget {
final ChatMessageModel chatMessageModel;
final RequestStatusEnum? requestStatusEnum;
final ChatTypeEnum chatTypeEnum;
final RequestsTypeEnum requestsTypeEnum;
final RequestModel? requestModel;
const ChatMessageCustomWidget({super.key, required this.chatMessageModel, required this.requestStatusEnum, required this.chatTypeEnum, this.requestsTypeEnum = RequestsTypeEnum.specialCarRequest});
const ChatMessageCustomWidget({
super.key,
required this.chatMessageModel,
required this.requestStatusEnum,
required this.chatTypeEnum,
this.requestModel,
this.requestsTypeEnum = RequestsTypeEnum.specialCarRequest,
});
@override
State<ChatMessageCustomWidget> createState() => _ChatMessageCustomWidgetState();
}
class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
Future buildDealNotCompletedBottomSheetOptions({required ChatMessageModel chatMessageModel}) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: true,
builder: (BuildContext context) {
return Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) {
return InfoBottomSheet(
title: LocaleKeys.pleaseSpecify.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44),
description: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
12.height,
ListView.separated(
shrinkWrap: true,
itemCount: chatVM.offerRejectModelList.length,
separatorBuilder: (BuildContext context, int index) {
bool indexContains = chatVM.indexesForDealNotCompleted.indexWhere((i) => i == index) != -1;
if ((!indexContains)) {
return const SizedBox();
}
return const Divider(thickness: 0.5);
},
itemBuilder: (BuildContext context, int index) {
bool indexContains = chatVM.indexesForDealNotCompleted.indexWhere((i) => i == index) != -1;
if (!indexContains) {
return const SizedBox();
}
OfferRequestCommentModel offerRequestCommentModel = chatVM.offerRejectModelList[index];
return CircleCheckBoxWithTitle(
isChecked: offerRequestCommentModel.isSelected ?? false,
title: '${offerRequestCommentModel.title}',
onSelected: () {
chatVM.updateSelectionInOfferRejectModelList(index);
},
selectedColor: MyColors.darkPrimaryColor,
);
},
),
if (chatVM.offerRejectModelList.last.isSelected ?? false) ...[
// comparing if the "other" is selected
12.height,
TxtField(
maxLines: 5,
value: chatVM.rejectOfferDescription,
errorValue: chatVM.rejectOfferDescriptionError,
keyboardType: TextInputType.text,
hint: LocaleKeys.description.tr(),
onChanged: (v) => chatVM.updateRejectOfferDescription(v),
),
],
25.height,
ShowFillButton(
title: LocaleKeys.submit.tr(),
onPressed: () async {
String comments = "";
if (chatVM.offerRejectModelList.last.isSelected ?? false) {
comments = chatVM.rejectOfferDescription;
} else {
comments = chatVM.offerRejectModelList.firstWhere((element) => element.isSelected!).title ?? "";
}
if (!chatVM.isRejectOfferButtonValidated()) {
return;
}
Navigator.pop(context);
bool status = await chatVM.onSendMessageForActionOnRequestOffer(
receiverId: (chatMessageModel.isMyMessage ?? false) ? chatMessageModel.receiverUserID ?? "" : chatMessageModel.senderUserID ?? "",
chatMessageType: ChatMessageTypeEnum.offer,
comments: comments,
requestId: chatMessageModel.reqOffer!.requestID ?? -1,
serviceProviderID: chatMessageModel.serviceProviderID ?? 0,
requestOfferID: chatMessageModel.reqOffer!.id ?? -1,
offerPrice: chatMessageModel.reqOffer!.price.toString(),
serviceItemName: chatMessageModel.reqOffer!.serviceItemName ?? "",
manufacturedOn: chatMessageModel.reqOffer!.manufacturedOn ?? "",
manufacturedById: chatMessageModel.reqOffer!.manufacturedById ?? 0,
requestOfferStatusEnum: RequestOfferStatusEnum.rejected,
context: context,
);
if (status) {
final requestVM = context.read<RequestsVM>();
chatMessageModel.reqOffer!.requestOfferStatusEnum = RequestOfferStatusEnum.rejected;
int index = chatVM.serviceProviderOffersList
.indexWhere((element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId));
if (index != -1) {
chatVM.serviceProviderOffersList[index].requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum;
}
setState(() {});
chatVM.updateRejectOfferDescription('');
Utils.showToast(LocaleKeys.offerRejected.tr());
// navigateReplaceWithName(context, AppRoutes.dashboard);
}
},
maxWidth: double.infinity,
),
19.height,
],
),
));
});
},
);
}
Future buildDealBottomSheetOptionsForSpecialCar({required ChatMessageModel chatMessageModel, required RequestOfferStatusEnum requestOfferStatusEnum, required bool fromCancelOffer}) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: true,
builder: (BuildContext sheetContext) {
return Consumer(builder: (BuildContext context, ChatVM chatVM, Widget? child) {
return InfoBottomSheet(
title: LocaleKeys.pleaseSpecify.tr().toText(fontSize: 28, isBold: true, letterSpacing: -1.44),
description: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
12.height,
CheckBoxWithTitleDescription(
isSelected: chatVM.dealOptionsModelList[0].isSelected!,
title: '${chatVM.dealOptionsModelList[0].title}',
description: '',
onSelection: (bool value) {
chatVM.updateIsSelectedInDealOptionsModelList(0, value);
},
),
CheckBoxWithTitleDescription(
isSelected: chatVM.dealOptionsModelList[1].isSelected ?? false,
title: '${chatVM.dealOptionsModelList[1].title}',
description: '',
onSelection: (bool value) {
chatVM.updateIsSelectedInDealOptionsModelList(1, value);
},
),
],
),
25.height,
ShowFillButton(
title: LocaleKeys.continu.tr(),
onPressed: () async {
pop(sheetContext);
if (chatVM.dealOptionsModelList[0].isSelected ?? false) {
if (fromCancelOffer) {
final requestVM = context.read<RequestsVM>();
return dealCompletedConsentBottomSheet(
mainContext: context,
requestStatusEnum: RequestStatusEnum.completed,
requestId: requestVM.currentSelectedRequest!.id,
);
} else {
final requestVM = context.read<RequestsVM>();
return dealCompletedConsentBottomSheet(
mainContext: context,
requestStatusEnum: RequestStatusEnum.completed,
requestId: requestVM.currentSelectedRequest!.id,
acceptRequestOffer: () async {
bool status = await chatVM.onSendMessageForActionOnRequestOffer(
receiverId: chatMessageModel.senderUserID ?? "",
chatMessageType: ChatMessageTypeEnum.offer,
comments: GlobalConsts.acceptingThisOffer,
requestId: chatMessageModel.reqOffer!.requestID ?? -1,
serviceProviderID: chatMessageModel.serviceProviderID ?? 0,
requestOfferID: chatMessageModel.reqOffer!.id ?? -1,
offerPrice: chatMessageModel.reqOffer!.price.toString(),
serviceItemName: chatMessageModel.reqOffer!.serviceItemName ?? "",
manufacturedOn: chatMessageModel.reqOffer!.manufacturedOn ?? "",
manufacturedById: chatMessageModel.reqOffer!.manufacturedById ?? 0,
requestOfferStatusEnum: RequestOfferStatusEnum.accepted,
context: context,
);
if (status) {
chatMessageModel.reqOffer!.requestOfferStatusEnum = RequestOfferStatusEnum.accepted;
requestVM.currentSelectedRequest!.requestStatus = RequestStatusEnum.inProgress;
requestVM.updateAcceptedReqOffer(chatMessageModel.reqOffer!);
requestVM.updateAcceptedRequestOfferProviderName(chatMessageModel.senderName ?? "");
int index = chatVM.serviceProviderOffersList.indexWhere(
(element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId));
if (index != -1) {
chatVM.serviceProviderOffersList[index].requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum;
}
setState(() {});
Utils.showToast(LocaleKeys.offerAccepted.tr());
return true;
} else {
return false;
}
});
}
} else {
if (fromCancelOffer) {
buildRejectOrCancelOfferBottomSheet(chatMessageModel: chatMessageModel, requestOfferStatusEnum: RequestOfferStatusEnum.cancel);
} else {
buildDealNotCompletedBottomSheetOptions(chatMessageModel: chatMessageModel);
}
}
},
maxWidth: double.infinity,
),
19.height,
],
),
));
});
},
);
}
Future buildRejectOrCancelOfferBottomSheet({required ChatMessageModel chatMessageModel, required RequestOfferStatusEnum requestOfferStatusEnum}) {
return showModalBottomSheet(
context: context,
@ -149,7 +381,8 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
if (status) {
final requestVM = context.read<RequestsVM>();
chatMessageModel.reqOffer!.requestOfferStatusEnum = requestOfferStatusEnum;
int index = chatVM.serviceProviderOffersList.indexWhere((element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId));
int index = chatVM.serviceProviderOffersList
.indexWhere((element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId));
if (index != -1) {
chatVM.serviceProviderOffersList[index].requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum;
@ -207,14 +440,15 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
requestVM.currentSelectedRequest!.requestStatus = RequestStatusEnum.inProgress;
requestVM.updateAcceptedReqOffer(chatMessageModel.reqOffer!);
requestVM.updateAcceptedRequestOfferProviderName(chatMessageModel.senderName ?? "");
int index = chatVM.serviceProviderOffersList.indexWhere((element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId));
int index = chatVM.serviceProviderOffersList
.indexWhere((element) => (element.providerId == requestVM.currentSelectedOffer!.providerId) && (element.requestId == requestVM.currentSelectedOffer!.requestId));
if (index != -1) {
chatVM.serviceProviderOffersList[index].requestOfferStatusEnum = chatMessageModel.reqOffer!.requestOfferStatusEnum;
}
setState(() {});
// Navigator.pop(context);
Utils.showToast("Offer Accepted");
Utils.showToast(LocaleKeys.offerAccepted.tr());
// navigateReplaceWithName(context, AppRoutes.dashboard);
}
},
@ -281,7 +515,8 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
fontSize: 12,
isItalic: true,
),
).toContainer(borderRadius: 40, width: double.infinity, backgroundColor: chatMessageModel.isMyMessage! ? MyColors.adPendingStatusColor.withOpacity(0.16) : MyColors.grey98Color.withOpacity(0.1)),
).toContainer(
borderRadius: 40, width: double.infinity, backgroundColor: chatMessageModel.isMyMessage! ? MyColors.adPendingStatusColor.withOpacity(0.16) : MyColors.grey98Color.withOpacity(0.1)),
],
);
}
@ -294,7 +529,8 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
"${(chatMessageModel.reqOffer!.price ?? 0.0).toInt()}".toText(fontSize: 19, isBold: true, color: AppState().currentAppType == AppType.provider ? MyColors.white : MyColors.darkTextColor),
"${(chatMessageModel.reqOffer!.price ?? 0.0).toInt()}"
.toText(fontSize: 19, isBold: true, color: AppState().currentAppType == AppType.provider ? MyColors.white : MyColors.darkTextColor),
5.width,
LocaleKeys.sar.tr().toText(color: MyColors.lightTextColor, height: 2.2, fontSize: 10, isBold: true),
],
@ -311,7 +547,11 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
borderColor: MyColors.greenColor,
isFilled: false,
onPressed: () {
offerAcceptConfirmationBottomSheet(chatMessageModel: chatMessageModel);
if (widget.requestsTypeEnum == RequestsTypeEnum.specialCarRequest) {
buildDealBottomSheetOptionsForSpecialCar(chatMessageModel: chatMessageModel, requestOfferStatusEnum: requestOfferStatusEnum, fromCancelOffer: false);
} else {
offerAcceptConfirmationBottomSheet(chatMessageModel: chatMessageModel);
}
},
backgroundColor: MyColors.white,
txtColor: MyColors.greenColor,
@ -338,6 +578,23 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
10.height,
Row(
children: [
if (widget.requestsTypeEnum == RequestsTypeEnum.specialCarRequest) ...[
Expanded(
child: ShowFillButton(
maxHeight: 27,
title: LocaleKeys.more.tr(),
fontSize: 9,
borderColor: MyColors.lightTextColor,
isFilled: false,
onPressed: () {
buildDealBottomSheetOptionsForSpecialCar(chatMessageModel: chatMessageModel, requestOfferStatusEnum: requestOfferStatusEnum, fromCancelOffer: true);
},
backgroundColor: MyColors.white,
txtColor: MyColors.lightTextColor,
),
),
20.width,
],
Expanded(
child: ShowFillButton(
maxHeight: 27,
@ -468,11 +725,42 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
return widget.toCircle(borderRadius: 100);
}
Future<void> onOfferEditIconPressed() async {
int index = context.read<RequestsVM>().myFilteredRequests.indexWhere((request) => request.id == widget.requestModel!.id);
RequestDetailPageArguments requestDetailArguments = RequestDetailPageArguments(
requestIndex: index, // Not getting used in case of update offer
requestModel: widget.requestModel!,
);
RequestsVM requestVM = context.read<RequestsVM>();
requestVM.resetSendOfferBottomSheet();
ReqOffer offer = widget.chatMessageModel.reqOffer!;
requestVM.updateOfferPrice((offer.price ?? "").toString());
requestVM.updateServiceItem((offer.serviceItemName ?? "").toString());
requestVM.updateItemManufacturer((offer.manufacturedById ?? "").toString());
requestVM.updateServiceItemCreatedOn((offer.manufacturedOn ?? "").toString());
requestVM.updateOfferDescription((widget.chatMessageModel.chatText ?? "").toString());
requestVM.updateIsDeliveryAvailableStatus((offer.isDeliveryAvailable ?? false));
if (offer.reqOfferImages != null && offer.reqOfferImages!.isNotEmpty) {
for (var element in offer.reqOfferImages!) {
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);
}
}
}
buildSendOfferBottomSheet(context: context, requestDetailPageArguments: requestDetailArguments, isFromChatScreen: true, offerId: offer.id);
}
Widget buildFreeTextDetailsInMessage({required ChatMessageTypeEnum chatMessageTypeEnum}) {
return Row(
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (chatMessageTypeEnum == ChatMessageTypeEnum.offer && (widget.chatMessageModel.isMyMessage == true) && widget.chatMessageModel.isRead == true) ...[
if (chatMessageTypeEnum == ChatMessageTypeEnum.offer && (widget.chatMessageModel.isMyMessage == true) && (widget.chatMessageModel.isRead ?? false)) ...[
Row(
children: [
const Icon(Icons.remove_red_eye_outlined, size: 12, color: MyColors.lightTextColor),
@ -480,14 +768,18 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
LocaleKeys.viewed.tr().toText(fontSize: 10, color: MyColors.lightTextColor, fontWeight: MyFonts.Medium),
],
),
] else if (AppState().currentAppType == AppType.provider &&
chatMessageTypeEnum == ChatMessageTypeEnum.offer &&
widget.chatMessageModel.reqOffer!.requestOfferStatusEnum == RequestOfferStatusEnum.offer &&
(widget.chatMessageModel.isMyMessage == true)) ...[
MyAssets.icEdit.buildSvg(color: MyColors.white, height: 15).onPress(() => onOfferEditIconPressed()),
],
Expanded(
child: Directionality(
textDirection: (widget.chatMessageModel.isMyMessage ?? false) ? TextDirection.ltr : TextDirection.rtl,
textDirection: TextDirection.ltr,
child: (widget.chatMessageModel.chatText ?? "").toText(
color: (widget.chatMessageModel.isMyMessage ?? false) ? MyColors.white : MyColors.lightTextColor,
fontSize: 12,
// isBold: true,
),
),
),
@ -518,12 +810,12 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
});
}
return SizedBox(
// height: (gridItemSize * 2) + (spacing * 2), // Fixed height for 2 rows including spacing
// height: (gridItemSize * 2) + (spacing * 2), // Fixed height for 2 rows including spacing
child: GridView.builder(
physics: const NeverScrollableScrollPhysics(),
// Prevent scrolling inside grid
// Prevent scrolling inside grid
shrinkWrap: true,
// Shrink size to fit the content
// Shrink size to fit the content
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // Show 2 images per row
crossAxisSpacing: spacing,
@ -609,19 +901,18 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
case ChatMessageTypeEnum.file:
case ChatMessageTypeEnum.offer:
messageTypeWidget = Column(
crossAxisAlignment: (widget.chatMessageModel.isMyMessage ?? false) ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
buildFreeTextDetailsInMessage(chatMessageTypeEnum: chatMessageTypeEnum),
if (widget.requestsTypeEnum == RequestsTypeEnum.serviceRequest) ...[
2.height,
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
"${LocaleKeys.deliveryAvailable.tr()} : ${(widget.chatMessageModel.reqOffer!.isDeliveryAvailable ?? false) ? LocaleKeys.yes.tr() : LocaleKeys.no.tr()}".toText(
fontSize: 10,
color: (widget.chatMessageModel.isMyMessage ?? false) ? MyColors.white : MyColors.lightTextColor,
fontWeight: MyFonts.Medium,
),
],
Directionality(
textDirection: TextDirection.ltr,
child: "${LocaleKeys.deliveryAvailable.tr()} : ${(widget.chatMessageModel.reqOffer!.isDeliveryAvailable ?? false) ? LocaleKeys.yes.tr() : LocaleKeys.no.tr()}".toText(
fontSize: 10,
color: (widget.chatMessageModel.isMyMessage ?? false) ? MyColors.white : MyColors.lightTextColor,
fontWeight: MyFonts.Medium,
),
),
],
if (widget.chatMessageModel.reqOffer!.reqOfferImages != null && widget.chatMessageModel.reqOffer!.reqOfferImages!.isNotEmpty) ...[
@ -671,7 +962,10 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
((widget.chatMessageModel.isMyMessage ?? false) ? "You" : widget.chatMessageModel.senderName ?? "").toText(fontSize: 16, isBold: true),
((widget.chatMessageModel.isMyMessage ?? false) ? LocaleKeys.you.tr() : widget.chatMessageModel.senderName ?? "").toText(
fontSize: 16,
isBold: true,
),
],
),
5.height,

@ -1,3 +1,6 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/app_state.dart';
import 'package:mc_common_app/classes/consts.dart';
@ -18,10 +21,9 @@ import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
import 'package:mc_common_app/widgets/common_widgets/categories_list.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart';
class AdsFragment extends StatelessWidget {
const AdsFragment({Key? key}) : super(key: key);
const AdsFragment({super.key});
List<AdDetailsModel> getAdsList(AdVM adVM) {
if (adVM.isExploreAdsTapped) {
@ -135,7 +137,14 @@ class AdsFragment extends StatelessWidget {
},
child: adVM.state == ViewState.busy
? const Center(child: CircularProgressIndicator())
: AdsListWidget(isAdsFragment: true, shouldShowAdStatus: !adVM.isExploreAdsTapped, adsList: getAdsList(adVM)),
: AdsListWidget(
isAdsFragment: true,
shouldShowAdStatus: !adVM.isExploreAdsTapped,
adsList: getAdsList(adVM),
onFetchMoreAds: () {
log("fetch more ads");
},
),
),
)
],
@ -143,6 +152,7 @@ class AdsFragment extends StatelessWidget {
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
adVM.getVehicleAdsDuration();
if (AppState().userType != UserType.providerDealer) {
if (adVM.vehicleAdDurationId.selectedId == -1 && adVM.vehicleAdsDurations.isNotEmpty) {
SelectionModel selection = SelectionModel(

@ -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) {

@ -80,19 +80,21 @@ class _OfferListPageState extends State<OfferListPage> {
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<OfferListPage> {
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,

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/consts.dart';
@ -21,8 +23,14 @@ import 'package:mc_common_app/widgets/txt_field.dart';
import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart';
Future buildSendOfferBottomSheet({required BuildContext context, required RequestDetailPageArguments requestDetailPageArguments, required bool isFromChatScreen}) {
Future buildSendOfferBottomSheet({
required BuildContext context,
required RequestDetailPageArguments requestDetailPageArguments,
required bool isFromChatScreen,
required int? offerId,
}) {
final requestDetail = requestDetailPageArguments.requestModel;
final requestIndex = requestDetailPageArguments.requestIndex;
return showModalBottomSheet(
context: context,
isScrollControlled: true,
@ -134,24 +142,42 @@ Future buildSendOfferBottomSheet({required BuildContext context, required Reques
),
25.height,
ShowFillButton(
title: LocaleKeys.submit.tr(),
title: offerId == null ? LocaleKeys.submit.tr() : LocaleKeys.update.tr(),
maxHeight: 55,
onPressed: () {
requestsVM.onSendOfferPressed(
context: context,
receiverId: requestDetail.customerID,
message: requestsVM.offerDescription,
requestId: requestDetail.id,
offerPrice: requestsVM.offerPrice,
requestModel: requestDetail,
isDeliveryAvailable: requestsVM.isDeliveryAvailableStatus,
requestIndex: requestDetailPageArguments.requestIndex,
isFromChatScreen: isFromChatScreen,
manufacturedById: 1,
// Todo: It should be the ID of the manufacturer
manufacturedOn: requestsVM.serviceItemCreatedOn,
serviceItemName: requestsVM.serviceItem,
);
if (offerId == null) {
requestsVM.onSendOfferPressed(
context: context,
offerId: offerId,
receiverId: requestDetail.customerID,
message: requestsVM.offerDescription,
requestId: requestDetail.id,
offerPrice: requestsVM.offerPrice,
requestModel: requestDetail,
isDeliveryAvailable: requestsVM.isDeliveryAvailableStatus,
requestIndex: requestDetailPageArguments.requestIndex,
isFromChatScreen: isFromChatScreen,
manufacturedById: 1,
// Todo: It should be the ID of the manufacturer
manufacturedOn: requestsVM.serviceItemCreatedOn,
serviceItemName: requestsVM.serviceItem,
);
} else {
requestsVM.updateOfferFromProvider(
message: requestsVM.offerDescription,
requestId: requestDetail.id,
offerId: offerId,
offerPrice: requestsVM.offerPrice,
isDeliveryAvailable: requestsVM.isDeliveryAvailableStatus,
serviceItemName: requestsVM.serviceItem,
manufacturedById: 1,
// Todo: It should be the ID of the manufacturer
manufacturedOn: requestsVM.serviceItemCreatedOn,
receiverId: requestDetail.customerId,
customerRequestIndex: requestIndex,
context: context,
);
}
},
maxWidth: double.infinity,
),

@ -77,7 +77,12 @@ class RequestDetailPage extends StatelessWidget {
maxHeight: 55,
title: LocaleKeys.completeDeal.tr(),
onPressed: () {
return dealCompletedConfirmationBottomSheet(mainContext: context, requestStatusEnum: RequestStatusEnum.completed, requestId: requestId);
return dealCompletedConsentBottomSheet(
mainContext: context,
requestStatusEnum: RequestStatusEnum.completed,
requestId: requestId,
acceptRequestOffer: () {},
);
},
);
}
@ -159,7 +164,12 @@ class RequestDetailPage extends StatelessWidget {
title: LocaleKeys.provideOffer.tr(),
isBold: false,
fontSize: 18,
onPressed: () => buildSendOfferBottomSheet(context: context, requestDetailPageArguments: requestDetailPageArguments, isFromChatScreen: false),
onPressed: () => buildSendOfferBottomSheet(
context: context,
requestDetailPageArguments: requestDetailPageArguments,
isFromChatScreen: false,
offerId: null, // null means creating new offer
),
),
] else ...[
buildRequestDetailActionFooter(

@ -94,20 +94,25 @@ class _ReviewRequestOfferState extends State<ReviewRequestOffer> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.locationInformation.tr().toText(fontSize: 18),
MyAssets.icEdit.buildSvg().onPress(() => buildLocationInformationEditBottomSheet(context, requestVM)),
],
),
LocaleKeys.locationInformation.tr().toText(fontSize: 18),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// LocaleKeys.locationInformation.tr().toText(fontSize: 18),
// MyAssets.icEdit.buildSvg().onPress(() => buildLocationInformationEditBottomSheet(context, requestVM)),
// ],
// ),
8.height,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SingleDetailWidget(text: requestVM.currentSelectedRequest!.address ?? "", type: LocaleKeys.location.tr()),
16.height,
SingleDetailWidget(text: requestVM.additionalAddressSparePartRequestDelivery.isNotEmpty ? requestVM.additionalAddressSparePartRequestDelivery : "N/A", type: LocaleKeys.additionalAddressDetails.tr()),
// 16.height,
// SingleDetailWidget(
// text: requestVM.additionalAddressSparePartRequestDelivery.isNotEmpty ? requestVM.additionalAddressSparePartRequestDelivery : "N/A",
// type: LocaleKeys.additionalAddressDetails.tr(),
// ),
],
),
],
@ -117,9 +122,13 @@ class _ReviewRequestOfferState extends State<ReviewRequestOffer> {
Widget buildServiceInformation(BuildContext context) {
final requestVM = context.read<RequestsVM>();
String formattedDate = "";
String manufacturedOnFormattedDate = "";
if (requestVM.acceptedRequestOffer!.manufacturedOn != null) {
formattedDate = DateHelper.formatAsDayMonthYear(DateHelper.parseStringToDate(DateHelper.formatDateT(requestVM.acceptedRequestOffer!.manufacturedOn.toString() ?? "")));
manufacturedOnFormattedDate = DateHelper.formatAsDayMonthYear(DateHelper.parseStringToDate(DateHelper.formatDateT(requestVM.acceptedRequestOffer!.manufacturedOn.toString() ?? "")));
}
String requestCreatedOn = "";
if (requestVM.currentSelectedRequest!.createdOn != null) {
requestCreatedOn = DateHelper.formatAsDayMonthYear(DateHelper.parseStringToDate(DateHelper.formatDateT(requestVM.currentSelectedRequest!.createdOn.toString() ?? "")));
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -148,8 +157,8 @@ class _ReviewRequestOfferState extends State<ReviewRequestOffer> {
16.height,
SingleDetailWidget(text: (requestVM.acceptedRequestOffer!.manufacturedById ?? "").toString(), type: LocaleKeys.manufacturedBy.tr()),
],
16.height,
SingleDetailWidget(text: "${requestVM.acceptedRequestOffer!.price.toString()} SAR", type: LocaleKeys.offerPrice.tr()),
// 16.height,
// SingleDetailWidget(text: "${requestVM.acceptedRequestOffer!.price.toString()} ${LocaleKeys.sar.tr()}", type: LocaleKeys.offerPrice.tr()),
16.height,
SingleDetailWidget(text: requestVM.acceptedRequestOfferProviderName ?? "", type: LocaleKeys.providerName.tr()),
16.height,
@ -165,12 +174,18 @@ class _ReviewRequestOfferState extends State<ReviewRequestOffer> {
SingleDetailWidget(text: requestVM.currentSelectedRequest!.brand, type: LocaleKeys.vehicleBrand.tr()),
16.height,
SingleDetailWidget(text: requestVM.acceptedRequestOffer!.serviceItemName ?? "", type: LocaleKeys.serviceName.tr()),
if (formattedDate.isNotEmpty) ...[
if (manufacturedOnFormattedDate.isNotEmpty) ...[
16.height,
SingleDetailWidget(text: formattedDate, type: LocaleKeys.manufacturedOn.tr()),
SingleDetailWidget(text: manufacturedOnFormattedDate, type: LocaleKeys.manufacturedOn.tr()),
],
16.height,
SingleDetailWidget(text: "${requestVM.currentSelectedRequest!.price.toString()} SAR", type: LocaleKeys.totalPrice.tr()),
// SingleDetailWidget(text: "${requestVM.currentSelectedRequest!.price.toString()} ${LocaleKeys.sar.tr()}", type: LocaleKeys.totalPrice.tr()),
SingleDetailWidget(text: "${requestVM.acceptedRequestOffer!.price.toString()} ${LocaleKeys.sar.tr()}", type: LocaleKeys.offerPrice.tr()),
if (requestCreatedOn.isNotEmpty) ...[
16.height,
SingleDetailWidget(text: requestCreatedOn, type: LocaleKeys.requestCreatedOn.tr()),
],
],
),
),

@ -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>();
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();
});
}

@ -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<ProviderAcceptedRequestsV
)
: ListView.separated(
itemBuilder: (context, index) {
return RequestItem(request: requestsVM.providersAcceptedRequestsList[index], appType: AppState().currentAppType, requestIndex: index);
return RequestItem(
request: requestsVM.providersAcceptedRequestsList[index],
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 {
navigateWithName(context, AppRoutes.offersListPage, arguments: request.id);
}
},
);
},
separatorBuilder: (context, index) {
return 16.height;

@ -57,14 +57,6 @@ class CustomSettingOptionsTile extends StatelessWidget {
),
isForLanguage
? LocaleKeys.english.tr().toText(fontSize: 12, isUnderLine: true, color: MyColors.primaryColor)
// const Icon(
// Icons.language,
// size: 18,
// color: MyColors.primaryColor,
// )
//
: showTrailingArrow
? const Icon(Icons.arrow_forward, size: 18)
: const SizedBox()

@ -9,43 +9,55 @@ class CheckBoxWithTitleDescription extends StatelessWidget {
final String title, description;
final Function(bool) onSelection;
final bool isDisabled;
final CrossAxisAlignment? crossAxisAlignment;
const CheckBoxWithTitleDescription({
required this.isSelected,
required this.title,
required this.description,
required this.onSelection,
this.crossAxisAlignment,
this.isDisabled = false,
super.key,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Checkbox(
value: isSelected,
activeColor: isDisabled ? MyColors.lightIconColor : MyColors.darkPrimaryColor,
onChanged: (bool? v) {
if (isDisabled) return;
onSelection(v ?? false);
return GestureDetector(
onTap: isDisabled
? null
: () {
onSelection(!isSelected);
},
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
title.toText(fontSize: 14, isBold: true),
description.toText(fontSize: 12, color: MyColors.lightTextColor),
],
child: SizedBox(
width: double.infinity,
child: Row(
crossAxisAlignment: crossAxisAlignment == null ? (description.isNotEmpty ? CrossAxisAlignment.start : CrossAxisAlignment.center) : crossAxisAlignment!,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Checkbox(
value: isSelected,
activeColor: isDisabled ? MyColors.lightIconColor : MyColors.darkPrimaryColor,
onChanged: isDisabled
? null
: (bool? v) {
onSelection(v ?? false);
},
),
),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
title.toText(fontSize: 14, isBold: true),
if (description.isNotEmpty) ...[
description.toText(fontSize: 12, color: MyColors.lightTextColor),
]
],
),
),
],
),
),
);
}

@ -1,14 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mc_common_app/extensions/int_extensions.dart';
import 'package:mc_common_app/extensions/string_extensions.dart';
import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
class DropDownText extends StatelessWidget {
String title;
final String title;
final bool showDropDownIcon;
DropDownText(this.title, {super.key});
DropDownText({required this.title, required this.showDropDownIcon, super.key});
@override
Widget build(BuildContext context) {
@ -20,7 +19,8 @@ class DropDownText extends StatelessWidget {
fontSize: 16,
letterSpacing: -0.64,
),
const Icon(Icons.keyboard_arrow_down_outlined),
if (showDropDownIcon)
const Icon(Icons.keyboard_arrow_down_outlined),
],
).toContainer(
isEnabledBorder: true,

Loading…
Cancel
Save