Merge branch 'faiz_development'

aamir_dev
Faiz Hashmi 2 years ago
commit 54fd8736c7

@ -1,12 +1,12 @@
import 'package:car_customer_app/repositories/provider_repo.dart'; import 'package:car_customer_app/repositories/provider_repo.dart';
import 'package:car_customer_app/repositories/schedule_repo.dart'; import 'package:car_customer_app/repositories/appointment_repo.dart';
import 'package:mc_common_app/config/dependencies.dart'; import 'package:mc_common_app/config/dependencies.dart';
class CustomerDependencies { class CustomerDependencies {
static void addDependencies() { static void addDependencies() {
AppDependencies.addDependencies(); AppDependencies.addDependencies();
injector.registerSingleton<ProviderRepo>(() => ProviderRepoImp()); injector.registerSingleton<ProviderRepo>(() => ProviderRepoImp());
injector.registerSingleton<ScheduleRepo>(() => ScheduleRepoImp()); injector.registerSingleton<AppointmentRepo>(() => ScheduleRepoImp());
} }
} }

@ -1,4 +1,5 @@
import 'package:car_customer_app/views/appointments/appointment_detail_view.dart'; 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_appointment_services_view.dart';
import 'package:car_customer_app/views/appointments/book_appointments_item_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'; import 'package:car_customer_app/views/appointments/pick_items_view.dart';
@ -16,21 +17,24 @@ import 'package:mc_common_app/views/advertisement/ads_detail_view.dart';
import 'package:mc_common_app/views/advertisement/create_ad_view.dart'; import 'package:mc_common_app/views/advertisement/create_ad_view.dart';
import 'package:mc_common_app/views/advertisement/select_ad_type_view.dart'; import 'package:mc_common_app/views/advertisement/select_ad_type_view.dart';
import 'package:mc_common_app/views/payments/payment_methods_view.dart'; import 'package:mc_common_app/views/payments/payment_methods_view.dart';
import 'package:mc_common_app/views/advertisement/ads_search_filter_view.dart'; import 'package:mc_common_app/views/advertisement/ads_filter_view.dart';
class CustomerAppRoutes { class CustomerAppRoutes {
static final Map<String, WidgetBuilder> routes = { static final Map<String, WidgetBuilder> routes = {
AppRoutes.dashboard: (context) => DashboardPage(), AppRoutes.dashboard: (context) => DashboardPage(),
AppRoutes.bookProviderAppView: (context) => BookProviderAppView(), AppRoutes.bookProviderAppView: (context) => BookProviderAppView(),
AppRoutes.appointmentDetailView: (context) => AppointmentDetailView(appointmentListModel: ModalRoute.of(context)!.settings.arguments as AppointmentListModel), AppRoutes.appointmentDetailView: (context) => AppointmentDetailView(appointmentListModel: ModalRoute.of(context)!.settings.arguments as AppointmentListModel),
AppRoutes.adsDetailView: (context) => AdsDetailView(adDetails: ModalRoute.of(context)!.settings.arguments as AdDetailsModel), AppRoutes.adsDetailView: (context) => AdsDetailView(adDetails: ModalRoute.of(context)!.settings.arguments as AdDetailsModel),
AppRoutes.createAdView: (context) => CreateAdView(), AppRoutes.createAdView: (context) => CreateAdView(),
AppRoutes.adsSearchFilterScreen: (context) => AdsSearchFilterView(), AppRoutes.adsFilterView: (context) => AdsFilterView(),
AppRoutes.selectAdTypeView: (context) => SelectAdTypeView(isProvider: ModalRoute.of(context)!.settings.arguments as bool), AppRoutes.selectAdTypeView: (context) => SelectAdTypeView(isProvider: ModalRoute.of(context)!.settings.arguments as bool),
AppRoutes.bookAppointmenServicesView: (context) => BookAppointmentServicesView(), AppRoutes.bookAppointmenServicesView: (context) => BookAppointmentServicesView(),
AppRoutes.bookAppointmenSchedulesView: (context) => BookAppointmentSchedulesView(
screenArgumentsForAppointmentDetailPage: ModalRoute.of(context)!.settings.arguments as ScreenArgumentsForAppointmentDetailPage,
),
AppRoutes.bookAppointmentsItemView: (context) => BookAppointmentsItemView(), AppRoutes.bookAppointmentsItemView: (context) => BookAppointmentsItemView(),
AppRoutes.reviewAppointmentView: (context) => ReviewAppointment(), AppRoutes.reviewAppointmentView: (context) => ReviewAppointment(),
AppRoutes.paymentMethodsView: (context) => PaymentMethodsView(paymentType: ModalRoute.of(context)!.settings.arguments as PaymentTypesEnum), AppRoutes.paymentMethodsView: (context) => PaymentMethodsView(paymentType: ModalRoute.of(context)!.settings.arguments as PaymentTypes),
AppRoutes.branchDetailPage: (context) => BranchDetailPage(branchDetailModel: ModalRoute.of(context)!.settings.arguments as BranchDetailModel), AppRoutes.branchDetailPage: (context) => BranchDetailPage(branchDetailModel: ModalRoute.of(context)!.settings.arguments as BranchDetailModel),
AppRoutes.providerProfilePage: (context) => ProviderProfilePage(providerId: ModalRoute.of(context)!.settings.arguments as int), AppRoutes.providerProfilePage: (context) => ProviderProfilePage(providerId: ModalRoute.of(context)!.settings.arguments as int),
}; };

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

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:mc_common_app/api/api_client.dart'; import 'package:mc_common_app/api/api_client.dart';
import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/app_state.dart';
import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/classes/consts.dart';
@ -7,8 +9,9 @@ import 'package:mc_common_app/models/m_response.dart';
import 'package:mc_common_app/models/provider_branches_models/profile/services.dart'; import 'package:mc_common_app/models/provider_branches_models/profile/services.dart';
import 'package:mc_common_app/models/schedule_model.dart'; import 'package:mc_common_app/models/schedule_model.dart';
import 'package:mc_common_app/models/service_schedule_model.dart'; import 'package:mc_common_app/models/service_schedule_model.dart';
import 'package:mc_common_app/utils/enums.dart';
abstract class ScheduleRepo { abstract class AppointmentRepo {
Future<Services> getAllServices(String branchId); Future<Services> getAllServices(String branchId);
Future<MResponse> createSchedule(Map map); Future<MResponse> createSchedule(Map map);
@ -21,10 +24,17 @@ abstract class ScheduleRepo {
Future<MResponse> updateServicesInSchedule(Map map); Future<MResponse> updateServicesInSchedule(Map map);
Future<List<ServiceAppointmentScheduleModel>> mergeServiceIntoAvailableSchedules({required List<String> serviceItemIds}); Future<List<ServiceAppointmentScheduleModel>> mergeServiceIntoAvailableSchedules({
required List<String> serviceItemIdsForHome,
required List<String> serviceItemIdsForWorkshop,
});
Future<GenericRespModel> createServiceAppointment({required List<ServiceAppointmentScheduleModel> schedules, required int serviceProviderID});
Future<GenericRespModel> cancelOrRescheduleServiceAppointment({required int serviceAppointmentID, required int serviceSlotID, required int appointmentScheduleAction});
} }
class ScheduleRepoImp implements ScheduleRepo { class ScheduleRepoImp implements AppointmentRepo {
@override @override
Future<Services> getAllServices(String branchId) async { Future<Services> getAllServices(String branchId) async {
Map<String, dynamic> map = {"ProviderBranchID": branchId}; Map<String, dynamic> map = {"ProviderBranchID": branchId};
@ -71,18 +81,79 @@ class ScheduleRepoImp implements ScheduleRepo {
return await injector.get<ApiClient>().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.updateGroup, map, token: t); return await injector.get<ApiClient>().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.updateGroup, map, token: t);
} }
Future<List<ServiceAppointmentScheduleModel>> mergeServiceIntoAvailableSchedules({required List<String> serviceItemIds}) async { Future<List<ServiceAppointmentScheduleModel>> mergeServiceIntoAvailableSchedules({
required List<String> serviceItemIdsForHome,
required List<String> serviceItemIdsForWorkshop,
}) async {
String t = AppState().getUser.data!.accessToken ?? ""; String t = AppState().getUser.data!.accessToken ?? "";
var dummyServiceIds = ["3", "2"]; var queryParameters = [
var queryParameters = {"ServiceItemIDs": dummyServiceIds}; {
GenericRespModel adsGenericModel = await injector.get<ApiClient>().getJsonForObject( "appointmentType": 2,
"ServiceItemIDs": serviceItemIdsForHome,
},
{
"appointmentType": 1,
"ServiceItemIDs": serviceItemIdsForWorkshop,
}
];
GenericRespModel adsGenericModel = await injector.get<ApiClient>().postJsonForObject(
(json) => GenericRespModel.fromJson(json), (json) => GenericRespModel.fromJson(json),
ApiConsts.GetServiceItemAppointmentScheduleSlots, ApiConsts.GetServiceItemAppointmentScheduleSlots,
queryParameters,
token: t, token: t,
queryParameters: queryParameters,
); );
List<ServiceAppointmentScheduleModel> serviceAppointmentScheduleModel = List<ServiceAppointmentScheduleModel> serviceAppointmentScheduleModel =
List.generate(adsGenericModel.data.length, (index) => ServiceAppointmentScheduleModel.fromJson(adsGenericModel.data[index])); List.generate(adsGenericModel.data.length, (index) => ServiceAppointmentScheduleModel.fromJson(adsGenericModel.data[index], isForAppointment: true));
return serviceAppointmentScheduleModel; return serviceAppointmentScheduleModel;
} }
Future<GenericRespModel> createServiceAppointment({required List<ServiceAppointmentScheduleModel> schedules, required int serviceProviderID}) async {
String t = AppState().getUser.data!.accessToken ?? "";
int customerId = AppState().getUser.data!.userInfo!.customerId ?? 0;
List<Map<String, dynamic>> mapList = [];
schedules.forEach((schedule) {
List<int> serviceItemIds = [];
schedule.servicesListInAppointment!.forEach((service) {
service.serviceItems!.forEach((item) {
serviceItemIds.add(item.id!);
});
});
mapList.add({
"serviceSlotID": schedule.selectedCustomTimeDateSlotModel!.date!.slotId,
"serviceProviderID": serviceProviderID,
"customerID": customerId,
"serviceItemID": serviceItemIds,
});
});
GenericRespModel adsGenericModel = await injector.get<ApiClient>().postJsonForObject(
(json) => GenericRespModel.fromJson(json),
ApiConsts.ServiceProvidersAppointmentCreate,
mapList,
token: t,
);
return adsGenericModel;
}
@override
Future<GenericRespModel> cancelOrRescheduleServiceAppointment({required int serviceAppointmentID, required int serviceSlotID, required int appointmentScheduleAction}) async {
String t = AppState().getUser.data!.accessToken ?? "";
final payload = {
"serviceAppointmentID": serviceAppointmentID,
"serviceSlotID": serviceSlotID,
"appointmentScheduleAction": appointmentScheduleAction,
};
GenericRespModel adsGenericModel = await injector.get<ApiClient>().postJsonForObject(
(json) => GenericRespModel.fromJson(json),
ApiConsts.ServiceProviderAppointmentRescheduleCancelAppointment,
payload,
token: t,
);
return adsGenericModel;
}
} }

@ -1,7 +1,15 @@
import 'package:car_customer_app/repositories/provider_repo.dart'; import 'package:car_customer_app/repositories/provider_repo.dart';
import 'package:car_customer_app/repositories/schedule_repo.dart'; import 'package:car_customer_app/repositories/appointment_repo.dart';
import 'package:car_customer_app/view_models/dashboard_view_model.dart';
import 'package:car_customer_app/views/appointments/book_appointment_schedules_view.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/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_list_model.dart';
import 'package:mc_common_app/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_detail_model.dart';
import 'package:mc_common_app/models/provider_branches_models/provider_profile_model.dart'; import 'package:mc_common_app/models/provider_branches_models/provider_profile_model.dart';
import 'package:mc_common_app/models/service_schedule_model.dart'; import 'package:mc_common_app/models/service_schedule_model.dart';
@ -10,21 +18,30 @@ import 'package:mc_common_app/models/services/service_model.dart';
import 'package:mc_common_app/models/widgets_models.dart'; import 'package:mc_common_app/models/widgets_models.dart';
import 'package:mc_common_app/repositories/common_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/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/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/base_view_model.dart';
import 'package:mc_common_app/view_models/payment_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/dropdown/dropdow_field.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart';
class AppointmentsVM extends BaseVM { class AppointmentsVM extends BaseVM {
final CommonRepo commonRepo; final CommonRepo commonRepo;
final CommonAppServices commonServices; final CommonAppServices commonServices;
final ProviderRepo providerRepo; final ProviderRepo providerRepo;
final ScheduleRepo scheduleRepo; final AppointmentRepo scheduleRepo;
AppointmentsVM({required this.commonServices, required this.scheduleRepo, required this.providerRepo, required this.commonRepo}); AppointmentsVM({required this.commonServices, required this.scheduleRepo, required this.providerRepo, required this.commonRepo});
bool isFetchingLists = false; bool isFetchingLists = false;
List<AppointmentListModel> myAppointments = []; List<AppointmentListModel> myAppointments = [];
List<AppointmentListModel> myUpComingAppointments = [];
List<AppointmentListModel> myFilteredAppointments = [];
List<FilterListModel> appointmentsFilterOptions = []; List<FilterListModel> appointmentsFilterOptions = [];
// List<ScheduleData> availableSchedules = []; // List<ScheduleData> availableSchedules = [];
@ -37,44 +54,118 @@ class AppointmentsVM extends BaseVM {
List<ServiceAppointmentScheduleModel> serviceAppointmentScheduleList = []; List<ServiceAppointmentScheduleModel> serviceAppointmentScheduleList = [];
// Future<void> getSchedulesByBranchId() async { bool ifItemAlreadySelected(int id) {
// availableSchedules = await scheduleRepo.getSchedules(selectedBranchModel!.id!.toString()); int indexFound = allSelectedItemsInAppointments.indexWhere((element) => element.id == id);
// log("schedules: ${availableSchedules.toString()}"); if (indexFound != -1) {
// notifyListeners(); return true;
// } }
return false;
}
List<DropValue> servicesInSchedule = []; List<ItemData> allSelectedItemsInAppointments = [];
Future<void> mergeServiceIntoAvailableSchedules() async { Future<void> onItemsSelectedInService() async {
List<String> serviceItemIds = []; if (currentServiceSelection != null) {
serviceItems.forEach((serviceItem) { int index = servicesInCurrentAppointment.indexWhere((element) => element.serviceId == currentServiceSelection!.serviceId!);
if (serviceItem.isUpdateOrSelected!) {
serviceItemIds.add(serviceItem.id!.toString()); 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!);
} }
});
var scheduleList = await scheduleRepo.mergeServiceIntoAvailableSchedules(serviceItemIds: serviceItemIds); resetCategorySelectionBottomSheet();
serviceAppointmentScheduleList.addAll(scheduleList); notifyListeners();
servicesInSchedule.clear(); }
serviceAppointmentScheduleList.forEach((schedule) { }
schedule.serviceItemList!.forEach((item) {
if (!ifItemAlreadyThere(item.serviceProviderServiceId!)) { Future<void> onBookAppointmentPressed(BuildContext context) async {
servicesInSchedule.add(DropValue(item.serviceProviderServiceId!, item.description!, "")); Utils.showLoading(context);
bool isSuccess = false;
List<int> appointmentIdsList = [];
try {
GenericRespModel genericRespModel = await scheduleRepo.createServiceAppointment(
schedules: serviceAppointmentScheduleList,
serviceProviderID: selectedBranchModel!.serviceProviderId ?? 0,
);
if (genericRespModel.messageStatus == 2 || genericRespModel.data == null) {
Utils.hideLoading(context);
Utils.showToast("${genericRespModel.message.toString()}");
return;
}
if (genericRespModel.data != null) {
genericRespModel.data.forEach((element) {
if (element['appointmentID'] != 0) {
appointmentIdsList.add(element['appointmentID']);
isSuccess = true;
} else {
isSuccess = false;
return;
}
});
}
context.read<DashboardVM>().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) {
notifyListeners(); Utils.showToast("${e.toString()}");
}
} }
bool ifItemAlreadyThere(int id) { Future<void> onConfirmAppointmentPressed({required BuildContext context, required appointmentId}) async {
int index = servicesInSchedule.indexWhere((element) => element.id == id); context.read<PaymentVM>().updateAppointmentIdsForPayment(ids: [appointmentId]);
if (index == -1) { navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.appointment);
return false; }
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.data == 1) {
context.read<DashboardVM>().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()}");
} }
return true;
} }
void updateIsHomeTapped(bool value) { void updateIsHomeTapped(bool value) {
isHomeTapped = value; isHomeTapped = value;
if (currentServiceSelection != null) {
currentServiceSelection!.isHomeSelected = value;
}
notifyListeners(); notifyListeners();
} }
@ -83,6 +174,9 @@ class AppointmentsVM extends BaseVM {
void updatePickedHomeLocation(String value) { void updatePickedHomeLocation(String value) {
pickedHomeLocation = value; pickedHomeLocation = value;
pickHomeLocationError = ""; pickHomeLocationError = "";
if (currentServiceSelection != null) {
currentServiceSelection!.homeLocation = value;
}
notifyListeners(); notifyListeners();
} }
@ -99,6 +193,8 @@ class AppointmentsVM extends BaseVM {
BranchDetailModel? selectedBranchModel; BranchDetailModel? selectedBranchModel;
List<ServiceModel> branchServices = []; List<ServiceModel> branchServices = [];
List<ServiceModel> servicesInCurrentAppointment = [];
ServiceModel? currentServiceSelection; ServiceModel? currentServiceSelection;
void updateBranchServiceId(SelectionModel id) async { void updateBranchServiceId(SelectionModel id) async {
@ -116,6 +212,12 @@ class AppointmentsVM extends BaseVM {
currentServiceSelection = null; currentServiceSelection = null;
} }
resetAfterBookingAppointment() {
servicesInCurrentAppointment.clear();
allSelectedItemsInAppointments.clear();
serviceAppointmentScheduleList.clear();
}
populateAppointmentsFilterList() { populateAppointmentsFilterList() {
appointmentsFilterOptions.clear(); appointmentsFilterOptions.clear();
appointmentsFilterOptions = [ appointmentsFilterOptions = [
@ -128,18 +230,29 @@ class AppointmentsVM extends BaseVM {
notifyListeners(); notifyListeners();
} }
applyFilterOnAppointmentsVM({required int index}) { applyFilterOnAppointmentsVM({required AppointmentStatusEnum appointmentStatusEnum}) {
if (appointmentsFilterOptions.isEmpty) return; if (appointmentsFilterOptions.isEmpty) return;
for (var value in appointmentsFilterOptions) { for (var value in appointmentsFilterOptions) {
value.isSelected = false; value.isSelected = false;
} }
appointmentsFilterOptions[index].isSelected = true; appointmentsFilterOptions[appointmentStatusEnum.getIdFromAppointmentStatusEnum() == -1 ? 0 : appointmentStatusEnum.getIdFromAppointmentStatusEnum()].isSelected = true;
if (appointmentStatusEnum.getIdFromAppointmentStatusEnum() == -1) {
myFilteredAppointments = myAppointments;
notifyListeners();
return;
}
myFilteredAppointments = myAppointments.where((element) => element.appointmentStatusID! == appointmentStatusEnum.getIdFromAppointmentStatusEnum()).toList();
notifyListeners(); notifyListeners();
} }
Future<void> getMyAppointments() async { Future<void> getMyAppointments() async {
isFetchingLists = true; isFetchingLists = true;
myAppointments = await commonRepo.getMyAppointments(); myAppointments = await commonRepo.getMyAppointments();
myFilteredAppointments = myAppointments;
myUpComingAppointments = myAppointments.where((element) => element.appointmentStatusEnum == AppointmentStatusEnum.booked).toList();
// applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments);
isFetchingLists = false; isFetchingLists = false;
notifyListeners(); notifyListeners();
} }
@ -150,34 +263,72 @@ class AppointmentsVM extends BaseVM {
notifyListeners(); notifyListeners();
} }
List<ItemData> serviceItems = []; updateSelectedAppointmentDate({required int dateIndex, required int scheduleIndex}) {
List<ItemData> selectedServiceItems = []; serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList!.forEach((element) {
ProviderProfileModel? providerProfileModel; element.date!.isSelected = false;
});
int selectedSubServicesCounter = 0; 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();
}
updateSelectedSubServicesCounter(int value) { updateSelectedAppointmentSlotByDate({required int scheduleIndex, required int slotIndex}) {
selectedSubServicesCounter = value; serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList!.forEach((element) {
element.availableSlots!.forEach((element) => 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(); notifyListeners();
} }
double amountToPayForAppointment = 0.0;
double totalAmount = 0.0;
List<ItemData> serviceItemsFromApi = [];
ProviderProfileModel? providerProfileModel;
int selectedSubServicesCounter = 0;
onItemUpdateOrSelected(int index, bool selected, int itemId) { onItemUpdateOrSelected(int index, bool selected, int itemId) {
serviceItems[index].isUpdateOrSelected = selected; int serviceIndex = servicesInCurrentAppointment.indexWhere((element) => element.serviceId == currentServiceSelection!.serviceId!);
serviceItemsFromApi[index].isUpdateOrSelected = selected;
serviceItemsFromApi[index].isHomeSelected = isHomeTapped;
if (selected) { if (selected) {
selectedSubServicesCounter = selectedSubServicesCounter + 1; selectedSubServicesCounter = selectedSubServicesCounter + 1;
updateSelectedSubServicesCounter(selectedSubServicesCounter);
selectSubServicesError = ""; selectSubServicesError = "";
currentServiceSelection!.serviceItems!.add(serviceItems[index]); 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) { if (!selected) {
selectedSubServicesCounter = selectedSubServicesCounter - 1; selectedSubServicesCounter = selectedSubServicesCounter - 1;
updateSelectedSubServicesCounter(selectedSubServicesCounter);
currentServiceSelection!.serviceItems!.removeWhere((element) => element.id == itemId); 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(); notifyListeners();
} }
populateProvidersFilterList() { populateBranchesFilterList() {
providersFilterOptions.clear(); providersFilterOptions.clear();
providersFilterOptions = [ providersFilterOptions = [
FilterListModel(title: "All Providers", isSelected: true, id: -1), FilterListModel(title: "All Providers", isSelected: true, id: -1),
@ -208,10 +359,15 @@ class AppointmentsVM extends BaseVM {
} }
Future<List<ItemData>> getServiceItems(int serviceId) async { Future<List<ItemData>> getServiceItems(int serviceId) async {
serviceItems.clear(); serviceItemsFromApi.clear();
serviceItems = await providerRepo.getServiceItems(serviceId); serviceItemsFromApi = await providerRepo.getServiceItems(serviceId);
serviceItemsFromApi.forEach((item) {
if (ifItemAlreadySelected(item.id!)) {
item.isUpdateOrSelected = true;
}
});
setState(ViewState.idle); setState(ViewState.idle);
return serviceItems; return serviceItemsFromApi;
} }
getBranchAndServices(int providerId) async { getBranchAndServices(int providerId) async {
@ -263,7 +419,7 @@ class AppointmentsVM extends BaseVM {
void updatePickHomeLocationError(String value) { void updatePickHomeLocationError(String value) {
pickHomeLocationError = value; pickHomeLocationError = value;
notifyListeners(); // notifyListeners();
} }
bool isServiceSelectionValidated() { bool isServiceSelectionValidated() {
@ -280,8 +436,8 @@ class AppointmentsVM extends BaseVM {
return true; return true;
} }
bool onSubServicesNextPressed() { bool validateItemsSelection() {
for (var value in serviceItems) { for (var value in serviceItemsFromApi) {
if (value.isUpdateOrSelected!) { if (value.isUpdateOrSelected!) {
return true; return true;
} }
@ -291,26 +447,227 @@ class AppointmentsVM extends BaseVM {
return false; return false;
} }
// void mergeServiceInAvailableSchedule() { String getTotalPrice(List<ServiceModel> serviceItems) {
// log("schedules: ${availableSchedules}"); var totalPrice = 0.0;
// for (var schedule in availableSchedules) { serviceItems.forEach((element) {
// for (var service in schedule.scheduleServices!) { totalPrice = totalPrice + (element.currentTotalServicePrice);
// if (branchSelectedServiceId.selectedId == service.serviceId) { });
// log("ID matched: ${service.serviceId}"); return totalPrice.toString();
// log("SelectedServices: ${schedule.selectedServices}"); }
//
// void openTheAddServiceBottomSheet(BuildContext context, AppointmentsVM appointmentsVM) {
// int isAlreadyThereIndex = schedule.selectedServices!.indexWhere((element) => element.serviceProviderServiceId == branchSelectedServiceId.selectedId); showModalBottomSheet(
// if (isAlreadyThereIndex != -1) { context: context,
// log("removing: ${service.serviceId}"); isScrollControlled: true,
// schedule.selectedServices!.removeAt(isAlreadyThereIndex); enableDrag: true,
// } builder: (BuildContext context) {
// schedule.selectedServices!.add(currentServiceSelection!); return AppointmentServicePickBottomSheet();
// notifyListeners(); },
// return; );
// } }
// }
// } void priceBreakDownClicked(BuildContext context, ServiceModel selectedService) {
// notifyListeners(); 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 = 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());
}
});
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;
serviceAppointmentScheduleList.forEach(
(schedule) {
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 = [];
appointmentListModel.appointmentServicesList!.forEach((service) {
service.serviceItems!.forEach((serviceItem) {
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.data == 1) {
context.read<DashboardVM>().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()}");
}
}
} }

@ -1,28 +1,123 @@
import 'package:car_customer_app/view_models/appointments_view_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/classes/consts.dart';
import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/int_extensions.dart';
import 'package:mc_common_app/extensions/string_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_list_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/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/widgets/button/show_fill_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/app_bar.dart';
import 'package:mc_common_app/widgets/common_widgets/card_button_with_icon.dart'; import 'package:mc_common_app/widgets/common_widgets/card_button_with_icon.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart';
class AppointmentDetailView extends StatelessWidget { class AppointmentDetailView extends StatelessWidget {
AppointmentListModel appointmentListModel; final AppointmentListModel appointmentListModel;
AppointmentDetailView({Key? key, required this.appointmentListModel}) : super(key: key); AppointmentDetailView({Key? key, required this.appointmentListModel}) : super(key: key);
final List<String> servicesList = [ Widget getBaseActionButtonWidget({required Color color, required String text, Color textColor = MyColors.white, required Function() onPressed}) {
"Mechanic", return Expanded(
"Electrician", child: ShowFillButton(
" Car Denting", maxHeight: 55,
"Oil Change", title: text,
]; onPressed: onPressed,
backgroundColor: color,
txtColor: textColor,
fontSize: 18,
),
);
}
Widget buildBottomActionButton({required AppointmentStatusEnum appointmentStatusEnum, required BuildContext context}) {
switch (appointmentStatusEnum) {
case AppointmentStatusEnum.booked:
return Align(
alignment: Alignment.bottomCenter,
child: Row(
children: [
getBaseActionButtonWidget(color: MyColors.redColor, onPressed: () => appointmentCancelConfirmationSheet(context), text: "Cancel"),
12.width,
getBaseActionButtonWidget(
color: MyColors.greenColor,
onPressed: () {
context.read<AppointmentsVM>().onConfirmAppointmentPressed(context: context, appointmentId: appointmentListModel.id);
},
text: "Confirm"),
],
),
);
case AppointmentStatusEnum.confirmed:
return Align(
alignment: Alignment.bottomCenter,
child: Row(
children: [
getBaseActionButtonWidget(color: MyColors.redColor, onPressed: () => appointmentCancelConfirmationSheet(context), text: "Cancel"),
],
),
);
case AppointmentStatusEnum.arrived:
return Align(
alignment: Alignment.bottomCenter,
child: Row(
children: [
getBaseActionButtonWidget(color: MyColors.grey98Color.withOpacity(0.3), textColor: MyColors.lightTextColor, onPressed: () {}, text: "In Progress"),
],
),
);
case AppointmentStatusEnum.cancelled:
return Align(
alignment: Alignment.bottomCenter,
child: Row(
children: [
getBaseActionButtonWidget(color: MyColors.grey98Color.withOpacity(0.3), textColor: MyColors.lightTextColor, onPressed: () {}, text: "Cancelled"),
],
),
);
case AppointmentStatusEnum.allAppointments:
return SizedBox();
}
}
void appointmentCancelConfirmationSheet(BuildContext context) {
final appointmentsVm = context.read<AppointmentsVM>();
return actionConfirmationBottomSheet(
context: context,
title: "Do you want to cancel this appointment?".toText(fontSize: 28, isBold: true, letterSpacing: -1.44),
subtitle: "Your appointment will be cancelled and you cannot undo this action.",
actionButtonYes: Expanded(
child: ShowFillButton(
maxHeight: 55,
title: "Yes",
fontSize: 15,
onPressed: () {
Navigator.pop(context);
appointmentsVm.onCancelAppointmentPressed(context: context, appointmentListModel: appointmentListModel);
},
),
),
actionButtonNo: Expanded(
child: ShowFillButton(
maxHeight: 55,
isFilled: false,
borderColor: MyColors.darkPrimaryColor,
title: "No",
txtColor: MyColors.darkPrimaryColor,
fontSize: 15,
onPressed: () {
Navigator.pop(context);
},
),
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
AppointmentsVM appointmentsVM = context.read<AppointmentsVM>();
return Scaffold( return Scaffold(
appBar: CustomAppBar( appBar: CustomAppBar(
title: "Appointment", title: "Appointment",
@ -38,88 +133,94 @@ class AppointmentDetailView extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
appointmentListModel.providerName!.toText(fontSize: 18, isBold: true), appointmentListModel.providerName!.toText(fontSize: 16, isBold: true),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
MyAssets.miniClockDark.buildSvg( MyAssets.miniClockDark.buildSvg(
height: 10, height: 12,
width: 10, width: 12,
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
10.width, 5.width,
"${appointmentListModel.duration ?? ""} ${appointmentListModel.appointmentDate!.toFormattedDateWithoutTime()}".toText(fontSize: 12, isBold: true, color: MyColors.lightTextColor), "${appointmentListModel.duration ?? ""} ${appointmentListModel.appointmentDate!.toFormattedDateWithoutTime()}".toText(fontSize: 12, isBold: true, color: MyColors.lightTextColor),
], ],
), ),
13.height, 13.height,
Row( if (appointmentListModel.appointmentServicesList != null && appointmentListModel.appointmentServicesList!.isNotEmpty) ...[
children: [ Column(
MyAssets.maintenanceIcon.buildSvg( children: List.generate(appointmentListModel.appointmentServicesList!.length, (index) {
height: 10, ServiceModel service = appointmentListModel.appointmentServicesList![index];
width: 10, return Column(
fit: BoxFit.fill, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
10.width, Row(
"Maintenance".toText(fontSize: 18, isBold: true), children: [
], // MyAssets.maintenanceIcon.buildSvg(
), // height: 10,
Column( // width: 10,
children: servicesList // fit: BoxFit.fill,
.map((e) => e // ),
.toText( // 10.width,
textAlign: TextAlign.start, "${index + 1}. ${service.providerServiceDescription}".toText(fontSize: 14, isBold: true),
fontSize: 13, ],
isBold: true, ),
color: MyColors.lightTextColor, if (service.serviceItems != null && service.serviceItems!.isNotEmpty) ...[
) Column(
.paddingOnly(bottom: 5)) children: List.generate(
.toList(), service.serviceItems!.length,
).paddingOnly(left: 15), (index) => "${service.serviceItems![index].name}".toText(
textAlign: TextAlign.start,
fontSize: 12,
color: MyColors.lightTextColor,
),
),
).paddingOnly(left: 15),
],
5.height,
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
((service.currentTotalServicePrice).toString()).toText(fontSize: 25, 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, service)),
],
);
}),
),
],
15.height, 15.height,
Row( Row(
children: [ children: [
CardButtonWithIcon( CardButtonWithIcon(
title: "Reschedule Appointment", title: "Reschedule Appointment",
onCardTapped: () {}, onCardTapped: () {
context.read<AppointmentsVM>().onRescheduleAppointmentPressed(context: context, appointmentListModel: appointmentListModel);
},
icon: MyAssets.scheduleAppointmentIcon.buildSvg(), icon: MyAssets.scheduleAppointmentIcon.buildSvg(),
), ),
10.width, if (appointmentListModel.appointmentStatusEnum == AppointmentStatusEnum.booked) ...[
CardButtonWithIcon( 10.width,
title: "Pay for Appointment", CardButtonWithIcon(
onCardTapped: () {}, title: "Pay for Appointment",
icon: MyAssets.creditCardIcon.buildSvg(), onCardTapped: () {
), context.read<AppointmentsVM>().onConfirmAppointmentPressed(context: context, appointmentId: appointmentListModel.id);
},
icon: MyAssets.creditCardIcon.buildSvg(),
),
],
], ],
), ),
15.height, 15.height,
], ],
).toWhiteContainer(width: double.infinity, allPading: 12), ).toWhiteContainer(width: double.infinity, allPading: 12),
Align( buildBottomActionButton(appointmentStatusEnum: appointmentListModel.appointmentStatusEnum!, context: context),
alignment: Alignment.bottomCenter,
child: Row(
children: [
Expanded(
child: ShowFillButton(
maxHeight: 55,
title: "Cancel",
onPressed: () {},
backgroundColor: MyColors.redColor,
),
),
if (appointmentListModel.appointmentStatusID == 1) ...[
12.width,
Expanded(
child: ShowFillButton(
maxHeight: 55,
title: "Confirm",
onPressed: () {},
backgroundColor: MyColors.greenColor,
),
),
],
],
),
),
], ],
), ),
), ),

@ -0,0 +1,159 @@
import 'package:car_customer_app/view_models/appointments_view_model.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/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/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';
import 'package:mc_common_app/widgets/common_widgets/time_slots.dart';
import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart';
class ScreenArgumentsForAppointmentDetailPage {
final int routeFlag; // 1 = coming from create appointment || 2 = coming from reschedule appointment
final int appointmentId; // 1 = coming from create appointment || 2 = coming from reschedule appointment
ScreenArgumentsForAppointmentDetailPage({required this.routeFlag, required this.appointmentId});
}
class BookAppointmentSchedulesView extends StatelessWidget {
final ScreenArgumentsForAppointmentDetailPage screenArgumentsForAppointmentDetailPage;
BookAppointmentSchedulesView({Key? key, required this.screenArgumentsForAppointmentDetailPage}) : super(key: key);
@override
Widget build(BuildContext context) {
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.serviceAppointmentScheduleList[scheduleIndex].servicesListInAppointment!.length,
itemBuilder: (BuildContext context, int serviceIndex) {
ServiceModel selectedService = appointmentsVM.serviceAppointmentScheduleList[scheduleIndex].servicesListInAppointment![serviceIndex];
return Row(
children: [
Expanded(
child: ("${serviceIndex + 1}. ${selectedService.providerServiceDescription}").toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor),
),
],
);
},
),
],
),
],
),
children: [
Column(
children: [
CustomCalenderWidget(customTimeDateSlotList: scheduleData.customTimeDateSlotList ?? [], scheduleIndex: scheduleIndex),
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: () => Navigator.pop(context),
backgroundColor: MyColors.greyButtonColor,
),
),
12.width,
Expanded(
child: ShowFillButton(
maxHeight: 55,
title: screenArgumentsForAppointmentDetailPage.routeFlag == 1 ? "Review" : "Confirm",
onPressed: () {
if (screenArgumentsForAppointmentDetailPage.routeFlag == 1) {
appointmentsVM.onReviewButtonPressed(context);
} else {
appointmentsVM.onRescheduleAppointmentConfirmPressed(
context: context,
appointmentId: screenArgumentsForAppointmentDetailPage.appointmentId,
selectedSlotId: appointmentsVM.serviceAppointmentScheduleList.first.selectedCustomTimeDateSlotModel!.date!.slotId,
);
}
},
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/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:flutter/material.dart';
import 'package:mc_common_app/classes/consts.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/int_extensions.dart';
import 'package:mc_common_app/extensions/string_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/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/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/views/advertisement/custom_add_button.dart';
import 'package:mc_common_app/widgets/button/show_fill_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/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:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class BookAppointmentServicesView extends StatelessWidget { class BookAppointmentServicesView extends StatelessWidget {
BookAppointmentServicesView({Key? key}) : super(key: key); 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",
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -149,9 +34,9 @@ class BookAppointmentServicesView extends StatelessWidget {
CustomAddButton( CustomAddButton(
needsBorder: true, needsBorder: true,
bgColor: MyColors.white, bgColor: MyColors.white,
onTap: () => openTheAddServiceBottomSheet(context, appointmentsVM), onTap: () => appointmentsVM.openTheAddServiceBottomSheet(context, appointmentsVM),
text: "Add Services", text: "Add Services",
icon: Container( icon: Container(
height: 24, height: 24,
width: 24, width: 24,
decoration: const BoxDecoration(shape: BoxShape.circle, color: MyColors.darkTextColor), decoration: const BoxDecoration(shape: BoxShape.circle, color: MyColors.darkTextColor),
@ -161,121 +46,53 @@ class BookAppointmentServicesView extends StatelessWidget {
10.height, 10.height,
ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: appointmentsVM.serviceAppointmentScheduleList.length, itemCount: appointmentsVM.servicesInCurrentAppointment.length,
itemBuilder: (BuildContext context, int scheduleIndex) { itemBuilder: (BuildContext context, int serviceIndex) {
ServiceAppointmentScheduleModel scheduleData = appointmentsVM.serviceAppointmentScheduleList[scheduleIndex]; ServiceModel serviceData = appointmentsVM.servicesInCurrentAppointment[serviceIndex];
return Column( return Column(
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: "Schedule: ${scheduleIndex + 1}".toText(fontSize: 20, isBold: true), child: (serviceData.serviceDescription ?? "").toText(fontSize: 15, isBold: true),
), ),
Align(
alignment: Alignment.topRight,
child: Icon(
Icons.delete_outline,
size: 28,
),
).onPress(() {}),
], ],
), ),
if (true) ...[ if (true) ...[
5.height,
Row( Row(
mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
("Available Dates").toText(fontSize: 14, isBold: true), "Service Location: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
(serviceData.isHomeSelected ? serviceData.homeLocation : "Workshop").toText(fontSize: 12, isBold: true).expand(),
], ],
), ),
5.height, 5.height,
SizedBox( Column(
width: double.infinity, children: List.generate(serviceData.serviceItems!.length, (itemIndex) {
child: BuildTimeSlots( ItemData itemData = serviceData.serviceItems![itemIndex];
timeSlots: scheduleData.availableDates!, return Row(
onPressed: (index) => null, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
"${itemData.name}: ".toText(fontSize: 13, color: MyColors.lightTextColor, isBold: true),
("${itemData.price}").toText(fontSize: 13, isBold: true).expand(),
],
);
}),
), ),
], 8.height,
if (scheduleData.selectedDate!.isNotEmpty) ...[
5.height,
Row( Row(
mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
("Available Slots").toText(fontSize: 14, isBold: true), ((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])),
5.height,
SizedBox(
width: double.infinity,
child: BuildTimeSlots(
timeSlots: _dummySlots,
onPressed: (index) => null,
),
),
], ],
20.height,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
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),
),
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)),
],
),
], ],
).toWhiteContainer(width: double.infinity, allPading: 12, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 10)); ).toWhiteContainer(width: double.infinity, allPading: 12, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 10));
}, },
@ -287,7 +104,11 @@ class BookAppointmentServicesView extends StatelessWidget {
txtColor: MyColors.black, txtColor: MyColors.black,
maxHeight: 55, maxHeight: 55,
title: "Cancel", title: "Cancel",
onPressed: () {}, onPressed: () {
appointmentsVM.servicesInCurrentAppointment.clear();
appointmentsVM.allSelectedItemsInAppointments.clear();
Navigator.pop(context);
},
backgroundColor: MyColors.greyButtonColor, backgroundColor: MyColors.greyButtonColor,
), ),
), ),
@ -295,9 +116,9 @@ class BookAppointmentServicesView extends StatelessWidget {
Expanded( Expanded(
child: ShowFillButton( child: ShowFillButton(
maxHeight: 55, maxHeight: 55,
title: "Review", title: "Next",
onPressed: () { onPressed: () {
navigateWithName(context, AppRoutes.reviewAppointmentView); appointmentsVM.onServicesNextPressed(context);
}, },
backgroundColor: MyColors.darkPrimaryColor, backgroundColor: MyColors.darkPrimaryColor,
), ),

@ -57,18 +57,19 @@ class BookAppointmentsItemView extends StatelessWidget {
Divider(), Divider(),
], ],
).horPaddingMain(), ).horPaddingMain(),
appointmentsVM.serviceItems.isEmpty appointmentsVM.serviceItemsFromApi.isEmpty
? Expanded(child: Center(child: "No Items to show.".toText(fontSize: 16, color: MyColors.lightTextColor))) ? Expanded(child: Center(child: "No Items to show.".toText(fontSize: 16, color: MyColors.lightTextColor)))
: ListView.separated( : ListView.separated(
separatorBuilder: (BuildContext context, int index) => Divider(), separatorBuilder: (BuildContext context, int index) => Divider(),
itemCount: appointmentsVM.serviceItems.length, itemCount: appointmentsVM.serviceItemsFromApi.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
ItemData itemData = appointmentsVM.serviceItems[index]; ItemData itemData = appointmentsVM.serviceItemsFromApi[index];
return ServiceItemWithPriceCheckBox( return ServiceItemWithPriceCheckBox(
description: "Some description about the sub-services", description: itemData.description ?? "Some description about the sub-services",
title: itemData.name!, title: itemData.name!,
isSelected: itemData.isUpdateOrSelected!, isSelected: itemData.isUpdateOrSelected!,
onSelection: (bool value) { onSelection: (bool value) {
print("itemId: ${itemData.id}");
appointmentsVM.onItemUpdateOrSelected(index, !itemData.isUpdateOrSelected!, itemData.id!); appointmentsVM.onItemUpdateOrSelected(index, !itemData.isUpdateOrSelected!, itemData.id!);
}, },
priceWidget: Row( priceWidget: Row(
@ -119,17 +120,21 @@ class BookAppointmentsItemView extends StatelessWidget {
maxHeight: 55, maxHeight: 55,
title: "Next", title: "Next",
onPressed: () async { onPressed: () async {
bool resp = appointmentsVM.onSubServicesNextPressed(); bool resp = appointmentsVM.validateItemsSelection();
if (resp) { if (resp) {
Utils.showLoading(context); appointmentsVM.onItemsSelectedInService();
await appointmentsVM.mergeServiceIntoAvailableSchedules(); Navigator.pop(context);
appointmentsVM.resetCategorySelectionBottomSheet();
Navigator.of(context).pushNamedAndRemoveUntil(AppRoutes.bookAppointmenServicesView, (Route<dynamic> route) => false);
// appointmentsVM.mergeServiceInAvailableSchedule();
} }
// bool resp = appointmentsVM.validateItemsSelection();
// if (resp) {
// Utils.showLoading(context);
// await appointmentsVM.mergeServiceIntoAvailableSchedules();
// appointmentsVM.resetCategorySelectionBottomSheet();
// Utils.hideLoading(context);
// Navigator.of(context).pushReplacementNamed(AppRoutes.bookAppointmenServicesView);
// }
}, },
backgroundColor: !appointmentsVM.isServiceSelectionValidated() ? MyColors.lightTextColor.withOpacity(0.6) : MyColors.primaryColor, backgroundColor: !appointmentsVM.isServiceSelectionValidated() ? MyColors.lightTextColor.withOpacity(0.6) : MyColors.darkPrimaryColor,
); );
}, },
), ),

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:car_customer_app/view_models/appointments_view_model.dart'; import 'package:car_customer_app/view_models/appointments_view_model.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -5,8 +7,10 @@ import 'package:mc_common_app/classes/consts.dart';
import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/int_extensions.dart';
import 'package:mc_common_app/extensions/string_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/generated/locale_keys.g.dart';
import 'package:mc_common_app/models/schedule_model.dart';
import 'package:mc_common_app/models/service_schedule_model.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/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/theme/colors.dart';
import 'package:mc_common_app/widgets/button/show_fill_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/app_bar.dart';
@ -92,50 +96,108 @@ class ReviewAppointment extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: "Schedule: ${scheduleIndex + 1}".toText(fontSize: 20, isBold: true), child: "Services".toText(fontSize: 14, isBold: true, color: MyColors.lightTextColor),
), ),
], ],
), ),
10.height,
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ListView.separated( ListView.separated(
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
shrinkWrap: true, shrinkWrap: true,
itemCount: appointmentsVM.servicesInSchedule.length, itemCount: scheduleData.servicesListInAppointment!.length,
itemBuilder: (BuildContext context, int serviceIndex) { 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;
}
return Column( return Column(
children: [ children: [
Row( if (scheduleData.servicesListInAppointment!.isNotEmpty) ...[
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( Column(
children: List.generate(scheduleData.serviceItemList!.length, (itemIndex) { children: List.generate(scheduleData.servicesListInAppointment!.length, (serviceIndex) {
ItemData itemData = scheduleData.serviceItemList![itemIndex]; ServiceModel serviceData = scheduleData.servicesListInAppointment![serviceIndex];
return Row( return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
"${itemData.name}: ".toText(fontSize: 13, color: MyColors.lightTextColor, isBold: true), Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"${serviceData.providerServiceDescription}".toText(fontSize: 16, isBold: true),
],
),
Column(
children: List.generate(serviceData.serviceItems!.length, (itemIndex) {
ItemData itemData = serviceData.serviceItems![itemIndex];
return Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
"${itemData.name}: ${itemData.price} SAR".toText(fontSize: 13, isBold: true, color: MyColors.lightTextColor),
],
),
],
);
}),
),
], ],
); );
}), }),
), ).paddingOnly(bottom: 10),
], ],
5.height,
SizedBox(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Time & Location".toText(fontSize: 16, isBold: true),
3.height,
Row(children: [
"Date & Time: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
"${scheduleData.selectedCustomTimeDateSlotModel!.date!.date} at ${selectedTimeSlot}".toText(fontSize: 12, isBold: true),
]),
Row(children: [
"Location: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
(scheduleData.appointmentType == 2 ? "Home" : "Workshop").toText(fontSize: 12, isBold: true),
]),
],
),
),
10.height,
Divider(thickness: 0.7),
Builder(builder: (BuildContext context) {
double totalServicePrice = 0.0;
double totalKms = 15.3;
double rangePricePerKm = 5;
totalServicePrice = rangePricePerKm * totalKms;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Amount".toText(fontSize: 16, isBold: true),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Service Charges".toText(fontSize: 14, color: MyColors.lightTextColor, isBold: true),
"${scheduleData.amountTotal.toString()} SAR".toText(fontSize: 16, isBold: true),
],
),
if (scheduleData.appointmentType == 1) ...[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Location Charges ($rangePricePerKm x $totalKms )".toText(fontSize: 14, color: MyColors.lightTextColor, isBold: true),
"${totalServicePrice.toString()} SAR".toText(fontSize: 16, isBold: true),
],
),
],
10.height,
],
);
}),
], ],
); );
}, },
@ -144,148 +206,74 @@ class ReviewAppointment extends StatelessWidget {
], ],
), ),
], ],
).toWhiteContainer(width: double.infinity, allPading: 12, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 10)); ).toWhiteContainer(width: double.infinity, allPading: 12, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 0));
}, },
); );
// AppointmentsVM appointmentsVM = context.read<AppointmentsVM>();
// return SizedBox(
// width: double.infinity,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: List.generate(
// appointmentsVM.servicesInSchedule.length,
// (serviceIndex) => Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// appointmentsVM.servicesInSchedule[serviceIndex].value.toText(fontSize: 16, isBold: true),
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: List.generate(
// appointmentsVM,
// (index) => itemList[index].toText(
// fontSize: 12,
// color: MyColors.lightTextColor,
// isBold: true,
// )),
// ),
// ],
// ).paddingOnly(bottom: 14),
// ),
// ).toWhiteContainer(width: double.infinity, allPading: 12, margin: EdgeInsets.symmetric(horizontal: 21)),
// );
} }
Widget buildTimeAndLocationInfoCard() { Widget buildNextButtonFooter({required BuildContext context}) {
return SizedBox( AppointmentsVM appointmentsVM = context.read<AppointmentsVM>();
width: double.infinity, return Container(
color: MyColors.white,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
"Time & Location".toText(fontSize: 16, isBold: true), Divider(thickness: 0.7, height: 3),
3.height, 8.height,
Row(children: [ if (appointmentsVM.amountToPayForAppointment > 0) ...[
"Date & Time: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), Row(
"2nd Feb, 2023 at 09:30 AM".toText(fontSize: 12, isBold: true),
]),
Row(children: [
"Location: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true),
"PM58+F97, Al Olaya, Riyadh 12333".toText(fontSize: 12, isBold: true),
]),
],
).toWhiteContainer(width: double.infinity, allPading: 12, margin: EdgeInsets.only(left: 21, right: 21, top: 10)),
);
}
Widget buildChargesBreakDown({required BuildContext context}) {
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!);
});
});
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Services".toText(fontSize: 16, isBold: true),
Column(
children: List.generate(
allSelectedItems.length,
(index) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
"${allSelectedItems[index].name}".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), "Payable now".toText(fontSize: 14, isBold: true),
"${allSelectedItems[index].price} SAR".toText(fontSize: 12, isBold: true), Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
appointmentsVM.amountToPayForAppointment.toString().toText(fontSize: 16, isBold: true),
2.width,
"SAR".toText(color: MyColors.lightTextColor, fontSize: 12, isBold: true).paddingOnly(bottom: 2),
],
)
], ],
), ).paddingOnly(left: 21, right: 21),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
"${totalServicePrice.toString()} SAR".toText(fontSize: 16, isBold: true),
],
),
10.height,
Divider(thickness: 0.7),
"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),
],
),
10.height,
Divider(
thickness: 0.7,
),
10.height,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Total Amount ".toText(fontSize: 16, isBold: true),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
"120".toText(fontSize: 29, isBold: true), "Remaining Amount".toText(fontSize: 14, isBold: true),
2.width, Row(
"SAR".toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5), crossAxisAlignment: CrossAxisAlignment.end,
children: [
(appointmentsVM.totalAmount - appointmentsVM.amountToPayForAppointment).toString().toText(fontSize: 16, isBold: true),
2.width,
"SAR".toText(color: MyColors.lightTextColor, fontSize: 12, isBold: true).paddingOnly(bottom: 2),
],
)
], ],
) ).paddingOnly(left: 21, right: 21),
], ],
), Row(
10.height, mainAxisAlignment: MainAxisAlignment.spaceBetween,
], children: [
).toWhiteContainer(width: double.infinity, allPading: 12, margin: EdgeInsets.only(left: 21, right: 21, top: 10, bottom: 21)); "Total Amount ".toText(fontSize: 18, isBold: true),
} Row(
crossAxisAlignment: CrossAxisAlignment.end,
Widget buildNextButtonFooter() { children: [
return Container( appointmentsVM.totalAmount.toString().toText(fontSize: 29, isBold: true),
height: 90, 2.width,
color: MyColors.white, "SAR".toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5),
child: Column( ],
mainAxisAlignment: MainAxisAlignment.end, )
children: [ ],
Divider(thickness: 0.7, height: 3), ).paddingOnly(left: 21, right: 21),
10.height, 5.height,
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ShowFillButton( child: ShowFillButton(
maxHeight: 55, maxHeight: 55,
backgroundColor: MyColors.primaryColor, backgroundColor: MyColors.darkPrimaryColor,
title: "Book Appointment", title: "Book Appointment",
onPressed: () {}, onPressed: () {
appointmentsVM.onBookAppointmentPressed(context);
},
).paddingOnly(bottom: 12, left: 21, right: 21), ).paddingOnly(bottom: 12, left: 21, right: 21),
), ),
], ],
@ -300,24 +288,18 @@ class ReviewAppointment extends StatelessWidget {
title: "Review Appointment", title: "Review Appointment",
isRemoveBackButton: false, isRemoveBackButton: false,
isDrawerEnabled: false, isDrawerEnabled: false,
actions: [MyAssets.searchIcon.buildSvg().paddingOnly(right: 21)],
onBackButtonTapped: () => Navigator.pop(context), onBackButtonTapped: () => Navigator.pop(context),
), ),
body: Stack( body: Column(
alignment: Alignment.bottomCenter, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SingleChildScrollView( ListView(
child: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, buildBranchInfoCard(context: context),
children: [ buildServicesInfoCard(context: context),
buildBranchInfoCard(context: context), ],
buildServicesInfoCard(context: context), ).expand(),
buildTimeAndLocationInfoCard(), buildNextButtonFooter(context: context),
buildChargesBreakDown(context: context),
],
),
),
buildNextButtonFooter(),
], ],
), ),
); );

