diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..0caff9e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,14 @@ +## For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx1024m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +#Wed Dec 13 16:00:22 AST 2023 +org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..fdcc671 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/lib/views/appoinments/add_new_service_appointment_page.dart b/lib/views/appoinments/add_new_service_appointment_page.dart new file mode 100644 index 0000000..08de3fd --- /dev/null +++ b/lib/views/appoinments/add_new_service_appointment_page.dart @@ -0,0 +1,286 @@ +import 'package:car_provider_app/view_models/service_view_model.dart'; +import 'package:car_provider_app/views/appoinments/widget/select_items_sheet.dart'; +import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/app_state.dart'; +import 'package:mc_common_app/config/dependencies.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/general_models/m_response.dart'; +import 'package:mc_common_app/models/services_models/item_model.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/utils/utils.dart'; +import 'package:mc_common_app/view_models/appointments_view_model.dart'; +import 'package:mc_common_app/widgets/bottom_sheet.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/dropdown/dropdow_field.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:easy_localization/easy_localization.dart'; + +import '../../generated/locale_keys.g.dart'; +import '../dashboard/widget/appointment_slider_widget.dart'; +import '../settings/schedule/widgets/chips_picker_item.dart'; + +class AddNewServiceAppointmentPage extends StatelessWidget { + AppointmentListModel appointmentListModel; + + AddNewServiceAppointmentPage(this.appointmentListModel, {Key? key}) + : super(key: key); + + DropValue? category; + DropValue? service; + List selectedList = []; + List? pickedItems; + + @override + Widget build(BuildContext context) { + ServiceVM serviceVM = context.read(); + serviceVM.fetchBranchCategory( + EasyLocalization.of(context)?.currentLocale?.countryCode ?? "SA"); + return Scaffold( + appBar: const CustomAppBar( + title: "Add Service", + ), + body: Padding( + padding: const EdgeInsets.all(21.0), + child: Column( + children: [ + AppointmentSliderWidget( + appointmentListModel: appointmentListModel, + isNeedTotalPayment: true, + isNeedToShowItems: true, + isNeedToShowToMoreText: false, + onTap: () {}, + ), + 12.height, + Expanded( + child: Consumer( + builder: (context, model, _) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Select services you want to add".toText( + fontSize: 18, + isBold: true, + ), + 12.height, + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + model.categoryDropList.isNotEmpty + ? DropdownField( + (DropValue value) async { + category = value; + service = null; + model.fetchProviderServices( + appointmentListModel.branchId + .toString(), + value.id.toString()); + }, + dropdownValue: category, + list: model.categoryDropList, + hint: + LocaleKeys.selectServiceCategory.tr(), + ) + : const Center( + child: CircularProgressIndicator(), + ), + 12.height, + model.servicesDropList.isNotEmpty + ? DropdownField( + (DropValue value) { + service = value; + pickedItems = null; + model.setState(ViewState.idle); + showMyBottomSheet( + context, + child: SelectItemsSheet( + serviceId: service?.id ?? 0, + onSelectItems: + (List selectedList) { + this.selectedList.clear(); + this.selectedList = selectedList; + pickedItems = []; + for (var element + in selectedList) { + pickedItems!.add( + PickerItem( + id: element.id ?? 0, + title: + element.name ?? ""), + ); + } + model.notifyListeners(); + }, + ), + ); + }, + dropdownValue: service, + list: model.servicesDropList, + hint: LocaleKeys.defineServices.tr(), + ) + : category == null + ? Container() + : model.services != null && + model.servicesDropList.isEmpty + ? const Text("No Service Found") + : const CircularProgressIndicator(), + 12.height, + (service != null && + pickedItems != null && + pickedItems!.length > 0) + ? ChipsPickerItem( + hint: 'Select Items', + itemsList: [...pickedItems ?? []], + onClick: () { + showMyBottomSheet( + context, + child: SelectItemsSheet( + serviceId: service?.id ?? 0, + list: pickedItems, + onSelectItems: + (List selectedList) { + pickedItems = []; + for (var element + in selectedList) { + pickedItems!.add( + PickerItem( + id: element.id ?? 0, + title: + element.name ?? ""), + ); + } + model.notifyListeners(); + }, + ), + ); + }, + ) + : service != null + ? const Text("No Item Selected Yet") + : const SizedBox(), + if ((service != null && + pickedItems != null && + pickedItems!.length > 0)) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 16.height, + "Total Additional Amount".toText( + fontSize: 14, + isBold: true, + color: MyColors.lightTextColor, + ), + Row( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + calculatePrice().toString().toText( + fontSize: 29, + isBold: true, + ), + 2.width, + "SAR".toText( + fontSize: 16, + isBold: true, + color: MyColors.lightTextColor, + ), + ], + ), + ], + ), + ], + ), + ], + ).toWhiteContainer(width: double.infinity, allPading: 12), + Row( + children: [ + Expanded( + child: ShowFillButton( + title: "Cancel", + maxWidth: double.infinity, + isFilled: false, + txtColor: MyColors.redColor, + borderColor: MyColors.redColor, + onPressed: () {}, + ), + ), + 12.width, + Expanded( + child: ShowFillButton( + title: "Add", + maxWidth: double.infinity, + onPressed: () async { + if (pickedItems != null && + pickedItems!.length > 0) { + List items = []; + pickedItems?.forEach((element) { + items.add(element.id); + }); + Utils.showLoading(context); + var postParams = { + "providerBranchID": + appointmentListModel.branchId, + "appointmentID": appointmentListModel.id, + "serviceItemID": items + }; + MResponse res = await serviceVM + .addNewServiceInAppointment(postParams); + _updateAppointment(context, + appointmentListModel.branchId ?? 0); + Utils.hideLoading(context); + if (res.messageStatus == 1) { + Utils.showToast( + "Items are added Successfully"); + pop(context); + } else { + Utils.showToast(res.message.toString()); + } + } else { + Utils.showToast("Please select items"); + } + }, + ), + ), + ], + ), + ], + ); + }, + ), + ), + ], + ), + ), + ); + } + + Future _updateAppointment(BuildContext context, int branchId) async { + await context.read().getProviderMyAppointments({ + "ServiceProviderID": injector + .get() + .getUser + .data + ?.userInfo + ?.providerId + .toString() ?? + "0", + "ProviderBranchID": branchId.toString(), + }, isNeedToRebuild: true); + } + + double calculatePrice() { + double total = 0; + for (var element in selectedList) { + total = total + double.parse(element.price ?? "0"); + } + return total; + } +} diff --git a/lib/views/appoinments/appointment_page.dart b/lib/views/appoinments/appointment_page.dart new file mode 100644 index 0000000..8681c15 --- /dev/null +++ b/lib/views/appoinments/appointment_page.dart @@ -0,0 +1,273 @@ +import 'package:car_provider_app/config/provider_routes.dart'; +import 'package:car_provider_app/views/dashboard/widget/appointment_slider_widget.dart'; +import 'package:mc_common_app/classes/app_state.dart'; +import 'package:mc_common_app/config/dependencies.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/models/provider_branches_models/branch_detail_model.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/date_helper.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/appointments_view_model.dart'; +import 'package:mc_common_app/widgets/common_widgets/categories_list.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_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/widgets/common_widgets/app_bar.dart'; +import 'package:percent_indicator/percent_indicator.dart'; +import 'package:provider/provider.dart'; + +class AppointmentPage extends StatelessWidget { + BranchDetailModel branch; + String date = ""; + + AppointmentPage({ + required this.branch, + Key? key, + }) : super(key: key); + + GlobalKey refreshIndicatorKey = + GlobalKey(); + + Future _pullRefresh(BuildContext context) async { + await context.read().getAppointmentSlotsInfo( + context: context, + map: { + "ProviderBranchID": branch.id.toString(), + "FromDate": date, + "ToDate": date + }, + isNeedToRebuild: true, + ); + await context.read().getProviderMyAppointments({ + "ServiceProviderID": injector + .get() + .getUser + .data + ?.userInfo + ?.providerId + .toString() ?? + "0", + "ProviderBranchID": branch.id.toString(), + "FromDate": date, + "DateTo": date, + }); + } + + @override + Widget build(BuildContext context) { + date = DateHelper.formatAsYearMonthDay(DateTime.now()); + context.read().setupProviderAppointmentFilter(); + _pullRefresh(context); + context.read().selectedBranch = branch.id ?? 0; + + return Scaffold( + appBar: CustomAppBar( + profileImageUrl: MyAssets.carBanner, + title: branch.branchName, + ), + body: SizedBox( + width: double.infinity, + height: double.infinity, + child: Consumer(builder: (BuildContext context, + AppointmentsVM appointmentsVM, Widget? child) { + if (appointmentsVM.state == ViewState.busy) { + return const Center(child: CircularProgressIndicator()); + } else { + return RefreshIndicator( + onRefresh: () => _pullRefresh(context), + key: refreshIndicatorKey, + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + children: [ + progressWidget(context, appointmentsVM), + FiltersList( + filterList: appointmentsVM.appointmentsFilterOptions, + padding: const EdgeInsets.symmetric(horizontal: 18), + onFilterTapped: (index, selectedFilterId) { + appointmentsVM.applyFilterOnAppointmentsVM( + appointmentStatusEnum: + selectedFilterId.toAppointmentStatusEnum(), + isNeedCustomerFilter: true, + ); + }, + ), + if (appointmentsVM.myFilteredAppointments2.isEmpty) + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: "No Appointment Found".toText(), + ), + ListView.separated( + itemBuilder: (context, index) { + return AppointmentSliderWidget( + appointmentListModel: + appointmentsVM.myFilteredAppointments2[index], + isNeedTotalPayment: false, + onTap: () { + context + .read() + .selectedAppointmentIndex = index; + navigateWithName( + context, + ProviderAppRoutes.appointmentDetailList, + // arguments: appointmentsVM + // .myFilteredAppointments2[index] + // .customerAppointmentList, + ); + }, + ); + }, + separatorBuilder: (context, snapchat) { + return 21.height; + }, + itemCount: appointmentsVM.myFilteredAppointments2.length, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: const EdgeInsets.all(21), + ), + ], + ), + ), + ); + } + }), + ), + ); + } + + Widget progressWidget(BuildContext context, AppointmentsVM appointmentsVM) { + double percent = (appointmentsVM.appointmentSlots?.occupiedSlots ?? + 0.0 / (appointmentsVM.appointmentSlots?.totalSlots ?? 0.0)) + .toDouble() * + 100; + return Column( + children: [ + Row( + children: [ + Expanded( + child: "Slots Overview".toText( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Row( + children: [ + date.toText( + fontWeight: FontWeight.bold, + ), + const Icon( + Icons.keyboard_arrow_down_outlined, + size: 16, + ), + ], + ) + .toContainer( + backgroundColor: MyColors.lightGreyEAColor, + borderRadius: 100, + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + ) + .onPress(() async { + date = await Utils.pickDateFromDatePicker( + context, + firstDate: null, + ); + _pullRefresh(context); + // context.read().notifyListeners(); + }), + ], + ), + 24.height, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 14, + height: 14, + color: MyColors.lightGreyEAColor, + ), + 4.width, + "Empty: ".toText( + fontSize: 8, + color: Colors.white, + ), + (appointmentsVM.appointmentSlots?.emptySlots ?? 0) + .toString() + .toText( + fontSize: 9, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ], + ).toContainer( + backgroundColor: MyColors.darkIconColor, + ), + 8.height, + Row( + children: [ + Container( + width: 14, + height: 14, + color: MyColors.darkPrimaryColor, + ), + 4.width, + "Occupied: ".toText( + fontSize: 8, + color: Colors.white, + ), + (appointmentsVM.appointmentSlots?.occupiedSlots ?? 0) + .toString() + .toText( + fontSize: 9, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ], + ).toContainer( + backgroundColor: MyColors.darkIconColor, + ), + ], + ), + CircularPercentIndicator( + radius: 60.0, + lineWidth: 12.0, + percent: percent, + circularStrokeCap: CircularStrokeCap.round, + center: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + "Total Slots".toText( + fontSize: 13, + fontWeight: FontWeight.bold, + ), + (appointmentsVM.appointmentSlots?.totalSlots ?? 0) + .toString() + .toText( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ], + ), + backgroundColor: MyColors.lightGreyEAColor, + progressColor: MyColors.darkPrimaryColor, + ), + ], + ) + ], + ).toWhiteContainer( + width: double.infinity, + pading: const EdgeInsets.all(12), + margin: const EdgeInsets.all(21), + ); + } +} diff --git a/lib/views/appoinments/merge_appointment_page.dart b/lib/views/appoinments/merge_appointment_page.dart new file mode 100644 index 0000000..66fb632 --- /dev/null +++ b/lib/views/appoinments/merge_appointment_page.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/app_state.dart'; +import 'package:mc_common_app/config/dependencies.dart'; + +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/models/general_models/m_response.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/utils/utils.dart'; +import 'package:mc_common_app/view_models/appointments_view_model.dart'; +import 'package:mc_common_app/widgets/button/show_fill_button.dart'; +import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; + +import '../../config/provider_routes.dart'; +import '../dashboard/widget/appointment_slider_widget.dart'; +import 'package:provider/provider.dart'; + +class MergeAppointmentListPage extends StatelessWidget { + MergeAppointmentListPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const CustomAppBar( + title: "Select Appointments", + ), + body: SizedBox( + width: double.infinity, + height: double.infinity, + child: Consumer( + builder: (BuildContext context, AppointmentsVM appointmentsVM, + Widget? child) { + return Column( + children: [ + Expanded( + child: Column( + children: [ + Expanded( + child: (appointmentsVM.state == ViewState.busy) + ? const Center(child: CircularProgressIndicator()) + : ListView.separated( + itemBuilder: (context, index) { + return AppointmentSliderWidget( + appointmentListModel: appointmentsVM + .myFilteredAppointments2[appointmentsVM + .selectedAppointmentIndex] + .customerAppointmentList![index], + isNeedTotalPayment: true, + isSelectable: true, + isNeedToShowAppointmentStatus: true, + isNeedToShowMergeStatus: true, + onTap: () { + appointmentsVM + .updateCheckBoxInMergeRequest(index); + }, + ); + }, + separatorBuilder: (context, snapchat) { + return 21.height; + }, + itemCount: appointmentsVM + .myFilteredAppointments2[ + appointmentsVM.selectedAppointmentIndex] + .customerAppointmentList! + .length, + padding: const EdgeInsets.all(21), + ), + ), + ], + ), + ), + ShowFillButton( + title: "Merge Appointments", + maxWidth: double.infinity, + margin: const EdgeInsets.all(21), + txtColor: appointmentsVM.inNeedToEnableMergeButton + ? MyColors.white + : MyColors.black, + backgroundColor: appointmentsVM.inNeedToEnableMergeButton + ? MyColors.primaryColor + : MyColors.greyButtonColor, + onPressed: () async { + Utils.showLoading(context); + List appointmentIDs = []; + + for (var element in appointmentsVM + .myFilteredAppointments2[ + appointmentsVM.selectedAppointmentIndex] + .customerAppointmentList!) { + if (element.isSelected ?? false) { + appointmentIDs.add(element.id ?? 0); + } + } + Map map = { + "serviceProviderID": injector + .get() + .getUser + .data + ?.userInfo + ?.providerId + .toString() ?? + "0", + "serviceProviderBranchID": appointmentsVM + .myFilteredAppointments2[ + appointmentsVM.selectedAppointmentIndex] + .branchId, + "serviceAppointmentIDs": appointmentIDs + }; + + try { + MResponse response = + await appointmentsVM.createMergeAppointment(map); + Utils.hideLoading(context); + if (response.messageStatus == 1) { + Utils.showToast( + "Appointment Merge is Successfully done"); + + _updateAppointment( + context, + appointmentsVM + .myFilteredAppointments2[ + appointmentsVM.selectedAppointmentIndex] + .branchId ?? + 0); + + pop(context); + pop(context); + } else { + Utils.showToast(response.message.toString()); + } + } catch (e) { + Utils.showToast(e.toString()); + Utils.hideLoading(context); + } + }, + ), + ], + ); + }, + ), + ), + ); + } + + Future _updateAppointment(BuildContext context, int branchId) async { + await context.read().getProviderMyAppointments({ + "ServiceProviderID": injector + .get() + .getUser + .data + ?.userInfo + ?.providerId + .toString() ?? + "0", + "ProviderBranchID": branchId.toString(), + }, isNeedToRebuild: true); + } +} diff --git a/lib/views/appoinments/widget/select_items_sheet.dart b/lib/views/appoinments/widget/select_items_sheet.dart new file mode 100644 index 0000000..ea74707 --- /dev/null +++ b/lib/views/appoinments/widget/select_items_sheet.dart @@ -0,0 +1,144 @@ +import 'package:car_provider_app/view_models/items_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/models/services_models/item_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/extensions/extensions_widget.dart'; +import 'package:mc_common_app/widgets/txt_field.dart'; +import 'package:provider/provider.dart'; + +import '../../settings/schedule/widgets/chips_picker_item.dart'; + +class SelectItemsSheet extends StatelessWidget { + int serviceId; + Function(List) onSelectItems; + List? list; + + SelectItemsSheet( + {Key? key, + required this.serviceId, + required this.onSelectItems, + this.list}) + : super(key: key); + + @override + Widget build(BuildContext context) { + context.read().getServiceItems(serviceId,list: list); + return SizedBox( + width: double.infinity, + height: MediaQuery.of(context).size.height / 1.4, + child: Column( + children: [ + Expanded( + child: Consumer( + builder: (_, model, child) { + return Padding( + padding: const EdgeInsets.only(left: 20, right: 20, top: 6), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: "Select Items" + .toText(fontSize: 24, isBold: true), + ), + Center( + child: (model.serviceItems == null + ? "0" + : model.serviceItems!.data! + .where((element) => + element.isUpdateOrSelected == true) + .toList() + .length + .toString()) + .toText( + fontSize: 10, + isBold: true, + color: Colors.white, + ), + ).toContainer( + borderRadius: 100, + width: 24, + height: 24, + paddingAll: 0, + backgroundColor: MyColors.darkPrimaryColor, + ), + ], + ), + 12.height, + TxtField( + hint: "Search Items", + onChanged: (v) {}, + ), + 12.height, + Expanded( + child: model.serviceItems?.data == null + ? const Center(child: CircularProgressIndicator()) + : model.serviceItems?.data!.length == 0 + ? Center(child: "No Item Found".toText()) + : ListView.separated( + itemBuilder: + (BuildContext context, int index) { + return Row( + children: [ + Checkbox( + value: model + .serviceItems! + .data![index] + .isUpdateOrSelected, + onChanged: (bool? v) { + model.serviceItems!.data![index] + .isUpdateOrSelected = v; + model.notifyListeners(); + }, + ), + 12.width, + Expanded( + child: model.serviceItems! + .data![index].name! + .toText(), + ), + ], + ); + }, + separatorBuilder: + (BuildContext context, int index) { + return const Divider( + height: 1, + ); + }, + itemCount: + model.serviceItems?.data?.length ?? 0, + ), + ), + ], + ), + ); + }, + ), + ), + ShowFillButton( + title: 'Add Selected Items', + maxWidth: double.infinity, + margin: const EdgeInsets.all(20), + onPressed: () { + if (context.read().serviceItems != null) { + List list = []; + context.read().serviceItems!.data!.forEach((element) { + if (element.isUpdateOrSelected ?? false) { + list.add(element); + } + }); + onSelectItems(list); + } + + Navigator.pop(context); + }, + ) + ], + ), + ); + } +} diff --git a/lib/views/appoinments/widget/sheets.dart b/lib/views/appoinments/widget/sheets.dart new file mode 100644 index 0000000..14afad8 --- /dev/null +++ b/lib/views/appoinments/widget/sheets.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/utils/utils.dart'; +import 'package:mc_common_app/widgets/button/show_fill_button.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:mc_common_app/widgets/txt_field.dart'; + +class ShowCollectPaymentSheet extends StatelessWidget { + Function() onClickYes; + Function() onClickNo; + + ShowCollectPaymentSheet( + {required this.onClickYes, required this.onClickNo, Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 21, right: 21, bottom: 21, top: 12), + child: Column( + children: [ + "Do you want to collect the money before providing services?".toText( + fontSize: 24, + isBold: true, + ), + 21.height, + Row( + children: [ + Expanded( + child: ShowFillButton( + title: "Yes", + onPressed: onClickYes, + ), + ), + 12.width, + Expanded( + child: ShowFillButton( + title: "No", + isFilled: false, + txtColor: MyColors.darkPrimaryColor, + onPressed: onClickNo, + ), + ), + ], + ), + ], + ), + ); + } +} + +class Reason { + String title; + bool isSelected; + + Reason(this.title, this.isSelected); +} + +class CancelAppointmentReasonSheet extends StatefulWidget { + Function(String) onCancelClick; + + CancelAppointmentReasonSheet({required this.onCancelClick, Key? key}) + : super(key: key); + + @override + State createState() => + _CancelAppointmentReasonSheetState(); +} + +class _CancelAppointmentReasonSheetState + extends State { + String reason = ""; + List reasonList = [ + Reason("Operational Issue", true), + Reason("Material Issue", false), + Reason("The customer no longer responding", false), + Reason("Other", false), + ]; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.only(left: 21, right: 21, top: 12, bottom: 21), + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Reason".toText(fontSize: 24, isBold: true), + ListView.separated( + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.only(top: 12, bottom: 12), + child: Row( + children: [ + Container( + width: 14, + height: 14, + decoration: BoxDecoration( + color: reasonList[index].isSelected + ? MyColors.darkPrimaryColor + : Colors.transparent, + borderRadius: BorderRadius.circular(122), + border: Border.all( + color: reasonList[index].isSelected + ? MyColors.darkPrimaryColor + : borderColor, + width: 1), + ), + child: const Icon( + Icons.done, + size: 12, + color: Colors.white, + ), + ), + 12.width, + reasonList[index].title.toText(isBold: true) + ], + ), + ).onPress(() { + for (var element in reasonList) { + element.isSelected = false; + } + + setState(() { + reason = reasonList[index].title; + reasonList[index].isSelected = true; + }); + }); + }, + separatorBuilder: (context, index) { + return Container( + width: double.infinity, + height: 1, + color: MyColors.borderColor, + ); + }, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: reasonList.length, + ), + if (reason == "Other") + TxtField( + hint: "Type Here...", + maxLines: 5, + onChanged: (v) { + reason = v; + }, + ), + 12.height, + Row( + children: [ + Expanded( + child: ShowFillButton( + title: "No", + isFilled: false, + txtColor: MyColors.darkPrimaryColor, + onPressed: () { + pop(context); + }, + ), + ), + 12.width, + Expanded( + child: ShowFillButton( + title: "Yes", + onPressed: () { + if (reason.isEmpty || reason == "Other") { + Utils.showToast("Please Select Reason"); + } else { + widget.onCancelClick(reason); + pop(context); + } + }, + ), + ), + ], + ) + ], + ), + ); + } +} diff --git a/lib/views/dashboard/fragments/branch_appointment_fragment.dart b/lib/views/dashboard/fragments/branch_appointment_fragment.dart new file mode 100644 index 0000000..e2d9cc6 --- /dev/null +++ b/lib/views/dashboard/fragments/branch_appointment_fragment.dart @@ -0,0 +1,170 @@ +import 'package:geolocator/geolocator.dart'; +import 'package:car_provider_app/config/provider_routes.dart'; +import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/app_state.dart'; +import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/models/general_models/m_response.dart'; +import 'package:mc_common_app/models/provider_branches_models/branch_detail_model.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/utils/utils.dart'; +import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:mc_common_app/widgets/tab/role_type_tab.dart'; +import '../../../generated/locale_keys.g.dart'; +import '../../../view_models/service_view_model.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class BranchAppointmentFragment extends StatelessWidget { + bool isNeedAppBar; + VoidCallback onBackButtonTapped; + + BranchAppointmentFragment( + {Key? key, this.isNeedAppBar = true, required this.onBackButtonTapped}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: !isNeedAppBar + ? null + : CustomAppBar( + title: "Appointments", + onBackButtonTapped: onBackButtonTapped, + ), + body: SizedBox( + width: double.infinity, + height: double.infinity, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 21, right: 21, top: 21), + child: RoleTypeTab( + context.watch().selectedBranchStatus == 3 + ? 0 + : context.watch().selectedBranchStatus, + [ + DropValue(3, "Active", ""), + DropValue(1, "Requested", ""), + ], + onSelect: (DropValue value) { + context.read().updateSelectedBranchType(value.id); + }, + ), + ), + Expanded( + child: Consumer( + builder: (context, model, _) { + if (model.state == ViewState.busy) { + return const Center(child: CircularProgressIndicator()); + } else { + List branches = []; + if (model.branches!.data != null) { + branches = model.branches!.data!.serviceProviderBranch! + .where((element) => + model.selectedBranchStatus == element.statusId) + .toList(); + } + return branches.isEmpty + ? Center(child: Text(LocaleKeys.no_branch.tr())) + : ListView.separated( + itemBuilder: (context, index) { + return Row( + children: [ + Container( + width: 74, + height: 50, + decoration: const BoxDecoration( + color: MyColors.darkPrimaryColor, + borderRadius: + BorderRadius.all(Radius.circular(8)), + ), + padding: const EdgeInsets.all(6), + child: SvgPicture.asset( + MyAssets.icBranches, + color: Colors.white, + ), + ), + 12.width, + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + Row( + children: [ + const Icon( + Icons.place, + size: 12, + color: MyColors.darkPrimaryColor, + ), + Geolocator.distanceBetween( + AppState() + .currentLocation + .latitude, + AppState() + .currentLocation + .latitude, + double.parse(branches[index] + .latitude ?? + "0"), + double.parse(branches[index] + .longitude ?? + "0")) + .toStringAsFixed(2) + .toText( + fontSize: 12, + color: + MyColors.darkPrimaryColor, + ) + ], + ), + Text( + branches[index].branchName ?? "", + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + "Tap to view".toText( + fontSize: 10, + color: MyColors.grey70Color), + ], + ), + ), + 12.width, + ], + ).toContainer().onPress(() async { + branches[index].countryID = + model.branches!.data!.countryID; + branches[index].countryName = + model.branches!.data!.countryName; + navigateWithName( + context, ProviderAppRoutes.appointment, + arguments: branches[index]); + }); + }, + separatorBuilder: (context, index) { + return 12.height; + }, + itemCount: branches.length, + padding: const EdgeInsets.all(12), + ); + } + }, + ), + ), + ], + ), + ), + ); + } +}