You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
car_common_app/lib/view_models/appointments_view_model.dart

1151 lines
43 KiB
Dart

import 'dart:developer';
import 'package:flutter/material.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';
import 'package:mc_common_app/models/appointments_models/appointment_list_model.dart';
import 'package:mc_common_app/models/appointments_models/appointment_slots.dart';
import 'package:mc_common_app/models/appointments_models/service_schedule_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/m_response.dart';
import 'package:mc_common_app/models/general_models/widgets_models.dart';
import 'package:mc_common_app/models/provider_branches_models/branch_detail_model.dart';
import 'package:mc_common_app/models/provider_branches_models/profile/categroy.dart';
import 'package:mc_common_app/models/provider_branches_models/profile/services.dart';
import 'package:mc_common_app/models/provider_branches_models/provider_model.dart';
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/repositories/appointment_repo.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/enums.dart';
import 'package:mc_common_app/utils/navigator.dart';
import 'package:mc_common_app/utils/utils.dart';
import 'package:mc_common_app/view_models/base_view_model.dart';
import 'package:mc_common_app/view_models/dashboard_view_model_customer.dart';
import 'package:mc_common_app/view_models/payment_view_model.dart';
import 'package:mc_common_app/views/appointments/book_appointment_schedules_view.dart';
import 'package:mc_common_app/views/appointments/widgets/appointment_service_pick_bottom_sheet.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';
import 'package:provider/provider.dart';
class AppointmentsVM extends BaseVM {
final CommonRepo commonRepo;
final CommonAppServices commonServices;
final BranchRepo branchRepo;
final AppointmentRepo scheduleRepo;
AppointmentsVM({required this.commonServices, required this.scheduleRepo, required this.commonRepo, required this.branchRepo});
bool isUpcommingEnabled = true;
bool isFetchingLists = false;
int selectedBranch = 0;
int selectedAppointmentIndex = 0;
int selectedAppointmentSubIndex = 0;
int selectedAppointmentId = 0;
List<AppointmentListModel> myAppointments = [];
List<AppointmentListModel> myUpComingAppointments = [];
List<AppointmentListModel> myFilteredAppointments = [];
List<FilterListModel> appointmentsFilterOptions = [];
List<CustomerData> customersAppointments = [];
List<AppointmentListModel> myFilteredAppointments2 = [];
// List<ScheduleData> availableSchedules = [];
bool isFetchingServices = false;
List<DropValue> branchCategories = [];
bool isHomeTapped = false;
List<ServiceAppointmentScheduleModel> serviceAppointmentScheduleList = [];
bool ifItemAlreadySelected(int id) {
int indexFound = allSelectedItemsInAppointments.indexWhere((element) => element.id == id);
if (indexFound != -1) {
return true;
}
return false;
}
List<ItemData> allSelectedItemsInAppointments = [];
setupProviderAppointmentFilter() {
appointmentsFilterOptions.clear();
appointmentsFilterOptions.add(FilterListModel(id: 0, title: "All Appointments", isSelected: true));
appointmentsFilterOptions.add(FilterListModel(id: 2, title: "Confirmed", isSelected: false));
appointmentsFilterOptions.add(FilterListModel(id: 3, title: "Arrived", isSelected: false));
appointmentsFilterOptions.add(FilterListModel(id: 7, title: "Work In Progress", isSelected: false));
appointmentsFilterOptions.add(FilterListModel(id: 8, title: "Completed", isSelected: false));
appointmentsFilterOptions.add(FilterListModel(id: 4, title: "Canceled", isSelected: false));
}
Future<void> onItemsSelectedInService() async {
if (currentServiceSelection != null) {
int index = servicesInCurrentAppointment.indexWhere((element) => element.serviceId == currentServiceSelection!.serviceId!);
if (index == -1) {
double totalPrice = 0.0;
for (var element in currentServiceSelection!.serviceItems!) {
totalPrice = totalPrice + double.parse(element.price ?? "0.0");
}
currentServiceSelection!.currentTotalServicePrice = totalPrice;
servicesInCurrentAppointment.insert(0, currentServiceSelection!);
}
resetCategorySelectionBottomSheet();
notifyListeners();
}
}
Future<void> onPayNowPressedForAppointment({required BuildContext context, required int appointmentID}) async {
context.read<PaymentVM>().updateAppointmentIdsForPayment(ids: [appointmentID]);
navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.partialAppointment);
}
Future<void> onBookAppointmentPressed(BuildContext context) async {
Utils.showLoading(context);
bool isSuccess = false;
List<int> appointmentIdsList = [];
try {
GenericRespModel genericRespModel = await scheduleRepo.createServiceAppointment(
schedules: serviceAppointmentScheduleList,
serviceProviderID: selectedBranchModel!.serviceProviderId ?? 0,
);
if (genericRespModel.data.isEmpty) {
Utils.hideLoading(context);
Utils.showToast("${genericRespModel.message.toString()}");
return;
}
if (genericRespModel.data != null && genericRespModel.data.isNotEmpty) {
genericRespModel.data.forEach((element) {
if (element['appointmentID'] != 0) {
appointmentIdsList.add(element['appointmentID']);
isSuccess = true;
} else {
isSuccess = false;
Utils.showToast(element['message']);
return;
}
});
}
context.read<DashboardVmCustomer>().onNavbarTapped(1);
applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.booked);
Utils.hideLoading(context);
resetAfterBookingAppointment();
if (isSuccess) {
if (amountToPayForAppointment > 0) {
context.read<PaymentVM>().updateAppointmentIdsForPayment(ids: appointmentIdsList);
navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.appointment);
} else {
Utils.showToast("Your appointment has been booked successfully!");
getMyAppointments();
navigateReplaceWithNameUntilRoute(context, AppRoutes.dashboard);
}
}
} catch (e) {
Utils.showToast("${e.toString()}");
}
}
Future<void> onConfirmAppointmentPressed({required BuildContext context, required appointmentId}) async {
context.read<PaymentVM>().updateAppointmentIdsForPayment(ids: [appointmentId]);
navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.appointment);
}
Future<void> onCancelAppointmentPressed({required BuildContext context, required AppointmentListModel appointmentListModel}) async {
Utils.showLoading(context);
try {
GenericRespModel genericRespModel = await scheduleRepo.cancelOrRescheduleServiceAppointment(
serviceAppointmentID: appointmentListModel.id ?? 0,
serviceSlotID: appointmentListModel.serviceSlotID ?? 0,
appointmentScheduleAction: 2, // 1 for Reschedule and 2 for Cancel
);
if (genericRespModel.messageStatus == 2 || genericRespModel.data == null) {
Utils.hideLoading(context);
Utils.showToast("${genericRespModel.message.toString()}");
return;
}
if (genericRespModel.messageStatus == 1) {
context.read<DashboardVmCustomer>().onNavbarTapped(1);
applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.cancelled);
Utils.showToast("${genericRespModel.message.toString()}");
await getMyAppointments();
Utils.hideLoading(context);
getMyAppointments();
navigateReplaceWithNameUntilRoute(context, AppRoutes.dashboard);
}
} catch (e) {
Utils.showToast("${e.toString()}");
}
}
void updateIsHomeTapped(bool value) {
isHomeTapped = value;
if (currentServiceSelection != null) {
currentServiceSelection!.isHomeSelected = value;
}
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) {
branchSelectedCategoryId = id;
getBranchServices(categoryId: branchSelectedCategoryId.selectedId);
notifyListeners();
}
List<FilterListModel> branchesFilterOptions = [];
List<ServiceModel> servicesInCurrentAppointment = [];
ServiceModel? currentServiceSelection;
void updateBranchServiceId(SelectionModel id) async {
branchSelectedServiceId = id;
currentServiceSelection = branchServices.firstWhere((element) => element.serviceProviderServiceId == id.selectedId);
notifyListeners();
}
void removeServiceInCurrentAppointment(int index) {
int serviceId = servicesInCurrentAppointment.elementAt(index).serviceProviderServiceId ?? -1;
allSelectedItemsInAppointments.removeWhere((element) => element.serviceProviderServiceId == serviceId);
servicesInCurrentAppointment.removeAt(index);
notifyListeners();
}
resetCategorySelectionBottomSheet() {
selectedSubServicesCounter = 0;
branchSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
isHomeTapped = false;
branchSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
currentServiceSelection = null;
}
resetAfterBookingAppointment() {
// allSelectedItemsInAppointments.clear();
servicesInCurrentAppointment.clear();
// serviceAppointmentScheduleList.clear();
}
List<EnumsModel> myAppointmentsEnum = [];
populateAppointmentsFilterList() async {
if (appointmentsFilterOptions.isNotEmpty) return;
myAppointmentsEnum = await commonRepo.getEnumTypeValues(enumTypeID: 13); //TODO: 13 is to get Appointments Filter Enums
for (int i = 0; i < myAppointmentsEnum.length; i++) {
appointmentsFilterOptions.add(FilterListModel(title: myAppointmentsEnum[i].enumValueStr, isSelected: false, id: myAppointmentsEnum[i].enumValue));
}
appointmentsFilterOptions.insert(0, FilterListModel(title: "All Appointments", isSelected: true, id: 0));
// TODO: THIS SHOULD REMOVED AND ADDED IN THE ENUMS API
appointmentsFilterOptions.add(FilterListModel(title: "Work In Progress", isSelected: false, id: 7));
appointmentsFilterOptions.add(FilterListModel(title: "Visit Completed", isSelected: false, id: 8));
notifyListeners();
}
applyFilterOnAppointmentsVM({required AppointmentStatusEnum appointmentStatusEnum, bool isNeedCustomerFilter = false}) {
if (appointmentsFilterOptions.isEmpty) return;
for (var value in appointmentsFilterOptions) {
value.isSelected = false;
}
for (var element in appointmentsFilterOptions) {
if (element.id == appointmentStatusEnum.getIdFromAppointmentStatusEnum()) {
element.isSelected = true;
}
}
// appointmentsFilterOptions[
// appointmentStatusEnum.getIdFromAppointmentStatusEnum()]
// .isSelected = true;
if (appointmentStatusEnum.getIdFromAppointmentStatusEnum() == 0) {
myFilteredAppointments = myAppointments;
if (isNeedCustomerFilter) findAppointmentsBasedOnCustomers();
notifyListeners();
return;
}
myFilteredAppointments = myAppointments.where((element) => element.appointmentStatusID! == appointmentStatusEnum.getIdFromAppointmentStatusEnum()).toList();
if (isNeedCustomerFilter) findAppointmentsBasedOnCustomers();
notifyListeners();
}
findAppointmentsBasedOnCustomers() {
// Use a Set to ensure uniqueness of customerIDs
Set<int> uniqueCustomerIDs = Set<int>();
// Extract unique customerIDs
for (var item in myFilteredAppointments) {
uniqueCustomerIDs.add(item.customerID ?? 0);
}
// Create a list of CustomerData instances
myFilteredAppointments2 = uniqueCustomerIDs.map((id) {
List<AppointmentListModel> list = myFilteredAppointments.where((item) => item.customerID == id).toList();
AppointmentListModel model = list.first;
model.customerAppointmentList = list;
return model;
}).toList();
// customersAppointments = uniqueCustomerIDs.map((id) {
// List<AppointmentListModel> list = myFilteredAppointments
// .where((item) => item.customerID == id)
// .toList();
// var customerItem =
// myFilteredAppointments.firstWhere((item) => item.customerID == id);
//
// return CustomerData(
// customerID: id,
// customerName: customerItem.customerName ?? "",
// appointmentList: list,
// );
// }).toList();
}
Future<void> getMyAppointments({bool isNeedToRebuild = false}) async {
if (isNeedToRebuild) setState(ViewState.busy);
myAppointments = await commonRepo.getMyAppointments();
myFilteredAppointments = myAppointments;
myUpComingAppointments = myAppointments.where((element) => element.appointmentStatusEnum == AppointmentStatusEnum.confirmed).toList();
setState(ViewState.idle);
// applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments);
notifyListeners();
}
AppointmentSlots? appointmentSlots;
Future<void> getAppointmentSlotsInfo({required Map<String, dynamic> map, required BuildContext context, bool isNeedToRebuild = false}) async {
if (isNeedToRebuild) setState(ViewState.busy);
try {
MResponse genericRespModel = await scheduleRepo.getAppointmentSlots(map);
if (genericRespModel.messageStatus == 1) {
appointmentSlots = AppointmentSlots.fromJson(genericRespModel.data);
} else {
Utils.showToast(genericRespModel.message.toString());
}
} catch (e) {
Utils.showToast(e.toString());
}
}
Future<void> getProviderMyAppointments(Map<String, dynamic> map, {bool isNeedToRebuild = false}) async {
if (isNeedToRebuild) setState(ViewState.busy);
myAppointments = await scheduleRepo.getMyAppointments(map);
myFilteredAppointments = myAppointments;
myUpComingAppointments = myAppointments.where((element) => element.appointmentStatusEnum == AppointmentStatusEnum.booked).toList();
applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments, isNeedCustomerFilter: true);
setState(ViewState.idle);
}
updateAppointmentStatus(Map<String, dynamic> map, {bool isNeedToRebuild = false}) async {
if (isNeedToRebuild) setState(ViewState.busy);
try {
MResponse genericRespModel = await scheduleRepo.updateAppointmentStatus(map);
if (genericRespModel.messageStatus == 1) {
Utils.showToast("appointment status updated");
} else {
Utils.showToast(genericRespModel.message.toString());
}
} catch (e) {
Utils.showToast(e.toString());
}
}
updateAppointmentPaymentStatus(Map<String, dynamic> map, {bool isNeedToRebuild = false}) async {
if (isNeedToRebuild) setState(ViewState.busy);
try {
MResponse genericRespModel = await scheduleRepo.updateAppointmentPaymentStatus(map);
if (genericRespModel.messageStatus == 1) {
Utils.showToast("payment status updated");
} else {
Utils.showToast(genericRespModel.message.toString());
}
} catch (e) {
Utils.showToast(e.toString());
}
}
Future<MResponse> createMergeAppointment(Map<String, dynamic> map, {bool isNeedToRebuild = false}) async {
if (isNeedToRebuild) setState(ViewState.busy);
MResponse genericRespModel = await scheduleRepo.createMergeAppointment(map);
return genericRespModel;
}
bool inNeedToEnableMergeButton = false;
updateCheckBoxInMergeRequest(int currentIndex) {
myFilteredAppointments2[selectedAppointmentIndex].customerAppointmentList![currentIndex].isSelected =
!(myFilteredAppointments2[selectedAppointmentIndex].customerAppointmentList?[currentIndex].isSelected ?? false);
int count = countSelected(myFilteredAppointments2[selectedAppointmentIndex].customerAppointmentList ?? []);
if (count > 1)
inNeedToEnableMergeButton = true;
else
inNeedToEnableMergeButton = false;
notifyListeners();
}
int countSelected(List<AppointmentListModel> appointments) {
return appointments.where((appointment) => appointment.isSelected == true).toList().length;
}
updateSelectedAppointmentDate({required int dateIndex, required int scheduleIndex}) {
for (var element in serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList!) {
element.date!.isSelected = false;
}
serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![dateIndex].date!.isSelected = true;
serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex = dateIndex;
final date = TimeSlotModel(
date: serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![dateIndex].date!.date,
slotId: serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![dateIndex].date!.slotId,
isSelected: true,
slot: "",
);
serviceAppointmentScheduleList[scheduleIndex].selectedCustomTimeDateSlotModel = CustomTimeDateSlotModel(date: date);
notifyListeners();
}
updateSelectedAppointmentSlotByDate({required int scheduleIndex, required int slotIndex}) {
for (var element in serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList!) {
for (var element in element.availableSlots!) {
element.isSelected = false;
}
}
int index = serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex!;
serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![index].availableSlots![slotIndex].isSelected = true;
serviceAppointmentScheduleList[scheduleIndex].selectedCustomTimeDateSlotModel!.availableSlots = serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![index].availableSlots!;
notifyListeners();
}
double amountToPayForAppointment = 0.0;
double totalAmount = 0.0;
List<ItemData> serviceItemsFromApi = [];
ProviderProfileModel? providerProfileModel;
int selectedSubServicesCounter = 0;
onItemUpdateOrSelected(int index, bool selected, int itemId) {
int serviceIndex = servicesInCurrentAppointment.indexWhere((element) => element.serviceId == currentServiceSelection!.serviceId!);
// print("servicesInCurrentAppointment: ${servicesInCurrentAppointment.length}");
// if (serviceIndex == -1) {
// return;
// }
serviceItemsFromApi[index].isUpdateOrSelected = selected;
serviceItemsFromApi[index].isHomeSelected = isHomeTapped;
if (selected) {
selectedSubServicesCounter = selectedSubServicesCounter + 1;
selectSubServicesError = "";
currentServiceSelection!.serviceItems!.add(serviceItemsFromApi[index]);
allSelectedItemsInAppointments.add(serviceItemsFromApi[index]);
for (var element in allSelectedItemsInAppointments) {
if (!ifItemAlreadySelected(element.id!)) {
servicesInCurrentAppointment[serviceIndex].serviceItems!.add(serviceItemsFromApi[index]);
servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice =
servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice + double.parse((serviceItemsFromApi[index].price) ?? "0.0");
}
}
}
if (!selected) {
selectedSubServicesCounter = selectedSubServicesCounter - 1;
currentServiceSelection!.serviceItems!.removeWhere((element) => element.id == itemId);
allSelectedItemsInAppointments.removeWhere((element) => element.id == itemId);
servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice =
servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice - double.parse((serviceItemsFromApi[index].price) ?? "0.0");
servicesInCurrentAppointment[serviceIndex].serviceItems!.removeWhere((element) => element.id == itemId);
}
notifyListeners();
}
applyFilterOnBranches({required int index}) async {
if (branchesFilterOptions.isEmpty) return;
for (var value in branchesFilterOptions) {
value.isSelected = false;
}
branchesFilterOptions[index].isSelected = true;
await getBranchesBasedOnCategoryFilters(categoryId: branchesFilterOptions[index].id);
notifyListeners();
}
Future<List<ItemData>> getServiceItems(int serviceId) async {
serviceItemsFromApi.clear();
serviceItemsFromApi = await branchRepo.getServiceItems(serviceId);
for (var item in serviceItemsFromApi) {
if (ifItemAlreadySelected(item.id!)) {
item.isUpdateOrSelected = true;
}
}
setState(ViewState.idle);
return serviceItemsFromApi;
}
String pickHomeLocationError = "";
String selectSubServicesError = "";
SelectionModel branchSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
void updatePickHomeLocationError(String value) {
pickHomeLocationError = value;
// notifyListeners();
}
bool isServiceSelectionValidated() {
if (branchSelectedServiceId.selectedId == -1) {
return false;
}
if (isHomeTapped) {
if (pickedHomeLocation == "") {
updatePickHomeLocationError(GlobalConsts.homeLocationEmptyError);
return false;
}
}
return true;
}
bool validateItemsSelection() {
for (var value in serviceItemsFromApi) {
if (value.isUpdateOrSelected!) {
return true;
}
}
selectSubServicesError = "Please select at least one sub service";
notifyListeners();
return false;
}
String getTotalPrice(List<ServiceModel> serviceItems) {
var totalPrice = 0.0;
for (var element in serviceItems) {
totalPrice = totalPrice + (element.currentTotalServicePrice);
}
return totalPrice.toString();
}
void openTheAddServiceBottomSheet(BuildContext context, AppointmentsVM appointmentsVM) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: true,
builder: (BuildContext context) {
return AppointmentServicePickBottomSheet();
},
);
}
void priceBreakDownClicked(BuildContext context, ServiceModel selectedService) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: true,
builder: (BuildContext context) {
double totalKms = 15.3;
return InfoBottomSheet(
title: "Charges Breakdown".toText(fontSize: 24, isBold: true),
description: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Services".toText(fontSize: 16, isBold: true),
Column(
children: List.generate(
selectedService.serviceItems!.length,
(index) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"${selectedService.serviceItems![index].name}".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
"${selectedService.serviceItems![index].price} SAR".toText(fontSize: 12, isBold: true),
],
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
"${selectedService.currentTotalServicePrice} SAR".toText(fontSize: 16, isBold: true),
],
),
if (selectedService.isHomeSelected) ...[
20.height,
"Home Location".toText(fontSize: 16, isBold: true),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"${totalKms}km ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
"${selectedService.rangePricePerKm} x $totalKms".toText(fontSize: 12, isBold: true),
],
),
8.height,
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
"${selectedService.rangePricePerKm ?? 0 * totalKms} SAR".toText(fontSize: 16, isBold: true),
],
),
],
30.height,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Total Amount ".toText(fontSize: 16, isBold: true),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
(selectedService.isHomeSelected
? "${(selectedService.currentTotalServicePrice) + (double.parse((selectedService.rangePricePerKm ?? "0.0")) * totalKms)}"
: "${selectedService.currentTotalServicePrice}")
.toText(fontSize: 29, isBold: true),
2.width,
"SAR".toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5),
],
)
],
),
30.height,
],
));
});
}
void onReviewButtonPressed(BuildContext context) {
bool isValidated = false;
for (int i = 0; i < serviceAppointmentScheduleList.length; i++) {
final schedule = serviceAppointmentScheduleList[i];
if (schedule.selectedCustomTimeDateSlotModel == null) {
isValidated = false;
break;
}
if (schedule.selectedCustomTimeDateSlotModel!.date == null || !schedule.selectedCustomTimeDateSlotModel!.date!.isSelected) {
isValidated = false;
break;
} else {
if (schedule.selectedCustomTimeDateSlotModel!.availableSlots == null) {
isValidated = false;
break;
} else {
TimeSlotModel slot = schedule.selectedCustomTimeDateSlotModel!.availableSlots!.firstWhere((element) => element.isSelected);
if (slot.date.isNotEmpty) {
isValidated = true;
break;
}
}
}
}
if (!isValidated) {
Utils.showToast("You must select appointment time for each schedule's appointment.");
return;
}
navigateWithName(context, AppRoutes.reviewAppointmentView);
}
void onServicesNextPressed(BuildContext context) async {
Utils.showLoading(context);
List<String> serviceItemIdsForHome = [];
List<String> serviceItemIdsForWorkshop = [];
for (var serviceItem in allSelectedItemsInAppointments) {
if (serviceItem.isHomeSelected!) {
serviceItemIdsForHome.add(serviceItem.id!.toString());
} else {
serviceItemIdsForWorkshop.add(serviceItem.id!.toString());
}
}
serviceAppointmentScheduleList = await scheduleRepo.mergeServiceIntoAvailableSchedules(
serviceItemIdsForHome: serviceItemIdsForHome,
serviceItemIdsForWorkshop: serviceItemIdsForWorkshop,
);
if (serviceAppointmentScheduleList.isEmpty) {
Utils.hideLoading(context);
Utils.showToast("There are no available appointments for selected Items.");
return;
}
totalAmount = 0.0;
amountToPayForAppointment = 0.0;
for (var schedule in serviceAppointmentScheduleList) {
amountToPayForAppointment = amountToPayForAppointment + (schedule.amountToPay ?? 0.0);
totalAmount = totalAmount + (schedule.amountTotal ?? 0.0);
}
Utils.hideLoading(context);
navigateWithName(context, AppRoutes.bookAppointmenSchedulesView, arguments: ScreenArgumentsForAppointmentDetailPage(routeFlag: 1, appointmentId: 0)); // 1 For Creating an Appointment
notifyListeners();
}
Future<void> onRescheduleAppointmentPressed({required BuildContext context, required AppointmentListModel appointmentListModel}) async {
Utils.showLoading(context);
List<String> serviceItemIdsForHome = [];
List<String> serviceItemIdsForWorkshop = [];
for (var service in appointmentListModel.appointmentServicesList!) {
for (var serviceItem in service.serviceItems!) {
serviceItemIdsForWorkshop.add(serviceItem.id!.toString());
// if (serviceItem.isHomeSelected ?? false) {
// serviceItemIdsForHome.add(serviceItem.id!.toString());
// } else {
// serviceItemIdsForWorkshop.add(serviceItem.id!.toString());
// }
}
}
serviceAppointmentScheduleList = await scheduleRepo.mergeServiceIntoAvailableSchedules(
serviceItemIdsForHome: serviceItemIdsForHome,
serviceItemIdsForWorkshop: serviceItemIdsForWorkshop,
);
if (serviceAppointmentScheduleList.isEmpty) {
Utils.hideLoading(context);
Utils.showToast("There are no available appointments for selected Items.");
return;
}
Utils.hideLoading(context);
navigateWithName(
context,
AppRoutes.bookAppointmenSchedulesView,
arguments: ScreenArgumentsForAppointmentDetailPage(routeFlag: 2, appointmentId: appointmentListModel.id ?? 0),
); // 2 For Rescheduling an Appointment
notifyListeners();
}
Future<void> onRescheduleAppointmentConfirmPressed({required BuildContext context, required int appointmentId, required int selectedSlotId}) async {
Utils.showLoading(context);
try {
GenericRespModel genericRespModel = await scheduleRepo.cancelOrRescheduleServiceAppointment(
serviceAppointmentID: appointmentId,
serviceSlotID: selectedSlotId,
appointmentScheduleAction: 1, // 1 for Reschedule and 2 for Cancel
);
if (genericRespModel.messageStatus == 2 || genericRespModel.data == null) {
Utils.hideLoading(context);
Utils.showToast("${genericRespModel.message.toString()}");
return;
}
if (genericRespModel.messageStatus == 1) {
context.read<DashboardVmCustomer>().onNavbarTapped(1);
applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.cancelled);
Utils.showToast("${genericRespModel.message.toString()}");
getMyAppointments();
Utils.hideLoading(context);
navigateReplaceWithNameUntilRoute(context, AppRoutes.dashboard);
}
} catch (e) {
Utils.showToast("${e.toString()}");
}
}
// BRANCHES RELATED
List<BranchDetailModel> nearbyBranches = [];
BranchDetailModel? selectedBranchModel;
List<ServiceModel> branchServices = [];
// populateBranchesFilterList() {
// branchesFilterOptions.clear(); // TODO: THIS SHOULD BE DYNAMIC AND FILTERS SHOULD COME FORM API
// branchesFilterOptions = [
// FilterListModel(title: "All Branches", isSelected: true, id: 0),
// FilterListModel(title: "Maintenance", isSelected: false, id: 1),
// FilterListModel(title: "Oil Service", isSelected: false, id: 2),
// FilterListModel(title: "Accessories", isSelected: false, id: 3),
// FilterListModel(title: "Tire Service", isSelected: false, id: 4),
// FilterListModel(title: "Dent and Paint", isSelected: false, id: 5),
// ];
// notifyListeners();
// }
Future<void> populateBranchesFilterList() async {
if (branchesFilterOptions.isNotEmpty) return;
branchesFilterOptions.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"));
});
branchesFilterOptions.insert(0, FilterListModel(id: 0, isSelected: true, title: "All Branches"));
notifyListeners();
setState(ViewState.idle);
}
getAllNearBranches({bool isNeedToRebuild = false, bool isFromRefresh = false}) async {
nearbyBranches.clear();
if (isNeedToRebuild) setState(ViewState.busy);
if (isFromRefresh) {
var selectedBranch = branchesFilterOptions.firstWhere((element) => element.isSelected);
nearbyBranches = await branchRepo.getBranchesByFilters(categoryIdsList: [selectedBranch.id]);
setState(ViewState.idle);
return;
}
nearbyBranches = await branchRepo.getAllNearBranchAndServices();
setState(ViewState.idle);
}
void getBranchCategories() async {
for (var value in selectedBranchModel!.branchServices!) {
if (!isCategoryAlreadyPresent(value.categoryId!)) {
branchCategories.add(DropValue(value.categoryId!, value.categoryName!, ""));
}
}
notifyListeners();
}
getBranchServices({required int categoryId}) async {
branchSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
isHomeTapped = false;
pickedHomeLocation = "";
pickHomeLocationError = "";
if (categoryId != -1) {
isFetchingServices = true;
branchServices = getFilteredBranchServices(categoryId: categoryId);
isFetchingServices = false;
notifyListeners();
}
}
List<ServiceModel> getFilteredBranchServices({required int categoryId}) {
List<ServiceModel> filteredServices = selectedBranchModel!.branchServices!.where((element) => element.categoryId == categoryId).toList();
return filteredServices;
}
getBranchAndServices(int providerId) async {
providerProfileModel = null;
providerProfileModel = await branchRepo.getBranchAndServicesByProviderId(providerId);
setState(ViewState.idle);
}
bool isCategoryAlreadyPresent(int id) {
final contain = branchCategories.where((element) => element.id == id);
if (contain.isEmpty) {
return false;
}
return true;
}
updateSelectedBranch(BranchDetailModel branchDetailModel) {
selectedBranchModel = branchDetailModel;
getBranchCategories();
notifyListeners();
}
int branchFiltersCounter = 0;
updateBranchFiltersCounter(int value) {
branchFiltersCounter = value;
notifyListeners();
}
double branchFilterCurrentDistance = 25.0;
updateBranchFilterCurrentDistance(double value) {
branchFilterCurrentDistance = value;
notifyListeners();
}
// Provider Filter
List<DropValue> branchFilterProviderSearchHistory = [];
void removeBranchFilterProviderSearchHistory({bool isClear = false, required int index}) {
if (isClear) {
branchFilterProviderSearchHistory.clear();
notifyListeners();
return;
}
branchFilterProviderSearchHistory.removeAt(index);
if (branchFilterProviderSearchHistory.isEmpty) {
updateBranchFiltersCounter(branchFiltersCounter - 1);
}
notifyListeners();
}
void addBranchFilterProviderSearchHistory({required DropValue value}) {
if (branchFilterProviderSearchHistory.isEmpty) {
updateBranchFiltersCounter(branchFiltersCounter + 1);
}
branchFilterProviderSearchHistory.add(value);
notifyListeners();
}
SelectionModel branchFilterSelectedProviderId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
void updateBranchFilterSelectedProviderId(SelectionModel id, {bool isForSearch = false}) async {
if (isForSearch) {
DropValue providerDrop = providersDropList.firstWhere((element) => element.id == id.selectedId);
if (!ifAlreadyExist(list: branchFilterProviderSearchHistory, value: providerDrop)) {
addBranchFilterProviderSearchHistory(value: providerDrop);
}
}
branchFilterSelectedProviderId = id;
notifyListeners();
}
// Category Filter
List<DropValue> branchFilterCategorySearchHistory = [];
void removeFilterCategorySearchHistory({bool isClear = false, required int index}) {
if (isClear) {
branchFilterCategorySearchHistory.clear();
notifyListeners();
return;
}
branchFilterCategorySearchHistory.removeAt(index);
if (branchFilterCategorySearchHistory.isEmpty) {
updateBranchFiltersCounter(branchFiltersCounter - 1);
}
notifyListeners();
}
void addBranchFilterCategorySearchHistory({required DropValue value}) {
if (branchFilterCategorySearchHistory.isEmpty) {
updateBranchFiltersCounter(branchFiltersCounter + 1);
}
branchFilterCategorySearchHistory.add(value);
notifyListeners();
}
SelectionModel branchFilterSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
void updateBranchFilterSelectedCategoryId(SelectionModel id, {bool isForSearch = false}) async {
if (isForSearch) {
DropValue categoryDrop = categoryDropList.firstWhere((element) => element.id == id.selectedId);
if (!ifAlreadyExist(list: branchFilterCategorySearchHistory, value: categoryDrop)) {
addBranchFilterCategorySearchHistory(value: categoryDrop);
}
}
branchFilterSelectedCategoryId = id;
notifyListeners();
}
// Services Filter
List<DropValue> branchFilterServicesSearchHistory = [];
void removeBranchFilterServicesSearchHistory({bool isClear = false, required int index}) {
if (isClear) {
branchFilterServicesSearchHistory.clear();
notifyListeners();
return;
}
branchFilterServicesSearchHistory.removeAt(index);
if (branchFilterServicesSearchHistory.isEmpty) {
updateBranchFiltersCounter(branchFiltersCounter - 1);
}
notifyListeners();
}
void addBranchFilterServicesSearchHistory({required DropValue value}) {
if (branchFilterServicesSearchHistory.isEmpty) {
updateBranchFiltersCounter(branchFiltersCounter + 1);
}
branchFilterServicesSearchHistory.add(value);
notifyListeners();
}
SelectionModel branchFilterSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
void updateBranchFilterSelectedServiceId(SelectionModel id, {bool isForSearch = false}) async {
if (isForSearch) {
DropValue serviceDrop = servicesDropList.firstWhere((element) => element.id == id.selectedId);
if (!ifAlreadyExist(list: branchFilterServicesSearchHistory, value: serviceDrop)) {
addBranchFilterServicesSearchHistory(value: serviceDrop);
}
}
branchFilterSelectedServiceId = id;
notifyListeners();
}
ifAlreadyExist({required List<DropValue> list, required DropValue value}) {
int index = list.indexWhere((element) {
return element.id == value.id;
});
if (index != -1) {
return true;
}
return false;
}
// Rating filter
double branchFilterByRating = 4.0;
updateBranchFilterByRating(double value) {
branchFilterByRating = value;
notifyListeners();
}
List<DropValue> categoryDropList = [];
List<DropValue> servicesDropList = [];
List<DropValue> providersDropList = [];
Future<void> fetchAllProviders() async {
if (providersDropList.isNotEmpty) return;
providersDropList.clear();
setOnlyState(ViewState.busy);
List<ProviderBasicDataModel> providers = await branchRepo.getAllProvidersWitheBasicData();
for (var element in providers) {
providersDropList.add(
DropValue(element.id ?? 0, element.providerName ?? "N/A", ""),
);
}
setState(ViewState.idle);
}
Future<void> fetchAllCategories(String countryCode) async {
if (categoryDropList.isNotEmpty) return;
categoryDropList.clear();
setOnlyState(ViewState.busy);
Category category = await branchRepo.fetchBranchCategory();
category.data?.forEach((element) {
categoryDropList.add(
DropValue(
element.id ?? 0,
((element.categoryName!.isEmpty
? "N/A"
: countryCode == "SA"
? element.categoryNameN
: element.categoryName) ??
"N/A"),
""),
);
});
setState(ViewState.idle);
}
Future<void> fetchAllServices() async {
if (servicesDropList.isNotEmpty) return;
servicesDropList.clear();
setState(ViewState.busy);
Services services = await branchRepo.fetchServicesByCategoryId(serviceCategoryId: -1); // to get all services
for (var element in services.data!) {
servicesDropList.add(
DropValue(
element.id ?? 0,
element.description ?? "N/A",
"",
),
);
}
setState(ViewState.idle);
}
Future<void> populateDataForBranchesFilter() async {
await fetchAllProviders(); // saudi arabia
await fetchAllCategories("SA"); // saudi arabia
await fetchAllServices(); // saudi arabia
updateBranchFilterCurrentDistance(25.0);
}
void clearBranchFilters() {
branchFilterServicesSearchHistory.clear();
branchFilterCategorySearchHistory.clear();
branchFilterProviderSearchHistory.clear();
branchFilterByRating = 4.0;
branchFilterCurrentDistance = 25.0;
branchFiltersCounter = 0;
clearBranchFilterSelections();
notifyListeners();
}
void clearBranchFilterSelections() {
branchFilterSelectedProviderId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
branchFilterSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
branchFilterSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: "");
}
Future<void> getBranchesBasedOnFilters() async {
nearbyBranches.clear();
setState(ViewState.busy);
List<int> providersIdsList = [];
if (branchFilterProviderSearchHistory.isNotEmpty) {
for (var element in branchFilterProviderSearchHistory) {
providersIdsList.add(element.id);
}
}
List<int> categoryIdsList = [];
if (branchFilterCategorySearchHistory.isNotEmpty) {
for (var element in branchFilterCategorySearchHistory) {
categoryIdsList.add(element.id);
}
}
List<int> servicesIdsList = [];
if (branchFilterServicesSearchHistory.isNotEmpty) {
for (var element in branchFilterServicesSearchHistory) {
servicesIdsList.add(element.id);
}
}
nearbyBranches = await branchRepo.getBranchesByFilters(
providerIdsList: providersIdsList.isNotEmpty ? providersIdsList : null,
categoryIdsList: categoryIdsList.isNotEmpty ? categoryIdsList : null,
serviceIdsList: servicesIdsList.isNotEmpty ? servicesIdsList : null,
distanceKm: branchFilterCurrentDistance.toInt(),
rating: branchFilterByRating.toInt(),
latitude: 24.694969,
longitude: 46.724129,
);
setState(ViewState.idle);
}
Future<void> getBranchesBasedOnCategoryFilters({required int categoryId}) async {
if (categoryId == 0) {
await getAllNearBranches();
return;
}
setState(ViewState.busy);
nearbyBranches.clear();
nearbyBranches = await branchRepo.getBranchesByFilters(categoryIdsList: [categoryId]);
setState(ViewState.idle);
}
}