@ -174,12 +174,13 @@ class AppointmentServicePickBottomSheet extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: ShowFillButton( child: ShowFillButton(
maxHeight: 55, maxHeight: 55,
backgroundColor: !appointmentsVM.isServiceSelectionValidated() ? MyColors.lightTextColor.withOpacity(0.6) : MyColors.primaryColor, backgroundColor: !appointmentsVM.isServiceSelectionValidated() ? MyColors.lightTextColor.withOpacity(0.6) : MyColors.darkPrimaryColor,
title: "Next", title: "Next",
onPressed: () { onPressed: () {
bool isValidated = appointmentsVM.isServiceSelectionValidated(); bool isValidated = appointmentsVM.isServiceSelectionValidated();
if (isValidated) { if (isValidated) {
appointmentsVM.getServiceItems(appointmentsVM.branchSelectedServiceId.selectedId); appointmentsVM.getServiceItems(appointmentsVM.branchSelectedServiceId.selectedId);
Navigator.pop(context);
navigateWithName(context, AppRoutes.bookAppointmentsItemView); navigateWithName(context, AppRoutes.bookAppointmentsItemView);
} }
}, },

@ -0,0 +1,239 @@
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/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;
final int scheduleIndex;
const CustomCalenderWidget({super.key, required this.customTimeDateSlotList, required this.scheduleIndex});
@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);
DropValue dv = DropValue(dt.month, "${dt.month.getMonthNameByNumber()}, ${dt.year}", "${dt.year}");
allDates.add(dt);
if (!ifMonthAlreadyThere(dv)) {
allMonths.add(dv);
}
}
selectedMonth = allDates.first.month;
selectedYear = allDates.first.year;
final appointmentsVM = context.read<AppointmentsVM>();
if (appointmentsVM.serviceAppointmentScheduleList[widget.scheduleIndex].selectedCustomTimeDateSlotModel != null) {
DateTime alreadySelectedDate = DateFormat('dd MMMM, yyyy').parse(appointmentsVM.serviceAppointmentScheduleList[widget.scheduleIndex].selectedCustomTimeDateSlotModel!.date!.date);
_selectedDay = alreadySelectedDate;
_focusedDay = alreadySelectedDate;
selectedMonth = alreadySelectedDate.month;
selectedYear = alreadySelectedDate.year;
}
datesInSelectedMonth = allDates.where((element) => element.month == selectedMonth).toList();
if (appointmentsVM.serviceAppointmentScheduleList[widget.scheduleIndex].selectedCustomTimeDateSlotModel == null) {
_focusedDay = datesInSelectedMonth.first;
}
}
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();
datesInSelectedMonth = allDates.where((element) => element.month == selectedMonth).toList();
_focusedDay = datesInSelectedMonth.first;
});
},
list: allMonths,
dropdownValue: DropValue(selectedMonth, "${selectedMonth.getMonthNameByNumber()}, ${selectedYear}", "${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(
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) {
int index = datesInSelectedMonth.indexWhere((element) => isSameDay(dateTime1, element));
if (index == -1) {
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(),
),
);
}
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) {
int index = datesInSelectedMonth.indexWhere((element) => isSameDay(selectedDay, element));
if (index == -1) {
return;
}
if (!isSameDay(_selectedDay, selectedDay)) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
int dateIndex = widget.customTimeDateSlotList.indexWhere((element) {
DateTime dateFromList = DateFormat('dd MMMM, yyyy').parse(element.date!.date);
if (isSameDay(dateFromList, selectedDay)) {
return true;
}
return false;
});
if (dateIndex != -1) {
appointmentsVM.updateSelectedAppointmentDate(scheduleIndex: widget.scheduleIndex, dateIndex: dateIndex);
}
}
},
),
],
);
},
);
}
}

