aDDED cUSTOM cALENDER

aamir_dev
Faiz Hashmi 2 years ago
parent 5bc5ca8954
commit 120dc86309

@ -1,4 +1,5 @@
import 'package:car_customer_app/views/appointments/appointment_detail_view.dart';
import 'package:car_customer_app/views/appointments/book_appointment_schedules_view.dart';
import 'package:car_customer_app/views/appointments/book_appointment_services_view.dart';
import 'package:car_customer_app/views/appointments/book_appointments_item_view.dart';
import 'package:car_customer_app/views/appointments/pick_items_view.dart';
@ -28,6 +29,7 @@ class CustomerAppRoutes {
AppRoutes.adsSearchFilterScreen: (context) => AdsSearchFilterView(),
AppRoutes.selectAdTypeView: (context) => SelectAdTypeView(isProvider: ModalRoute.of(context)!.settings.arguments as bool),
AppRoutes.bookAppointmenServicesView: (context) => BookAppointmentServicesView(),
AppRoutes.bookAppointmenSchedulesView: (context) => BookAppointmentSchedulesView(),
AppRoutes.bookAppointmentsItemView: (context) => BookAppointmentsItemView(),
AppRoutes.reviewAppointmentView: (context) => ReviewAppointment(),
AppRoutes.paymentMethodsView: (context) => PaymentMethodsView(paymentType: ModalRoute.of(context)!.settings.arguments as PaymentTypes),

@ -26,6 +26,7 @@ import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
import 'package:sizer/sizer.dart';
import 'package:intl/date_symbol_data_local.dart';
//test commit
Future<void> main() async {
@ -33,42 +34,41 @@ Future<void> main() async {
CustomerDependencies.addDependencies();
await EasyLocalization.ensureInitialized();
CustomerAppRoutes.routes.addAll(AppRoutes.routes);
runApp(
MultiProvider(
providers: <SingleChildWidget>[
ChangeNotifierProvider<BaseVM>(create: (_) => BaseVM()),
ChangeNotifierProvider<DashboardVM>(
create: (_) => DashboardVM(
commonServices: injector.get<CommonAppServices>(),
userRepo: injector.get<UserRepo>(),
),
),
ChangeNotifierProvider<UserVM>(
create: (_) => UserVM(userRepo: injector.get<UserRepo>()),
),
ChangeNotifierProvider<AdVM>(
create: (_) => AdVM(
commonServices: injector.get<CommonAppServices>(),
commonRepo: injector.get<CommonRepo>(),
adsRepo: injector.get<AdsRepo>(),
),
),
ChangeNotifierProvider<AppointmentsVM>(
create: (_) => AppointmentsVM(
scheduleRepo: injector.get<ScheduleRepo>(),
providerRepo: injector.get<ProviderRepo>(),
commonServices: injector.get<CommonAppServices>(),
commonRepo: injector.get<CommonRepo>(),
),
),
ChangeNotifierProvider<PaymentVM>(
create: (_) => PaymentVM(paymentService: injector.get<PaymentService>(), paymentRepo: injector.get<PaymentsRepo>()),
),
],
child: const MyApp(),
).setupLocale(),
);
initializeDateFormatting().then((_) => runApp(
MultiProvider(
providers: <SingleChildWidget>[
ChangeNotifierProvider<BaseVM>(create: (_) => BaseVM()),
ChangeNotifierProvider<DashboardVM>(
create: (_) => DashboardVM(
commonServices: injector.get<CommonAppServices>(),
userRepo: injector.get<UserRepo>(),
),
),
ChangeNotifierProvider<UserVM>(
create: (_) => UserVM(userRepo: injector.get<UserRepo>()),
),
ChangeNotifierProvider<AdVM>(
create: (_) => AdVM(
commonServices: injector.get<CommonAppServices>(),
commonRepo: injector.get<CommonRepo>(),
adsRepo: injector.get<AdsRepo>(),
),
),
ChangeNotifierProvider<AppointmentsVM>(
create: (_) => AppointmentsVM(
scheduleRepo: injector.get<ScheduleRepo>(),
providerRepo: injector.get<ProviderRepo>(),
commonServices: injector.get<CommonAppServices>(),
commonRepo: injector.get<CommonRepo>(),
),
),
ChangeNotifierProvider<PaymentVM>(
create: (_) => PaymentVM(paymentService: injector.get<PaymentService>(), paymentRepo: injector.get<PaymentsRepo>()),
),
],
child: MyApp(),
).setupLocale(),
));
}
// todo terminal command to generate translation files

@ -21,7 +21,10 @@ abstract class ScheduleRepo {
Future<MResponse> updateServicesInSchedule(Map map);
Future<List<ServiceAppointmentScheduleModel>> mergeServiceIntoAvailableSchedules({required int appointmentType, required List<String> serviceItemIds});
Future<List<ServiceAppointmentScheduleModel>> mergeServiceIntoAvailableSchedules({
required List<String> serviceItemIdsForHome,
required List<String> serviceItemIdsForWorkshop,
});
Future<void> createServiceAppointment({required List<String> serviceItemIds, required int serviceSlotID});
}
@ -73,12 +76,21 @@ class ScheduleRepoImp implements ScheduleRepo {
return await injector.get<ApiClient>().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.updateGroup, map, token: t);
}
Future<List<ServiceAppointmentScheduleModel>> mergeServiceIntoAvailableSchedules({required int appointmentType, required List<String> serviceItemIds}) async {
Future<List<ServiceAppointmentScheduleModel>> mergeServiceIntoAvailableSchedules({
required List<String> serviceItemIdsForHome,
required List<String> serviceItemIdsForWorkshop,
}) async {
String t = AppState().getUser.data!.accessToken ?? "";
var queryParameters = {
"appointmentType": appointmentType,
"ServiceItemIDs": serviceItemIds,
};
var queryParameters = [
{
"appointmentType": 2,
"ServiceItemIDs": serviceItemIdsForHome,
},
{
"appointmentType": 1,
"ServiceItemIDs": serviceItemIdsForWorkshop,
}
];
GenericRespModel adsGenericModel = await injector.get<ApiClient>().postJsonForObject(
(json) => GenericRespModel.fromJson(json),
ApiConsts.GetServiceItemAppointmentScheduleSlots,

@ -1,8 +1,14 @@
import 'dart:developer';
import 'package:car_customer_app/repositories/provider_repo.dart';
import 'package:car_customer_app/repositories/schedule_repo.dart';
import 'package:car_customer_app/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart';
import 'package:flutter/cupertino.dart';
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/provider_branches_models/branch_detail_model.dart';
import 'package:mc_common_app/models/provider_branches_models/provider_profile_model.dart';
@ -12,11 +18,14 @@ import 'package:mc_common_app/models/services/service_model.dart';
import 'package:mc_common_app/models/widgets_models.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/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 AppointmentsVM extends BaseVM {
final CommonRepo commonRepo;
@ -49,45 +58,39 @@ class AppointmentsVM extends BaseVM {
List<DropValue> servicesInSchedule = [];
Future<void> mergeServiceIntoAvailableSchedules() async {
List<String> serviceItemIds = [];
currentServiceSelection!.serviceItems!.forEach((serviceItem) {
if (serviceItem.isUpdateOrSelected!) {
serviceItemIds.add(serviceItem.id!.toString());
}
});
servicesInCurrentAppointment.add(currentServiceSelection!);
serviceAppointmentScheduleList = await scheduleRepo.mergeServiceIntoAvailableSchedules(
serviceItemIds: serviceItemIds,
appointmentType: isHomeTapped ? 2 : 1, // AppointmentType 1 for workshop and 2 for Home based schedule.
);
servicesInSchedule.clear();
if (serviceAppointmentScheduleList.isEmpty) {
Utils.showToast("There are no available appointments for selected Items.");
return;
bool ifServiceAlreadyThere(int id) {
int index = servicesInSchedule.indexWhere((element) => element.id == id);
if (index == -1) {
return false;
}
return true;
}
serviceAppointmentScheduleList.forEach(
(schedule) {
amountToPayForAppointment = amountToPayForAppointment + (schedule.amountToPay ?? 0.0);
schedule.serviceItemList!.forEach((item) {
if (!ifItemAlreadyThere(item.serviceProviderServiceId!)) {
servicesInSchedule.add(DropValue(item.serviceProviderServiceId!, item.description!, ""));
}
});
},
);
notifyListeners();
bool ifItemAlreadySelected(int id) {
int indexFound = allSelectedItemsInAppointments.indexWhere((element) => element.id == id);
if (indexFound != -1) {
return true;
}
return false;
}
List<ItemData> allSelectedItemsInAppointments = [];
Future<void> onItemsSelectedInService() async {
if (currentServiceSelection != null) {
servicesInCurrentAppointment.add(currentServiceSelection!);
int index = servicesInCurrentAppointment.indexWhere((element) => element.serviceId == currentServiceSelection!.serviceId!);
if (index == -1) {
double totalPrice = 0.0;
currentServiceSelection!.serviceItems!.forEach((element) {
totalPrice = totalPrice + double.parse(element.price ?? "0.0");
});
currentServiceSelection!.currentTotalServicePrice = totalPrice;
servicesInCurrentAppointment.insert(0, currentServiceSelection!);
}
resetCategorySelectionBottomSheet();
notifyListeners();
}
}
@ -96,9 +99,9 @@ class AppointmentsVM extends BaseVM {
try {
serviceAppointmentScheduleList.forEach((schedule) async {
List<String> serviceItemIds = [];
schedule.serviceItemList!.forEach((serviceItem) {
serviceItemIds.add(serviceItem.id.toString());
});
// schedule.serviceItemList!.forEach((serviceItem) {
// serviceItemIds.add(serviceItem.id.toString());
// });
await scheduleRepo.createServiceAppointment(serviceItemIds: serviceItemIds, serviceSlotID: schedule.selectedCustomTimeDateSlotModel!.date!.slotId);
});
} catch (e) {
@ -106,16 +109,11 @@ class AppointmentsVM extends BaseVM {
}
}
bool ifItemAlreadyThere(int id) {
int index = servicesInSchedule.indexWhere((element) => element.id == id);
if (index == -1) {
return false;
}
return true;
}
void updateIsHomeTapped(bool value) {
isHomeTapped = value;
if (currentServiceSelection != null) {
currentServiceSelection!.isHomeSelected = value;
}
notifyListeners();
}
@ -124,6 +122,9 @@ class AppointmentsVM extends BaseVM {
void updatePickedHomeLocation(String value) {
pickedHomeLocation = value;
pickHomeLocationError = "";
if (currentServiceSelection != null) {
currentServiceSelection!.homeLocation = value;
}
notifyListeners();
}
@ -217,67 +218,43 @@ class AppointmentsVM extends BaseVM {
int index = serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex!;
serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![index].availableSlots![slotIndex].isSelected = true;
serviceAppointmentScheduleList[scheduleIndex].selectedCustomTimeDateSlotModel!.availableSlots = serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![index].availableSlots!;
print("here: ${serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![index].availableSlots![slotIndex].slotId}");
notifyListeners();
}
double amountToPayForAppointment = 0.0;
double totalAmount = 0.0;
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 = true;
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);
}
List<ItemData> serviceItemsFromApi = [];
ProviderProfileModel? providerProfileModel;
int selectedSubServicesCounter = 0;
updateSelectedSubServicesCounter(int value) {
selectedSubServicesCounter = value;
notifyListeners();
}
onItemUpdateOrSelected(int index, bool selected, int itemId) {
int serviceIndex = servicesInCurrentAppointment.indexWhere((element) => element.serviceId == currentServiceSelection!.serviceId!);
serviceItemsFromApi[index].isUpdateOrSelected = selected;
serviceItemsFromApi[index].isHomeSelected = isHomeTapped;
if (selected) {
selectedSubServicesCounter = selectedSubServicesCounter + 1;
updateSelectedSubServicesCounter(selectedSubServicesCounter);
selectSubServicesError = "";
currentServiceSelection!.serviceItems!.add(serviceItemsFromApi[index]);
allSelectedItemsInAppointments.add(serviceItemsFromApi[index]);
allSelectedItemsInAppointments.forEach((element) {
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;
updateSelectedSubServicesCounter(selectedSubServicesCounter);
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();
}
@ -315,6 +292,11 @@ class AppointmentsVM extends BaseVM {
Future<List<ItemData>> getServiceItems(int serviceId) async {
serviceItemsFromApi.clear();
serviceItemsFromApi = await providerRepo.getServiceItems(serviceId);
serviceItemsFromApi.forEach((item) {
if (ifItemAlreadySelected(item.id!)) {
item.isUpdateOrSelected = true;
}
});
setState(ViewState.idle);
return serviceItemsFromApi;
}
@ -396,26 +378,173 @@ class AppointmentsVM extends BaseVM {
return false;
}
// void mergeServiceInAvailableSchedule() {
// log("schedules: ${availableSchedules}");
// for (var schedule in availableSchedules) {
// for (var service in schedule.scheduleServices!) {
// if (branchSelectedServiceId.selectedId == service.serviceId) {
// log("ID matched: ${service.serviceId}");
// log("SelectedServices: ${schedule.selectedServices}");
//
//
// int isAlreadyThereIndex = schedule.selectedServices!.indexWhere((element) => element.serviceProviderServiceId == branchSelectedServiceId.selectedId);
// if (isAlreadyThereIndex != -1) {
// log("removing: ${service.serviceId}");
// schedule.selectedServices!.removeAt(isAlreadyThereIndex);
// }
// schedule.selectedServices!.add(currentServiceSelection!);
// notifyListeners();
// return;
// }
// }
// }
// notifyListeners();
// }
String getTotalPrice(List<ServiceModel> serviceItems) {
var totalPrice = 0.0;
serviceItems.forEach((element) {
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 ?? 0.0) + (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 = true;
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 = [];
allSelectedItemsInAppointments.forEach((serviceItem) {
if (serviceItem.isHomeSelected!) {
serviceItemIdsForHome.add(serviceItem.id!.toString());
} else {
serviceItemIdsForWorkshop.add(serviceItem.id!.toString());
}
});
servicesInSchedule.clear();
//TODO: WE HAVE TO ADD PARAMETER IN THE allSelectedItemsInAppointments TO DECIDE WHETHER THE APPOINTMENT IS FOR HOME OR WORKSHOP
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;
}
serviceAppointmentScheduleList.forEach(
(schedule) {
amountToPayForAppointment = amountToPayForAppointment + (schedule.amountToPay ?? 0.0);
schedule.servicesListInAppointment!.forEach((service) {
if (!ifServiceAlreadyThere(service.serviceProviderServiceId!)) {
servicesInSchedule.add(DropValue(service.serviceProviderServiceId!, service.providerServiceDescription!, ""));
}
});
},
);
Utils.hideLoading(context);
navigateWithName(context, AppRoutes.bookAppointmenSchedulesView);
notifyListeners();
}
}

@ -1,9 +1,160 @@
import 'package:car_customer_app/view_models/appointments_view_model.dart';
import 'package:car_customer_app/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart';
import 'package:car_customer_app/views/appointments/widgets/custom_calender_widget.dart';
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/service_schedule_model.dart';
import 'package:mc_common_app/models/services/item_model.dart';
import 'package:mc_common_app/models/widgets_models.dart';
import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/utils/navigator.dart';
import 'package:mc_common_app/views/advertisement/custom_add_button.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/common_widgets/time_slots.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 BookAppointmentSchedulesView extends StatelessWidget {
const BookAppointmentSchedulesView({super.key});
BookAppointmentSchedulesView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Placeholder();
return Scaffold(
appBar: CustomAppBar(
title: "Book Appointment",
isRemoveBackButton: false,
isDrawerEnabled: false,
actions: [MyAssets.searchIcon.buildSvg().paddingOnly(right: 21)],
onBackButtonTapped: () => Navigator.pop(context),
),
body: Consumer(
builder: (BuildContext context, AppointmentsVM appointmentsVM, Widget? child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
21.height,
ListView.builder(
shrinkWrap: true,
itemCount: appointmentsVM.serviceAppointmentScheduleList.length,
itemBuilder: (BuildContext context, int scheduleIndex) {
ServiceAppointmentScheduleModel scheduleData = appointmentsVM.serviceAppointmentScheduleList[scheduleIndex];
return ExpansionTile(
tilePadding: EdgeInsets.symmetric(horizontal: 21, vertical: 10),
childrenPadding: EdgeInsets.only(left: 16, bottom: 10, right: 16),
title: Column(
children: [
Row(
children: [
Expanded(
child: "Schedule ${scheduleIndex + 1}".toText(fontSize: 20, isBold: true),
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Service Location: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
("${scheduleData.appointmentType == 2 ? "Home" : "Workshop"}").toText(fontSize: 12, isBold: true).expand(),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
5.height,
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: appointmentsVM.servicesInSchedule.length,
itemBuilder: (BuildContext context, int serviceIndex) {
DropValue selectedService = appointmentsVM.servicesInSchedule[serviceIndex];
return Row(
children: [
Expanded(
child: ("${serviceIndex + 1}. ${selectedService.value}").toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor),
),
],
);
},
),
],
),
],
),
children: [
Column(
children: [
if (true) ...[
// SizedBox(
// width: double.infinity,
// child: BuildDateSlotsForAppointment(
// customDateSlots: scheduleData.customTimeDateSlotList ?? [],
// onPressed: (dateIndex) {
// appointmentsVM.updateSelectedAppointmentDate(scheduleIndex: scheduleIndex, dateIndex: dateIndex);
// },
// ),
// ),
CustomCalenderWidget(customTimeDateSlotList: scheduleData.customTimeDateSlotList ?? []),
],
if (appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex != null) ...[
5.height,
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
("Available Slots").toText(fontSize: 14, isBold: true),
],
),
5.height,
SizedBox(
width: double.infinity,
child: BuildTimeSlots(
timeSlots: appointmentsVM.serviceAppointmentScheduleList[scheduleIndex]
.customTimeDateSlotList![appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex!].availableSlots ??
[],
onPressed: (slotIndex) {
appointmentsVM.updateSelectedAppointmentSlotByDate(scheduleIndex: scheduleIndex, slotIndex: slotIndex);
},
),
),
],
],
),
],
).toWhiteContainer(width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 10));
},
).expand(),
Row(
children: [
Expanded(
child: ShowFillButton(
txtColor: MyColors.black,
maxHeight: 55,
title: "Cancel",
onPressed: () {},
backgroundColor: MyColors.greyButtonColor,
),
),
12.width,
Expanded(
child: ShowFillButton(
maxHeight: 55,
title: "Review",
onPressed: () {
appointmentsVM.onReviewButtonPressed(context);
},
backgroundColor: MyColors.darkPrimaryColor,
),
)
],
).paddingAll(21)
],
);
},
));
}
}

@ -1,135 +1,20 @@
import 'package:car_customer_app/view_models/appointments_view_model.dart';
import 'package:car_customer_app/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart';
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/service_schedule_model.dart';
import 'package:mc_common_app/models/services/item_model.dart';
import 'package:mc_common_app/models/widgets_models.dart';
import 'package:mc_common_app/models/services/service_model.dart';
import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/utils/navigator.dart';
import 'package:mc_common_app/views/advertisement/custom_add_button.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/common_widgets/time_slots.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 BookAppointmentServicesView extends StatelessWidget {
BookAppointmentServicesView({Key? key}) : super(key: key);
void openTheAddServiceBottomSheet(BuildContext context, AppointmentsVM appointmentsVM) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: true,
builder: (BuildContext context) {
return AppointmentServicePickBottomSheet();
},
);
}
final _dummySlots = [
TimeSlotModel(isSelected: false, slotId: 1, slot: "11:00"),
TimeSlotModel(isSelected: false, slotId: 2, slot: "12:00"),
TimeSlotModel(isSelected: true, slotId: 3, slot: "13:00"),
TimeSlotModel(isSelected: false, slotId: 4, slot: "14:00"),
TimeSlotModel(isSelected: false, slotId: 5, slot: "15:00"),
TimeSlotModel(isSelected: false, slotId: 6, slot: "16:00"),
TimeSlotModel(isSelected: false, slotId: 7, slot: "17:00"),
TimeSlotModel(isSelected: false, slotId: 8, slot: "18:00"),
TimeSlotModel(isSelected: false, slotId: 9, slot: "19:00"),
TimeSlotModel(isSelected: false, slotId: 10, slot: "20:00"),
TimeSlotModel(isSelected: false, slotId: 11, slot: "21:00"),
TimeSlotModel(isSelected: false, slotId: 12, slot: "22:00"),
TimeSlotModel(isSelected: false, slotId: 13, slot: "23:00"),
TimeSlotModel(isSelected: false, slotId: 14, slot: "24:00"),
TimeSlotModel(isSelected: false, slotId: 15, slot: "25:00"),
TimeSlotModel(isSelected: false, slotId: 16, slot: "26:00"),
TimeSlotModel(isSelected: false, slotId: 17, slot: "27:00"),
TimeSlotModel(isSelected: false, slotId: 18, slot: "28:00"),
TimeSlotModel(isSelected: false, slotId: 19, slot: "29:00"),
];
void priceBreakDownClicked(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: true,
builder: (BuildContext context) {
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(
5,
(index) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Gear Kit".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
"200 SAR".toText(fontSize: 12, isBold: true),
],
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
"1149 SAR".toText(fontSize: 16, isBold: true),
],
),
20.height,
"Home Location".toText(fontSize: 16, isBold: true),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"10km ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
"5 x 10".toText(fontSize: 12, isBold: true),
],
),
8.height,
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
"50 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: [
"120".toText(fontSize: 29, isBold: true),
2.width,
"SAR".toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5),
],
)
],
),
30.height,
],
));
});
}
String getTotalPrice(List<ItemData> serviceItems) {
var totalPrice = 0.0;
serviceItems.forEach((element) {
totalPrice = totalPrice + double.parse(element.price ?? "0.0");
});
return totalPrice.toString();
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -149,7 +34,7 @@ class BookAppointmentServicesView extends StatelessWidget {
CustomAddButton(
needsBorder: true,
bgColor: MyColors.white,
onTap: () => openTheAddServiceBottomSheet(context, appointmentsVM),
onTap: () => appointmentsVM.openTheAddServiceBottomSheet(context, appointmentsVM),
text: "Add Services",
icon: Container(
height: 24,
@ -162,70 +47,52 @@ class BookAppointmentServicesView extends StatelessWidget {
ListView.builder(
shrinkWrap: true,
itemCount: appointmentsVM.servicesInCurrentAppointment.length,
itemBuilder: (BuildContext context, int scheduleIndex) {
ServiceAppointmentScheduleModel scheduleData = appointmentsVM.serviceAppointmentScheduleList[scheduleIndex];
itemBuilder: (BuildContext context, int serviceIndex) {
ServiceModel serviceData = appointmentsVM.servicesInCurrentAppointment[serviceIndex];
return Column(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
Row(
children: [
ListView.separated(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: appointmentsVM.servicesInSchedule.length,
itemBuilder: (BuildContext context, int serviceIndex) {
DropValue selectedService = appointmentsVM.servicesInSchedule[serviceIndex];
return Column(
children: [
Row(
children: [
Expanded(
child: selectedService.value.toText(fontSize: 15, isBold: true),
),
],
),
if (true) ...[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Service Location: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
("Home").toText(fontSize: 12, isBold: true).expand(),
],
),
5.height,
Column(
children: List.generate(scheduleData.serviceItemList!.length, (itemIndex) {
ItemData itemData = scheduleData.serviceItemList![itemIndex];
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"${itemData.name}: ".toText(fontSize: 13, color: MyColors.lightTextColor, isBold: true),
("${itemData.price}").toText(fontSize: 13, isBold: true).expand(),
],
);
}),
),
],
],
);
},
separatorBuilder: (BuildContext context, int index) => Divider(thickness: 2),
Expanded(
child: (serviceData.serviceDescription ?? "").toText(fontSize: 15, isBold: true),
),
8.height,
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
getTotalPrice(appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].serviceItemList ?? []).toText(fontSize: 32, isBold: true),
2.width,
"SAR".toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5),
Icon(
Icons.arrow_drop_down,
size: 30,
)
],
).onPress(() => priceBreakDownClicked(context)),
],
),
if (true) ...[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Service Location: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
(serviceData.isHomeSelected ? serviceData.homeLocation : "Workshop").toText(fontSize: 12, isBold: true).expand(),
],
),
5.height,
Column(
children: List.generate(serviceData.serviceItems!.length, (itemIndex) {
ItemData itemData = serviceData.serviceItems![itemIndex];
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"${itemData.name}: ".toText(fontSize: 13, color: MyColors.lightTextColor, isBold: true),
("${itemData.price}").toText(fontSize: 13, isBold: true).expand(),
],
);
}),
),
8.height,
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
((appointmentsVM.servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice).toString()).toText(fontSize: 32, isBold: true),
2.width,
"SAR".toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5),
Icon(
Icons.arrow_drop_down,
size: 30,
)
],
).onPress(() => appointmentsVM.priceBreakDownClicked(context, appointmentsVM.servicesInCurrentAppointment[serviceIndex])),
],
],
).toWhiteContainer(width: double.infinity, allPading: 12, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 10));
},
@ -245,9 +112,9 @@ class BookAppointmentServicesView extends StatelessWidget {
Expanded(
child: ShowFillButton(
maxHeight: 55,
title: "Review",
title: "Next",
onPressed: () {
appointmentsVM.onReviewButtonPressed(context);
appointmentsVM.onServicesNextPressed(context);
},
backgroundColor: MyColors.darkPrimaryColor,
),

@ -7,6 +7,7 @@ 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/service_schedule_model.dart';
import 'package:mc_common_app/models/services/item_model.dart';
import 'package:mc_common_app/models/services/service_model.dart';
import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/widgets/button/show_fill_button.dart';
import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
@ -104,7 +105,6 @@ class ReviewAppointment extends StatelessWidget {
shrinkWrap: true,
itemCount: appointmentsVM.servicesInSchedule.length,
itemBuilder: (BuildContext context, int serviceIndex) {
DropValue selectedService = appointmentsVM.servicesInSchedule[serviceIndex];
String selectedTimeSlot = "";
if (scheduleData.selectedCustomTimeDateSlotModel!.availableSlots != null) {
selectedTimeSlot = scheduleData.selectedCustomTimeDateSlotModel!.availableSlots!.firstWhere((element) => element.isSelected).slot;
@ -128,12 +128,12 @@ class ReviewAppointment extends StatelessWidget {
),
5.height,
Column(
children: List.generate(scheduleData.serviceItemList!.length, (itemIndex) {
ItemData itemData = scheduleData.serviceItemList![itemIndex];
children: List.generate(scheduleData.servicesListInAppointment!.length, (itemIndex) {
ServiceModel serviceData = scheduleData.servicesListInAppointment![itemIndex];
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"${itemData.name}: ${itemData.price} SAR".toText(fontSize: 13, color: MyColors.lightTextColor, isBold: true),
"${serviceData.providerServiceDescription}".toText(fontSize: 13, color: MyColors.lightTextColor, isBold: true),
],
);
}),
@ -196,12 +196,12 @@ class ReviewAppointment extends StatelessWidget {
AppointmentsVM appointmentsVM = context.read<AppointmentsVM>();
List<ItemData> allSelectedItems = [];
double totalServicePrice = 0.0;
appointmentsVM.serviceAppointmentScheduleList.forEach((schedule) {
schedule.serviceItemList!.forEach((item) {
allSelectedItems.add(item);
totalServicePrice = totalServicePrice + double.parse(item.price!);
});
});
// appointmentsVM.serviceAppointmentScheduleList.forEach((schedule) {
// schedule.servicesListInAppointment!.forEach((service) {
// allSelectedItems.add(item);
// totalServicePrice = totalServicePrice + double.parse(item.price!);
// });
// });
return Column(
crossAxisAlignment: CrossAxisAlignment.start,

@ -0,0 +1,215 @@
import 'dart:developer';
import 'package:car_customer_app/view_models/appointments_view_model.dart';
import 'package:flutter/material.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/service_schedule_model.dart';
import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
class CustomCalenderWidget extends StatefulWidget {
final List<CustomTimeDateSlotModel>? customTimeDateSlotList;
const CustomCalenderWidget({super.key, required this.customTimeDateSlotList});
@override
State<CustomCalenderWidget> createState() => _CustomCalenderWidgetState();
}
class _CustomCalenderWidgetState extends State<CustomCalenderWidget> {
List<DateTime> allDates = [];
List<DropValue> allMonths = [];
List<DateTime> datesInSelectedMonth = [];
CalendarFormat _calendarFormat = CalendarFormat.month;
late int selectedMonth;
late int selectedYear;
DateTime? _selectedDay;
DateTime _focusedDay = DateTime.now();
@override
void initState() {
super.initState();
populateDateList();
}
populateDateList() {
for (var value in widget.customTimeDateSlotList!) {
DateTime dt = DateFormat('dd MMMM, yyyy').parse(value.date!.date);
// TODO: THIS COMPARISON NEEDS TO BE FIXED!!!!!
// TODO: BECAUSE WE CAN ONLY VALUE IN DROPDOWN, SO SUBVALUE IS OF NO USE!!
DropValue dv = DropValue(dt.month, "${dt.month.getMonthNameByNumber()}, ${dt.year}", "");
allDates.add(dt);
if (!ifMonthAlreadyThere(dv)) {
allMonths.add(dv);
}
}
selectedMonth = allDates.first.month;
selectedYear = allDates.first.year;
datesInSelectedMonth = allDates.where((element) => element.month == selectedMonth).toList();
}
bool ifMonthAlreadyThere(DropValue monthDate) {
int index = allMonths.indexWhere((element) => element.id == monthDate.id && element.subValue == monthDate.subValue);
if (index == -1) {
return false;
}
return true;
}
bool ifDateAlreadyThere(DateTime dt) {
int index = allDates.indexWhere((element) => dt.month == element.month && dt.year == element.year);
if (index == -1) {
return false;
}
return true;
}
@override
Widget build(BuildContext context) {
return Consumer(
builder: (BuildContext context, AppointmentsVM appointmentsVM, Widget? child) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Builder(builder: (context) {
return DropdownField(
(DropValue value) {
setState(() {
selectedYear = int.parse(value.value.split(',')[1]);
selectedMonth = value.value.split(',')[0].getMonthNumberByName();
});
},
list: allMonths,
// dropdownValue: DropValue(1, "${selectedMonth.getMonthNameByNumber()}jhvk,", "${selectedYear}"),
hint: "${selectedMonth.getMonthNameByNumber()}, $selectedYear",
errorValue: "",
showAppointmentPickerVariant: true,
);
}),
),
Spacer(),
Icon(
Icons.calendar_today,
color: Colors.black,
size: 18,
).paddingOnly(right: 10)
],
).paddingOnly(left: 6, right: 0),
TableCalendar(
headerVisible: false,
firstDay: datesInSelectedMonth.first,
lastDay: datesInSelectedMonth.last,
focusedDay: _focusedDay,
calendarFormat: _calendarFormat,
weekendDays: [DateTime.friday, DateTime.saturday],
daysOfWeekHeight: 30,
availableGestures: AvailableGestures.none,
daysOfWeekStyle: DaysOfWeekStyle(
weekdayStyle: TextStyle(fontSize: 14, color: MyColors.black),
weekendStyle: TextStyle(fontSize: 14, color: MyColors.black.withOpacity(0.5)),
),
calendarBuilders: CalendarBuilders(
todayBuilder: (BuildContext context, DateTime dateTime1, DateTime dateTime2) {
return Container(
height: 50,
width: 50,
margin: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.blueAccent.withOpacity(0.4),
border: Border.all(color: Colors.orange, width: 2),
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
dateTime1.day.toString(),
),
);
},
selectedBuilder: (BuildContext context, DateTime dateTime1, DateTime dateTime2) {
return Container(
height: 50,
width: 50,
margin: EdgeInsets.all(5),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: MyColors.darkIconColor,
),
alignment: Alignment.center,
child: Text(
dateTime2.day.toString(),
style: TextStyle(color: MyColors.white),
),
);
},
defaultBuilder: (BuildContext context, DateTime dateTime1, DateTime dateTime2) {
return Container(
height: 50,
width: 50,
margin: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(color: Colors.orange, width: 2),
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
dateTime1.day.toString(),
),
);
},
disabledBuilder: (BuildContext context, DateTime dateTime1, DateTime selectedDt) {
return Container(
height: 50,
width: 50,
margin: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
dateTime1.day.toString(),
),
);
},
),
headerStyle: HeaderStyle(formatButtonVisible: false),
selectedDayPredicate: (day) {
// Use `selectedDayPredicate` to determine which day is currently selected.
// If this returns true, then `day` will be marked as selected.
// Using `isSameDay` is recommended to disregard
// the time-part of compared DateTime objects.
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
if (!isSameDay(_selectedDay, selectedDay)) {
// Call `setState()` when updating the selected day
setState(() {
_selectedDay = selectedDay;
_focusedDay = selectedDay;
});
}
},
),
],
);
},
);
}
}