@ -1,3 +1,4 @@
import 'package:car_customer_app/view_models/dashboard_view_model.dart';
import 'package:carousel_slider/carousel_slider.dart'; import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
@ -8,7 +9,9 @@ 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_list_model.dart';
import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/utils/navigator.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/extensions/extensions_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart';
import 'package:provider/provider.dart';
class CustomerAppointmentSliderWidget extends StatelessWidget { class CustomerAppointmentSliderWidget extends StatelessWidget {
final List<AppointmentListModel> myUpComingAppointments; final List<AppointmentListModel> myUpComingAppointments;
@ -18,31 +21,41 @@ class CustomerAppointmentSliderWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (myUpComingAppointments.isEmpty) { if (myUpComingAppointments.isEmpty) {
return InkWell( return CustomAddButton(
child: Container( needsBorder: true,
height: 86, bgColor: MyColors.white,
child: Row( onTap: () => context.read<DashboardVM>().onNavbarTapped(1),
mainAxisAlignment: MainAxisAlignment.center, text: "Add New Appointment",
children: [ icon: Container(
Container( height: 24,
height: 24, width: 24,
width: 24, decoration: const BoxDecoration(shape: BoxShape.circle, color: MyColors.darkTextColor),
decoration: BoxDecoration(shape: BoxShape.circle, color: MyColors.darkTextColor), child: const Icon(Icons.add, color: MyColors.white),
child: Icon( ),
Icons.add, ).padding(EdgeInsets.symmetric(vertical: 10, horizontal: 21));
color: MyColors.white, return Container(
), height: 86,
), child: Row(
SizedBox(width: 10), mainAxisAlignment: MainAxisAlignment.center,
"Add New Appointment".toText( children: [
fontSize: 15, Container(
isBold: true, height: 24,
color: MyColors.lightTextColor, width: 24,
decoration: BoxDecoration(shape: BoxShape.circle, color: MyColors.darkTextColor),
child: Icon(
Icons.add,
color: MyColors.white,
), ),
], ),
), SizedBox(width: 10),
"Add New Appointment".toText(
fontSize: 15,
isBold: true,
color: MyColors.lightTextColor,
),
],
), ),
).toWhiteContainer(width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 21, vertical: 10)); ).onPress(() {}).toWhiteContainer(width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 21, vertical: 10));
} }
return CarouselSlider.builder( return CarouselSlider.builder(
options: CarouselOptions( options: CarouselOptions(
@ -87,14 +100,14 @@ class BuildAppointmentContainerForCustomer extends StatelessWidget {
} }
List<Widget> buildServicesFromAppointment({required AppointmentListModel appointmentListModel}) { List<Widget> buildServicesFromAppointment({required AppointmentListModel appointmentListModel}) {
if (appointmentListModel.serviceAppointmentItems == null || appointmentListModel.serviceAppointmentItems!.isEmpty) { if (appointmentListModel.appointmentServicesList == null || appointmentListModel.appointmentServicesList!.isEmpty) {
return [SizedBox()]; return [SizedBox()];
} }
if (appointmentListModel.serviceAppointmentItems!.length == 1) { if (appointmentListModel.appointmentServicesList!.length == 1) {
return [ return [
showServices( showServices(
appointmentListModel.serviceAppointmentItems![0].serviceItemName!, appointmentListModel.appointmentServicesList![0].providerServiceDescription,
MyAssets.modificationsIcon, MyAssets.modificationsIcon,
) )
]; ];
@ -102,16 +115,13 @@ class BuildAppointmentContainerForCustomer extends StatelessWidget {
List<Widget> servicesList = List.generate( List<Widget> servicesList = List.generate(
2, 2,
(index) => showServices( (index) => showServices(appointmentListModel.appointmentServicesList![index].providerServiceDescription, MyAssets.modificationsIcon),
appointmentListModel.serviceAppointmentItems![index].serviceItemName!,
MyAssets.modificationsIcon,
),
); );
if (appointmentListModel.serviceAppointmentItems!.length > 1) { if (appointmentListModel.appointmentServicesList!.length > 1) {
servicesList.add( servicesList.add(
showServices( showServices(
"+ ${appointmentListModel.serviceAppointmentItems!.length - 1} More", "+ ${appointmentListModel.appointmentServicesList!.length - 1} More",
"", "",
isMoreText: true, isMoreText: true,
), ),

@ -34,30 +34,30 @@ class _DashboardPageState extends State<DashboardPage> {
void initState() { void initState() {
super.initState(); super.initState();
// dashboardVM = Provider.of<DashboardVM>(context, listen: false); // dashboardVM = Provider.of<DashboardVM>(context, listen: false);
fetchUsername();
scheduleMicrotask(() { scheduleMicrotask(() {
context.read<AppointmentsVM>().populateAppointmentsFilterList(); context.read<AppointmentsVM>().populateAppointmentsFilterList();
context.read<AppointmentsVM>().populateProvidersFilterList(); context.read<AppointmentsVM>().populateBranchesFilterList();
context.read<AdVM>().populateAdsFilterList(); context.read<AdVM>().populateAdsFilterList();
context.read<AppointmentsVM>().getAllNearBranches();
_onRefresh(); _onRefresh();
}); });
} }
Future<void> _onRefresh() async { Future<void> _onRefresh() async {
AdVM adVm = Provider.of<AdVM>(context, listen: false); AdVM adVM = Provider.of<AdVM>(context, listen: false);
AppointmentsVM appointmentsVM = Provider.of<AppointmentsVM>(context, listen: false); AppointmentsVM appointmentsVM = Provider.of<AppointmentsVM>(context, listen: false);
if (appointmentsVM.myAppointments.isEmpty) { if (appointmentsVM.myAppointments.isEmpty) {
await appointmentsVM.getMyAppointments(); await appointmentsVM.getMyAppointments();
} }
if (adVm.myAds.isEmpty) { if (appointmentsVM.nearbyBranches.isEmpty) {
await adVm.getMyAds(); await appointmentsVM.getAllNearBranches();
} }
if (adVm.exploreAds.isEmpty) { if (adVM.myAds.isEmpty) {
await adVm.getExploreAds(); await adVM.getMyAds();
}
if (adVM.exploreAds.isEmpty) {
await adVM.getExploreAds();
} }
final adVM = context.read<AdVM>();
if (adVM.vehicleTypes.isEmpty) { if (adVM.vehicleTypes.isEmpty) {
await adVM.getVehicleTypes(); await adVM.getVehicleTypes();
} }
@ -74,8 +74,6 @@ class _DashboardPageState extends State<DashboardPage> {
); );
} }
fetchUsername() async {}
List<Widget> fragments = [ List<Widget> fragments = [
const BranchesFragment(), const BranchesFragment(),
const AppointmentsFragment(), const AppointmentsFragment(),
@ -106,7 +104,6 @@ class _DashboardPageState extends State<DashboardPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isHomePage = context.watch<DashboardVM>().selectedNavbarBarIndex == 2; bool isHomePage = context.watch<DashboardVM>().selectedNavbarBarIndex == 2;
bool isAdsPage = context.watch<DashboardVM>().selectedNavbarBarIndex == 3;
return Scaffold( return Scaffold(
appBar: CustomAppBar( appBar: CustomAppBar(
backgroundColor: null, backgroundColor: null,
@ -119,7 +116,12 @@ class _DashboardPageState extends State<DashboardPage> {
(isHomePage (isHomePage
? MyAssets.notificationsBellIcon.buildSvg() ? MyAssets.notificationsBellIcon.buildSvg()
: InkWell( : InkWell(
onTap: () => navigateWithName(context, AppRoutes.adsSearchFilterScreen), onTap: () async {
if (context.read<DashboardVM>().selectedNavbarBarIndex == 3) {
await context.read<AdVM>().populateDataForAdFilter();
navigateWithName(context, AppRoutes.adsFilterView);
}
},
child: MyAssets.searchIcon.buildSvg(), child: MyAssets.searchIcon.buildSvg(),
)) ))
.paddingOnly(right: 21) .paddingOnly(right: 21)

@ -1,9 +1,12 @@
import 'dart:developer';
import 'package:car_customer_app/view_models/appointments_view_model.dart'; import 'package:car_customer_app/view_models/appointments_view_model.dart';
import 'package:car_customer_app/views/appointments/widgets/customer_appointment_slider_widget.dart'; import 'package:car_customer_app/views/appointments/widgets/customer_appointment_slider_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/config/routes.dart';
import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/int_extensions.dart';
import 'package:mc_common_app/extensions/string_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/theme/colors.dart'; import 'package:mc_common_app/theme/colors.dart';
import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/utils/navigator.dart';
import 'package:mc_common_app/widgets/common_widgets/categories_list.dart'; import 'package:mc_common_app/widgets/common_widgets/categories_list.dart';
@ -25,11 +28,11 @@ class AppointmentsFragment extends StatelessWidget {
16.height, 16.height,
FiltersList( FiltersList(
filterList: appointmentsVM.appointmentsFilterOptions, filterList: appointmentsVM.appointmentsFilterOptions,
onFilterTapped: (index, selectedFilterId) => appointmentsVM.applyFilterOnAppointmentsVM(index: index), onFilterTapped: (index, selectedFilterId) => appointmentsVM.applyFilterOnAppointmentsVM(appointmentStatusEnum: selectedFilterId.toAppointmentStatusEnum()),
), ),
16.height, 16.height,
Expanded( Expanded(
child: appointmentsVM.myAppointments.isEmpty child: appointmentsVM.myFilteredAppointments.isEmpty
? Column( ? Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -38,15 +41,21 @@ class AppointmentsFragment extends StatelessWidget {
) )
: ListView.builder( : ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: appointmentsVM.myAppointments.length, itemCount: appointmentsVM.myFilteredAppointments.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return BuildAppointmentContainerForCustomer( return BuildAppointmentContainerForCustomer(
onTapped: () => navigateWithName( onTapped: () {
context, AppointmentListModel appointmentModel = appointmentsVM.myFilteredAppointments[index];
AppRoutes.appointmentDetailView, appointmentModel.appointmentServicesList!.forEach((service) {
arguments: appointmentsVM.myAppointments[index], double totalServicePrice = 0.0;
), service.serviceItems!.forEach((item) {
appointmentListModel: appointmentsVM.myAppointments[index], totalServicePrice = totalServicePrice + (double.parse("${item.price ?? 0.0}"));
});
service.currentTotalServicePrice = totalServicePrice;
});
navigateWithName(context, AppRoutes.appointmentDetailView, arguments: appointmentModel);
},
appointmentListModel: appointmentsVM.myFilteredAppointments[index],
); );
}, },
), ),

@ -2,7 +2,6 @@ import 'package:car_customer_app/view_models/appointments_view_model.dart';
import 'package:car_customer_app/view_models/dashboard_view_model.dart'; import 'package:car_customer_app/view_models/dashboard_view_model.dart';
import 'package:car_customer_app/views/appointments/widgets/customer_appointment_slider_widget.dart'; import 'package:car_customer_app/views/appointments/widgets/customer_appointment_slider_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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/int_extensions.dart';
import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/enums.dart';
import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart';
@ -32,20 +31,21 @@ class HomeFragment extends StatelessWidget {
subTitle: "View All", subTitle: "View All",
onSubtitleTapped: () { onSubtitleTapped: () {
context.read<DashboardVM>().onNavbarTapped(1); context.read<DashboardVM>().onNavbarTapped(1);
context.read<AppointmentsVM>().applyFilterOnAppointmentsVM(index: 0); context.read<AppointmentsVM>().applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments);
}, },
).horPaddingMain(), ).horPaddingMain(),
CustomerAppointmentSliderWidget(myUpComingAppointments: context.watch<AppointmentsVM>().myAppointments), CustomerAppointmentSliderWidget(myUpComingAppointments: context.read<AppointmentsVM>().myUpComingAppointments),
7.height, 7.height,
ViewAllWidget( ViewAllWidget(
title: "My Recent Service Providers".toUpperCase(), title: "My Recent Service Providers".toUpperCase(),
subTitle: "View All", subTitle: "View All",
onSubtitleTapped: () { onSubtitleTapped: () {
print("value: ${AppState().getUser.data!.accessToken}");
context.read<DashboardVM>().onNavbarTapped(0); context.read<DashboardVM>().onNavbarTapped(0);
context.read<AppointmentsVM>().applyFilterOnProviders(index: 0); context.read<AppointmentsVM>().applyFilterOnProviders(index: 0);
}).horPaddingMain(), }).horPaddingMain(),
const ServiceProviderWidget().horPaddingMain(), Consumer(builder: (BuildContext context, AppointmentsVM appointmentsVM, Widget? child) {
return ServiceProviderWidget(nearbyBranches: context.read<AppointmentsVM>().nearbyBranches).horPaddingMain();
}),
Consumer( Consumer(
builder: (BuildContext context, AdVM adVM, Widget? child) { builder: (BuildContext context, AdVM adVM, Widget? child) {
return Column( return Column(

@ -38,6 +38,10 @@ class _BranchDetailPageState extends State<BranchDetailPage> {
return Scaffold( return Scaffold(
appBar: CustomAppBar( appBar: CustomAppBar(
title: "Branch Detail", title: "Branch Detail",
onBackButtonTapped: () {
context.read<AppointmentsVM>().resetCategorySelectionBottomSheet();
Navigator.pop(context);
},
), ),
body: Column( body: Column(
children: [ children: [
@ -94,13 +98,7 @@ class _BranchDetailPageState extends State<BranchDetailPage> {
color: MyColors.primaryColor, color: MyColors.primaryColor,
), ),
], ],
) ).padding(EdgeInsets.symmetric(vertical: 6, horizontal: 2)),
.padding(
EdgeInsets.symmetric(vertical: 6, horizontal: 2),
)
.onPress(
() {},
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -119,14 +117,8 @@ class _BranchDetailPageState extends State<BranchDetailPage> {
), ),
], ],
) )
.padding( .padding(EdgeInsets.symmetric(vertical: 6, horizontal: 2))
EdgeInsets.symmetric(vertical: 6, horizontal: 2), .onPress(() => navigateWithName(context, AppRoutes.providerProfilePage, arguments: widget.branchDetailModel.serviceProviderId)),
)
.onPress(
() {
navigateWithName(context, AppRoutes.providerProfilePage, arguments: widget.branchDetailModel.serviceProviderId);
},
),
], ],
), ),
20.height, 20.height,
@ -178,7 +170,10 @@ class _BranchDetailPageState extends State<BranchDetailPage> {
showItem("Home service range", widget.branchDetailModel.branchServices![index].customerLocationRange.toString() + "KM"), showItem("Home service range", widget.branchDetailModel.branchServices![index].customerLocationRange.toString() + "KM"),
showItem("Charges per KM", widget.branchDetailModel.branchServices![index].customerLocationRange.toString() + "SAR"), showItem("Charges per KM", widget.branchDetailModel.branchServices![index].customerLocationRange.toString() + "SAR"),
8.height, 8.height,
(widget.branchDetailModel.branchServices![index].itemsCount.toString() + "+ items").toText( ((widget.branchDetailModel.branchServices![index].itemsCount != null && widget.branchDetailModel.branchServices![index].itemsCount! > 0)
? widget.branchDetailModel.branchServices![index].itemsCount.toString() + " items"
: "No" + " items")
.toText(
fontSize: 12, fontSize: 12,
isBold: true, isBold: true,
color: MyColors.primaryColor, color: MyColors.primaryColor,
@ -187,7 +182,9 @@ class _BranchDetailPageState extends State<BranchDetailPage> {
20.height, 20.height,
], ],
).onPress(() { ).onPress(() {
showMyBottomSheet(context, child: ItemsListSheet(widget.branchDetailModel.branchServices![index].serviceProviderServiceId ?? 0)); if (widget.branchDetailModel.branchServices![index].itemsCount != null && widget.branchDetailModel.branchServices![index].itemsCount! > 0) {
showMyBottomSheet(context, child: ItemsListSheet(widget.branchDetailModel.branchServices![index].serviceProviderServiceId ?? 0));
}
}), }),
], ],
onExpansionChanged: (value) { onExpansionChanged: (value) {

@ -30,12 +30,12 @@ class _ItemsListSheetState extends State<ItemsListSheet> {
height: MediaQuery.of(context).size.height / 1.2, height: MediaQuery.of(context).size.height / 1.2,
child: Consumer<AppointmentsVM>( child: Consumer<AppointmentsVM>(
builder: (context, appointmentsVM, _) { builder: (context, appointmentsVM, _) {
return appointmentsVM.serviceItems.isEmpty return appointmentsVM.serviceItemsFromApi.isEmpty
? const EmptyWidget() ? const EmptyWidget()
: ListView.separated( : ListView.separated(
itemCount: appointmentsVM.serviceItems.length, itemCount: appointmentsVM.serviceItemsFromApi.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
ItemData serviceItemModel = appointmentsVM.serviceItems[index]; ItemData serviceItemModel = appointmentsVM.serviceItemsFromApi[index];
return SizedBox( return SizedBox(
width: double.infinity, width: double.infinity,
child: Row( child: Row(
@ -47,7 +47,7 @@ class _ItemsListSheetState extends State<ItemsListSheet> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
serviceItemModel.toString().toText(fontSize: 16, isBold: true), serviceItemModel.name.toString().toText(fontSize: 16, isBold: true),
4.height, 4.height,
showItem("Available for appointment:", (serviceItemModel.isAllowAppointment ?? false) ? "Yes" : "No", valueColor: Colors.green), showItem("Available for appointment:", (serviceItemModel.isAllowAppointment ?? false) ? "Yes" : "No", valueColor: Colors.green),
showItem("Allowing Workshop service:", (serviceItemModel.isAppointmentCompanyLoc ?? false) ? "Yes" : "No", valueColor: Colors.green), showItem("Allowing Workshop service:", (serviceItemModel.isAppointmentCompanyLoc ?? false) ? "Yes" : "No", valueColor: Colors.green),

@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.17.0 <3.0.0"
# Dependencies specify other packages that your package needs in order to work. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions
@ -34,6 +34,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
table_calendar: ^3.0.9
mc_common_app: mc_common_app:
path: /Volumes/Data/Projects/Flutter/car_common_app path: /Volumes/Data/Projects/Flutter/car_common_app

Loading…
Cancel
Save