@ -179,7 +179,7 @@ class _BranchDetailPageState extends State<BranchDetailPage> {
showItem("Charges per KM", widget.branchDetailModel.branchServices![index].customerLocationRange.toString() + "SAR"),
8.height,
((widget.branchDetailModel.branchServices![index].itemsCount != null && widget.branchDetailModel.branchServices![index].itemsCount! > 0)
? widget.branchDetailModel.branchServices![index].itemsCount.toString() + " items"
? widget.branchDetailModel.branchServices![index].itemsCount.toString() + " items"
: "No" + " items")
.toText(
fontSize: 12,

@ -47,7 +47,7 @@ class _ItemsListSheetState extends State<ItemsListSheet> {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
serviceItemModel.toString().toText(fontSize: 16, isBold: true),
serviceItemModel.name.toString().toText(fontSize: 16, isBold: true),
4.height,
showItem("Available for appointment:", (serviceItemModel.isAllowAppointment ?? false) ? "Yes" : "No", valueColor: Colors.green),
showItem("Allowing Workshop service:", (serviceItemModel.isAppointmentCompanyLoc ?? false) ? "Yes" : "No", valueColor: Colors.green),

@ -34,6 +34,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
table_calendar: ^3.0.9
mc_common_app:
path: /Volumes/Data/Projects/Flutter/car_common_app

Loading…
Cancel
Save