diff --git a/android/build.gradle b/android/build.gradle index 543cb25..aca8b4a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.4.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index e547954..53d5a7c 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 8d4492f..9625e10 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 899dfbb..d193b7b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -68,7 +68,6 @@ C74C73D850C4D4FC94940060 /* Pods-Runner.release.xcconfig */, 3643750A79F4A25346F39B6F /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -140,6 +139,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 9A2045E27F192C87BCD2FA50 /* [CP] Embed Pods Frameworks */, + 4CC2E88FCFA89EAD7EEA637B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -156,7 +156,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -200,10 +200,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -212,8 +214,26 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 4CC2E88FCFA89EAD7EEA637B /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -340,7 +360,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -356,9 +376,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 99Z3UD3LJM; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.example.carCustomerApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -414,7 +439,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -463,11 +488,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -480,9 +506,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 99Z3UD3LJM; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.example.carCustomerApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -499,9 +530,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 99Z3UD3LJM; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.example.carCustomerApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..3db53b6 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + NSCameraUsageDescription + Used to access media images for car parts CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -28,18 +30,22 @@ Main UISupportedInterfaceOrientations - UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/lib/config/customer_dependencies.dart b/lib/config/customer_dependencies.dart index 3debdff..804a53f 100644 --- a/lib/config/customer_dependencies.dart +++ b/lib/config/customer_dependencies.dart @@ -1,10 +1,12 @@ import 'package:car_customer_app/repositories/provider_repo.dart'; +import 'package:car_customer_app/repositories/schedule_repo.dart'; import 'package:mc_common_app/config/dependencies.dart'; class CustomerDependencies { static void addDependencies() { AppDependencies.addDependencies(); injector.registerSingleton(() => ProviderRepoImp()); + injector.registerSingleton(() => ScheduleRepoImp()); } } diff --git a/lib/config/customer_routes.dart b/lib/config/customer_routes.dart index f68dfef..6cca179 100644 --- a/lib/config/customer_routes.dart +++ b/lib/config/customer_routes.dart @@ -1,6 +1,8 @@ import 'package:car_customer_app/views/appointments/appointment_detail_view.dart'; import 'package:car_customer_app/views/appointments/book_appointment_services_view.dart'; +import 'package:car_customer_app/views/appointments/book_appointments_item_view.dart'; import 'package:car_customer_app/views/appointments/pick_items_view.dart'; +import 'package:car_customer_app/views/appointments/review_appointment_view.dart'; import 'package:car_customer_app/views/dashboard/dashboard_page.dart'; import 'package:car_customer_app/views/provider/branch_detail_page.dart'; import 'package:car_customer_app/views/provider/provider_profile_page.dart'; @@ -8,21 +10,28 @@ import 'package:flutter/cupertino.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/models/advertisment_models/ad_details_model.dart'; import 'package:mc_common_app/models/appointments_models/appointment_list_model.dart'; -import 'package:mc_common_app/models/services/branch_model.dart'; +import 'package:mc_common_app/models/provider_branches_models/branch_detail_model.dart'; +import 'package:mc_common_app/utils/enums.dart'; 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/select_ad_type_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'; -class CustomerAppRoutes { + class CustomerAppRoutes { static final Map routes = { AppRoutes.dashboard: (context) => DashboardPage(), AppRoutes.bookProviderAppView: (context) => BookProviderAppView(), 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.createAdView: (context) => CreateAdView(), + AppRoutes.adsSearchFilterScreen: (context) => AdsSearchFilterView(), + AppRoutes.selectAdTypeView: (context) => SelectAdTypeView(isProvider: ModalRoute.of(context)!.settings.arguments as bool), AppRoutes.bookAppointmenServicesView: (context) => BookAppointmentServicesView(), - AppRoutes.paymentMethodsView: (context) => PaymentMethodsView(), - AppRoutes.branchDetailPage: (context) => BranchDetailPage(branchModel: ModalRoute.of(context)!.settings.arguments as BranchModel), + AppRoutes.bookAppointmentsItemView: (context) => BookAppointmentsItemView(), + AppRoutes.reviewAppointmentView: (context) => ReviewAppointment(), + AppRoutes.paymentMethodsView: (context) => PaymentMethodsView(paymentType: ModalRoute.of(context)!.settings.arguments as PaymentTypesEnum), + AppRoutes.branchDetailPage: (context) => BranchDetailPage(branchDetailModel: ModalRoute.of(context)!.settings.arguments as BranchDetailModel), AppRoutes.providerProfilePage: (context) => ProviderProfilePage(providerId: ModalRoute.of(context)!.settings.arguments as int), }; } diff --git a/lib/main.dart b/lib/main.dart index e053104..966f089 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,9 @@ import 'package:car_customer_app/config/customer_dependencies.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/schedule_repo.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/providers_view_model.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; @@ -27,6 +27,7 @@ import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; import 'package:sizer/sizer.dart'; +//test commit Future main() async { WidgetsFlutterBinding.ensureInitialized(); CustomerDependencies.addDependencies(); @@ -53,15 +54,10 @@ Future main() async { adsRepo: injector.get(), ), ), - ChangeNotifierProvider( - create: (_) => ProvidersVM( - commonServices: injector.get(), - commonRepo: injector.get(), - providerRepo: injector.get(), - ), - ), ChangeNotifierProvider( create: (_) => AppointmentsVM( + scheduleRepo: injector.get(), + providerRepo: injector.get(), commonServices: injector.get(), commonRepo: injector.get(), ), diff --git a/lib/repositories/provider_repo.dart b/lib/repositories/provider_repo.dart index d92a66d..fee2cc1 100644 --- a/lib/repositories/provider_repo.dart +++ b/lib/repositories/provider_repo.dart @@ -1,51 +1,55 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:mc_common_app/models/model/provider_model.dart'; -import 'package:mc_common_app/models/services/item_model.dart'; -import 'package:mc_common_app/models/services/near_branch_model.dart'; -import 'package:http/http.dart' as http; import 'package:mc_common_app/api/api_client.dart'; import 'package:mc_common_app/classes/app_state.dart'; - import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/dependencies.dart'; - -import 'package:flutter/cupertino.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/provider_profile_model.dart'; +import 'package:mc_common_app/models/services/item_model.dart'; abstract class ProviderRepo { - Future getAllNearBranchAndServices(); + Future> getAllNearBranchAndServices(); - Future getServiceItems(int serviceId); + Future> getServiceItems(int serviceId); - Future getBranchAndServices(int providerId); + Future getBranchAndServices(int providerId); } class ProviderRepoImp implements ProviderRepo { + ApiClient apiClient = injector.get(); + AppState appState = injector.get(); + @override - Future getAllNearBranchAndServices() async { - // var postParams = {"serviceProviderID": AppState().getUser.data?.userInfo?.providerId.toString() ?? ""}; - String t = AppState().getUser.data!.accessToken ?? ""; - return await injector.get().getJsonForObject((json) => NearBrancheModel.fromJson(json), ApiConsts.GetAllNearBranches, token: t); + Future> getAllNearBranchAndServices() async { + GenericRespModel adsGenericModel = await apiClient.getJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.GetAllNearBranches, token: appState.getUser.data!.accessToken); + List nearBranches = List.generate(adsGenericModel.data.length, (index) => BranchDetailModel.fromJson(adsGenericModel.data[index])); + return nearBranches; } @override - Future getServiceItems(int serviceId) async { + Future> getServiceItems(int serviceId) async { var queryParameters = { "ServiceProviderServiceID": serviceId.toString(), }; - String? token = AppState().getUser.data?.accessToken; - debugPrint(token); - return await injector - .get() - .getJsonForObject((json) => ItemModel.fromJson(json), ApiConsts.getServiceItems, queryParameters: queryParameters, token: AppState().getUser.data!.accessToken ?? ""); + + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.getServiceItems, + token: appState.getUser.data!.accessToken, + queryParameters: queryParameters, + ); + List serviceItems = List.generate(adsGenericModel.data.length, (index) => ItemData.fromJson(adsGenericModel.data[index])); + return serviceItems; } @override - Future getBranchAndServices(int providerId) async { + Future getBranchAndServices(int providerId) async { var postParams = {"serviceProviderID": providerId.toString()}; - String t = AppState().getUser.data!.accessToken ?? ""; - return await injector.get().getJsonForObject((json) => ProviderModel.fromJson(json), ApiConsts.BranchesAndServices, queryParameters: postParams, token: t); + GenericRespModel adsGenericModel = + await apiClient.getJsonForObject((json) => GenericRespModel.fromJson(json), ApiConsts.BranchesAndServices, token: appState.getUser.data!.accessToken, queryParameters: postParams); + + return ProviderProfileModel.fromJson(adsGenericModel.data); } } diff --git a/lib/repositories/schedule_repo.dart b/lib/repositories/schedule_repo.dart new file mode 100644 index 0000000..7938095 --- /dev/null +++ b/lib/repositories/schedule_repo.dart @@ -0,0 +1,88 @@ +import 'package:mc_common_app/api/api_client.dart'; +import 'package:mc_common_app/classes/app_state.dart'; +import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/config/dependencies.dart'; +import 'package:mc_common_app/models/generic_resp_model.dart'; +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/schedule_model.dart'; +import 'package:mc_common_app/models/service_schedule_model.dart'; + +abstract class ScheduleRepo { + Future getAllServices(String branchId); + + Future createSchedule(Map map); + + Future addServicesInSchedule(Map map); + + Future updateSchedule(Map map); + + Future> getSchedules(String branchId); + + Future updateServicesInSchedule(Map map); + + Future> mergeServiceIntoAvailableSchedules({required List serviceItemIds}); +} + +class ScheduleRepoImp implements ScheduleRepo { + @override + Future getAllServices(String branchId) async { + Map map = {"ProviderBranchID": branchId}; + String t = AppState().getUser.data!.accessToken ?? ""; + return await injector.get().getJsonForObject((json) => Services.fromJson(json), ApiConsts.getServicesOfBranch, token: t, queryParameters: map); + } + + @override + Future createSchedule(Map map) async { + String t = AppState().getUser.data!.accessToken ?? ""; + return await injector.get().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.createSchedule, map, token: t); + } + + @override + Future addServicesInSchedule(Map map) async { + String t = AppState().getUser.data!.accessToken ?? ""; + return await injector.get().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.createGroup, map, token: t); + } + + @override + Future> getSchedules(String branchId) async { + Map map = {"ServiceProviderBranchID": branchId}; + String t = AppState().getUser.data!.accessToken ?? ""; + + GenericRespModel adsGenericModel = await injector.get().getJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.getSchedule, + token: t, + queryParameters: map, + ); + + return List.generate(adsGenericModel.data.length, (index) => ScheduleData.fromJson(adsGenericModel.data[index])); + } + + @override + Future updateSchedule(Map map) async { + String t = AppState().getUser.data!.accessToken ?? ""; + return await injector.get().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.updateSchedule, map, token: t); + } + + @override + Future updateServicesInSchedule(Map map) async { + String t = AppState().getUser.data!.accessToken ?? ""; + return await injector.get().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.updateGroup, map, token: t); + } + + Future> mergeServiceIntoAvailableSchedules({required List serviceItemIds}) async { + String t = AppState().getUser.data!.accessToken ?? ""; + var dummyServiceIds = ["3", "2"]; + var queryParameters = {"ServiceItemIDs": dummyServiceIds}; + GenericRespModel adsGenericModel = await injector.get().getJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.GetServiceItemAppointmentScheduleSlots, + token: t, + queryParameters: queryParameters, + ); + List serviceAppointmentScheduleModel = + List.generate(adsGenericModel.data.length, (index) => ServiceAppointmentScheduleModel.fromJson(adsGenericModel.data[index])); + return serviceAppointmentScheduleModel; + } +} diff --git a/lib/view_models/appointments_view_model.dart b/lib/view_models/appointments_view_model.dart index 2fb9aa2..08889a9 100644 --- a/lib/view_models/appointments_view_model.dart +++ b/lib/view_models/appointments_view_model.dart @@ -1,28 +1,78 @@ -import 'package:flutter/cupertino.dart'; +import 'package:car_customer_app/repositories/provider_repo.dart'; +import 'package:car_customer_app/repositories/schedule_repo.dart'; +import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/models/appointments_models/appointment_list_model.dart'; -import 'package:mc_common_app/models/provider_category_model.dart'; -import 'package:mc_common_app/models/provider_service_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/service_schedule_model.dart'; +import 'package:mc_common_app/models/services/item_model.dart'; +import 'package:mc_common_app/models/services/service_model.dart'; import 'package:mc_common_app/models/widgets_models.dart'; import 'package:mc_common_app/repositories/common_repo.dart'; import 'package:mc_common_app/services/common_services.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/view_models/base_view_model.dart'; +import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; -class AppointmentsVM extends ChangeNotifier { +class AppointmentsVM extends BaseVM { final CommonRepo commonRepo; final CommonAppServices commonServices; + final ProviderRepo providerRepo; + final ScheduleRepo scheduleRepo; - AppointmentsVM({required this.commonServices, required this.commonRepo}); + AppointmentsVM({required this.commonServices, required this.scheduleRepo, required this.providerRepo, required this.commonRepo}); bool isFetchingLists = false; List myAppointments = []; List appointmentsFilterOptions = []; + // List availableSchedules = []; + bool isFetchingServices = false; - List providerCategories = []; + List branchCategories = []; bool isHomeTapped = false; + List serviceAppointmentScheduleList = []; + + // Future getSchedulesByBranchId() async { + // availableSchedules = await scheduleRepo.getSchedules(selectedBranchModel!.id!.toString()); + // log("schedules: ${availableSchedules.toString()}"); + // notifyListeners(); + // } + + List servicesInSchedule = []; + + Future mergeServiceIntoAvailableSchedules() async { + List serviceItemIds = []; + serviceItems.forEach((serviceItem) { + if (serviceItem.isUpdateOrSelected!) { + serviceItemIds.add(serviceItem.id!.toString()); + } + }); + var scheduleList = await scheduleRepo.mergeServiceIntoAvailableSchedules(serviceItemIds: serviceItemIds); + serviceAppointmentScheduleList.addAll(scheduleList); + servicesInSchedule.clear(); + serviceAppointmentScheduleList.forEach((schedule) { + schedule.serviceItemList!.forEach((item) { + if (!ifItemAlreadyThere(item.serviceProviderServiceId!)) { + servicesInSchedule.add(DropValue(item.serviceProviderServiceId!, item.description!, "")); + } + }); + }); + notifyListeners(); + } + + bool ifItemAlreadyThere(int id) { + int index = servicesInSchedule.indexWhere((element) => element.id == id); + if (index == -1) { + return false; + } + return true; + } + void updateIsHomeTapped(bool value) { isHomeTapped = value; notifyListeners(); @@ -32,44 +82,38 @@ class AppointmentsVM extends ChangeNotifier { void updatePickedHomeLocation(String value) { pickedHomeLocation = value; + pickHomeLocationError = ""; notifyListeners(); } - SelectionModel providerCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + SelectionModel branchSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); - void updateProviderCategoryId(SelectionModel id) async { - providerCategoryId = id; - await getProviderServices(id.selectedId); + void updateProviderCategoryId(SelectionModel id) { + branchSelectedCategoryId = id; + getBranchServices(categoryId: branchSelectedCategoryId.selectedId); notifyListeners(); } - List providerServices = []; + List providersFilterOptions = []; + List nearbyBranches = []; + BranchDetailModel? selectedBranchModel; - SelectionModel providerServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + List branchServices = []; + ServiceModel? currentServiceSelection; - void updateProviderServiceId(SelectionModel id) async { - providerServiceId = id; - notifyListeners(); - } + void updateBranchServiceId(SelectionModel id) async { + branchSelectedServiceId = id; + currentServiceSelection = branchServices.firstWhere((element) => element.serviceProviderServiceId == id.selectedId); - void getProviderCategories() async { - notifyListeners(); - providerCategories = await commonRepo.getProviderServiceCategories(); notifyListeners(); } - getProviderServices(int categoryId) async { - providerServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + resetCategorySelectionBottomSheet() { + selectedSubServicesCounter = 0; + branchSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); isHomeTapped = false; - pickedHomeLocation = ""; - if (categoryId != -1) { - isFetchingServices = true; - notifyListeners(); - providerServices = await commonRepo.getProviderServices(categoryId: categoryId); - isFetchingServices = false; - - notifyListeners(); - } + branchSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + currentServiceSelection = null; } populateAppointmentsFilterList() { @@ -99,4 +143,174 @@ class AppointmentsVM extends ChangeNotifier { isFetchingLists = false; notifyListeners(); } + + updateSelectedBranch(BranchDetailModel branchDetailModel) { + selectedBranchModel = branchDetailModel; + getBranchCategories(); + notifyListeners(); + } + + List serviceItems = []; + List selectedServiceItems = []; + ProviderProfileModel? providerProfileModel; + + int selectedSubServicesCounter = 0; + + updateSelectedSubServicesCounter(int value) { + selectedSubServicesCounter = value; + notifyListeners(); + } + + onItemUpdateOrSelected(int index, bool selected, int itemId) { + serviceItems[index].isUpdateOrSelected = selected; + if (selected) { + selectedSubServicesCounter = selectedSubServicesCounter + 1; + updateSelectedSubServicesCounter(selectedSubServicesCounter); + selectSubServicesError = ""; + currentServiceSelection!.serviceItems!.add(serviceItems[index]); + } + if (!selected) { + selectedSubServicesCounter = selectedSubServicesCounter - 1; + updateSelectedSubServicesCounter(selectedSubServicesCounter); + currentServiceSelection!.serviceItems!.removeWhere((element) => element.id == itemId); + } + notifyListeners(); + } + + populateProvidersFilterList() { + providersFilterOptions.clear(); + providersFilterOptions = [ + FilterListModel(title: "All Providers", isSelected: true, id: -1), + FilterListModel(title: "Maintenance", isSelected: false, id: 0), + FilterListModel(title: "Oil Service", isSelected: false, id: 1), + FilterListModel(title: "Accessories", isSelected: false, id: 2), + FilterListModel(title: "Tire Service", isSelected: false, id: 3), + FilterListModel(title: "Dent and Paint", isSelected: false, id: 4), + ]; + notifyListeners(); + } + + applyFilterOnProviders({required int index}) { + if (providersFilterOptions.isEmpty) return; + for (var value in providersFilterOptions) { + value.isSelected = false; + } + providersFilterOptions[index].isSelected = true; + notifyListeners(); + } + + getAllNearBranches({bool isNeedToRebuild = false}) async { + //TODO: needs to lat,long into API + nearbyBranches.clear(); + if (isNeedToRebuild) setState(ViewState.busy); + nearbyBranches = await providerRepo.getAllNearBranchAndServices(); + setState(ViewState.idle); + } + + Future> getServiceItems(int serviceId) async { + serviceItems.clear(); + serviceItems = await providerRepo.getServiceItems(serviceId); + setState(ViewState.idle); + return serviceItems; + } + + getBranchAndServices(int providerId) async { + providerProfileModel = null; + providerProfileModel = await providerRepo.getBranchAndServices(providerId); + setState(ViewState.idle); + } + + String pickHomeLocationError = ""; + String selectSubServicesError = ""; + + SelectionModel branchSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + + bool isCategoryAlreadyPresent(int id) { + final contain = branchCategories.where((element) => element.id == id); + if (contain.isEmpty) { + return false; + } + return true; + } + + void getBranchCategories() async { + for (var value in selectedBranchModel!.branchServices!) { + if (!isCategoryAlreadyPresent(value.categoryId!)) { + branchCategories.add(DropValue(value.categoryId!, value.categoryName!, "")); + } + } + notifyListeners(); + } + + getBranchServices({required int categoryId}) async { + branchSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + isHomeTapped = false; + pickedHomeLocation = ""; + pickHomeLocationError = ""; + if (categoryId != -1) { + isFetchingServices = true; + // notifyListeners(); + branchServices = getFilteredBranchServices(categoryId: categoryId); + isFetchingServices = false; + notifyListeners(); + } + } + + List getFilteredBranchServices({required int categoryId}) { + List filteredServices = selectedBranchModel!.branchServices!.where((element) => element.categoryId == categoryId).toList(); + return filteredServices; + } + + void updatePickHomeLocationError(String value) { + pickHomeLocationError = value; + notifyListeners(); + } + + bool isServiceSelectionValidated() { + if (branchSelectedServiceId.selectedId == -1) { + return false; + } + + if (isHomeTapped) { + if (pickedHomeLocation == "") { + updatePickHomeLocationError(GlobalConsts.homeLocationEmptyError); + return false; + } + } + return true; + } + + bool onSubServicesNextPressed() { + for (var value in serviceItems) { + if (value.isUpdateOrSelected!) { + return true; + } + } + selectSubServicesError = "Please select at least one sub service"; + notifyListeners(); + return false; + } + +// void mergeServiceInAvailableSchedule() { +// log("schedules: ${availableSchedules}"); +// for (var schedule in availableSchedules) { +// for (var service in schedule.scheduleServices!) { +// if (branchSelectedServiceId.selectedId == service.serviceId) { +// log("ID matched: ${service.serviceId}"); +// log("SelectedServices: ${schedule.selectedServices}"); +// +// +// int isAlreadyThereIndex = schedule.selectedServices!.indexWhere((element) => element.serviceProviderServiceId == branchSelectedServiceId.selectedId); +// if (isAlreadyThereIndex != -1) { +// log("removing: ${service.serviceId}"); +// schedule.selectedServices!.removeAt(isAlreadyThereIndex); +// } +// schedule.selectedServices!.add(currentServiceSelection!); +// notifyListeners(); +// return; +// } +// } +// } +// notifyListeners(); +// } } diff --git a/lib/view_models/providers_view_model.dart b/lib/view_models/providers_view_model.dart deleted file mode 100644 index 568f713..0000000 --- a/lib/view_models/providers_view_model.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:mc_common_app/models/model/provider_model.dart'; -import 'package:mc_common_app/models/services/item_model.dart'; -import 'package:mc_common_app/models/services/near_branch_model.dart'; -import 'package:car_customer_app/repositories/provider_repo.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:mc_common_app/models/widgets_models.dart'; -import 'package:mc_common_app/repositories/common_repo.dart'; -import 'package:mc_common_app/services/common_services.dart'; -import 'package:mc_common_app/utils/enums.dart'; -import 'package:mc_common_app/view_models/base_view_model.dart'; - -class ProvidersVM extends BaseVM { - final CommonRepo commonRepo; - final CommonAppServices commonServices; - final ProviderRepo providerRepo; - - ProvidersVM({required this.commonServices, required this.commonRepo, required this.providerRepo}); - - List providersFilterOptions = []; - NearBrancheModel? branches; - ItemModel? serviceItems; - ProviderModel? providerModel; - - populateProvidersFilterList() { - providersFilterOptions.clear(); - providersFilterOptions = [ - FilterListModel(title: "All Providers", isSelected: true, id: -1), - FilterListModel(title: "Maintenance", isSelected: false, id: 0), - FilterListModel(title: "Oil Service", isSelected: false, id: 1), - FilterListModel(title: "Accessories", isSelected: false, id: 2), - FilterListModel(title: "Tire Service", isSelected: false, id: 3), - FilterListModel(title: "Dent and Paint", isSelected: false, id: 4), - ]; - notifyListeners(); - } - - applyFilterOnProviders({required int index}) { - if (providersFilterOptions.isEmpty) return; - for (var value in providersFilterOptions) { - value.isSelected = false; - } - providersFilterOptions[index].isSelected = true; - notifyListeners(); - } - - //Create new branch - getAllNearBranches({bool isNeedToRebuild = false}) async { - //TODO: needs to lat,long into API - branches = null; - if (isNeedToRebuild) setState(ViewState.busy); - branches = await providerRepo.getAllNearBranchAndServices(); - setState(ViewState.idle); - } - - Future getServiceItems(int serviceId) async { - serviceItems = null; - serviceItems = await providerRepo.getServiceItems(serviceId); - setState(ViewState.idle); - return serviceItems; - } - - getBranchAndServices(int providerId) async { - providerModel = null; - providerModel = await providerRepo.getBranchAndServices(providerId); - setState(ViewState.idle); - } -} diff --git a/lib/views/appointments/book_appointment_services_view.dart b/lib/views/appointments/book_appointment_services_view.dart index 71f4bb3..2212426 100644 --- a/lib/views/appointments/book_appointment_services_view.dart +++ b/lib/views/appointments/book_appointment_services_view.dart @@ -1,195 +1,133 @@ import 'package:car_customer_app/view_models/appointments_view_model.dart'; +import 'package:car_customer_app/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/models/service_schedule_model.dart'; +import 'package:mc_common_app/models/services/item_model.dart'; import 'package:mc_common_app/models/widgets_models.dart'; import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/views/advertisement/custom_add_button.dart'; import 'package:mc_common_app/widgets/button/show_fill_button.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; +import 'package:mc_common_app/widgets/common_widgets/info_bottom_sheet.dart'; +import 'package:mc_common_app/widgets/common_widgets/time_slots.dart'; import 'package:mc_common_app/widgets/dropdown/dropdow_field.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; -import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; class BookAppointmentServicesView extends StatelessWidget { - const BookAppointmentServicesView({Key? key}) : super(key: key); + BookAppointmentServicesView({Key? key}) : super(key: key); - void openTheAddServiceBottomSheet(BuildContext context) { + void openTheAddServiceBottomSheet(BuildContext context, AppointmentsVM appointmentsVM) { showModalBottomSheet( context: context, isScrollControlled: true, enableDrag: true, builder: (BuildContext context) { - AppointmentsVM appointmentsVM = context.watch(); - return SizedBox( - height: MediaQuery.of(context).size.height * 0.85, - child: Column( - children: [ - Container( - margin: const EdgeInsets.all(8), - height: 8, - width: 60, - decoration: const BoxDecoration(color: MyColors.lightTextColor, borderRadius: BorderRadius.all(Radius.circular(20))), - ), - 12.height, - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - "Select Category".toText(fontSize: 24, isBold: true), - ], - ), - 30.height, - Expanded( - child: ListView( - children: [ - Builder( - builder: (context) { - List serviceCategories = []; - for (var element in appointmentsVM.providerCategories) { - if (!element.isSelected!) { - serviceCategories.add(DropValue(element.id?.toInt() ?? 0, element.categoryName ?? "", "")); - } - } - return DropdownField( - (DropValue value) => appointmentsVM.updateProviderCategoryId(SelectionModel(selectedId: value.id, selectedOption: value.value, itemPrice: value.subValue)), - list: serviceCategories, - hint: "Select Category", - dropdownValue: appointmentsVM.providerCategoryId.selectedId != -1 - ? DropValue(appointmentsVM.providerCategoryId.selectedId, appointmentsVM.providerCategoryId.selectedOption, "") - : null, - ); - }, + 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), + ], ), - if (appointmentsVM.isFetchingServices) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [const CircularProgressIndicator().paddingAll(10)], - ), - ] else if (appointmentsVM.providerServices.isNotEmpty) ...[ - 8.height, - Builder( - builder: (context) { - List serviceCategories = []; - for (var element in appointmentsVM.providerServices) { - if (!element.isSelected!) { - serviceCategories.add(DropValue(element.id?.toInt() ?? 0, element.id.toString(), "")); - } - } - return DropdownField( - (DropValue value) => appointmentsVM.updateProviderServiceId(SelectionModel(selectedId: value.id, selectedOption: value.value, itemPrice: value.subValue)), - list: serviceCategories, - hint: "Select Services", - dropdownValue: appointmentsVM.providerServiceId.selectedId != -1 - ? DropValue(appointmentsVM.providerServiceId.selectedId, appointmentsVM.providerServiceId.selectedOption, "") - : null, - ); - }, - ), - ], - if (appointmentsVM.providerServiceId.selectedId != -1 && !appointmentsVM.isFetchingServices) ...[ - 16.height, - Row( - children: [ - "Select Service Location".toText( - fontSize: 16, - isBold: true, - color: MyColors.black, - ), - ], - ), - 8.height, - Row( - children: [ - Expanded( - child: ShowFillButton( - isFilled: appointmentsVM.isHomeTapped, - maxHeight: 48, - title: "Home", - txtColor: appointmentsVM.isHomeTapped ? MyColors.white : MyColors.darkTextColor, - onPressed: () => appointmentsVM.updateIsHomeTapped(true), - ), - ), - 12.width, - Expanded( - child: ShowFillButton( - isFilled: !appointmentsVM.isHomeTapped, - txtColor: !appointmentsVM.isHomeTapped ? MyColors.white : MyColors.darkTextColor, - maxHeight: 48, - title: "Workshop", - onPressed: () => appointmentsVM.updateIsHomeTapped(false), - ), - ), - ], - ), - if (appointmentsVM.isHomeTapped) ...[ - 8.height, - TxtField( - hint: 'Pick Home Location', - value: appointmentsVM.pickedHomeLocation, - isNeedClickAll: true, - postfixData: Icons.location_on, - postFixDataColor: MyColors.darkTextColor, - onTap: () { - //TODO: open the place picked to pick the location and save it in provider. - appointmentsVM.updatePickedHomeLocation("PM58+F97, Al Olaya, Riyadh 12333"); - }, - ), - 14.height, - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Icon( - Icons.warning, - color: MyColors.adPendingStatusColor, - size: 19, - ).paddingOnly(bottom: 2), - 3.width, - "Some services are not available on home location.".toText( - color: MyColors.adPendingStatusColor, - fontSize: 12, - isItalic: true, - ), - ], - ), - ] - ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + "1149 SAR".toText(fontSize: 16, isBold: true), ], ), - ), - SizedBox( - width: double.infinity, - child: Column( + 20.height, + "Home Location".toText(fontSize: 16, isBold: true), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (appointmentsVM.isHomeTapped && !appointmentsVM.isFetchingServices) ...[ - const Divider(thickness: 1, height: 1), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - // TODO: This Price will be decided according to the service selected - 150.toString().toText(fontSize: 30, isBold: true), - "SAR".toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor).paddingOnly(bottom: 5), - ], - ), - "These charges are additional to the actual service charges. For heavy items the charges may vary.".toText(fontSize: 12, color: MyColors.lightTextColor), - 22.height, - ], - SizedBox( - width: double.infinity, - child: ShowFillButton( - maxHeight: 55, - title: "Next", - onPressed: () {}, - ), - ), + "10km ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), + "5 x 10".toText(fontSize: 12, isBold: true), ], ), - ).paddingOnly(bottom: 20) - ], - )).horPaddingMain(); - }, - ); + 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 serviceItems) { + var totalPrice = 0.0; + serviceItems.forEach((element) { + totalPrice = totalPrice + double.parse(element.price ?? "0.0"); + }); + return totalPrice.toString(); } @override @@ -209,117 +147,166 @@ class BookAppointmentServicesView extends StatelessWidget { children: [ 21.height, CustomAddButton( - onTap: () => {openTheAddServiceBottomSheet(context)}, + needsBorder: true, + bgColor: MyColors.white, + onTap: () => openTheAddServiceBottomSheet(context, appointmentsVM), text: "Add Services", - icon: Container( + icon: Container( height: 24, width: 24, decoration: const BoxDecoration(shape: BoxShape.circle, color: MyColors.darkTextColor), child: const Icon(Icons.add, color: MyColors.white), ), - ), - // 20.height, - // ListView.builder( - // physics: const NeverScrollableScrollPhysics(), - // shrinkWrap: true, - // itemCount: adVM.specialServiceCards.length, - // itemBuilder: (BuildContext context, int index) { - // SpecialServiceCard specialServicesCard = adVM.specialServiceCards[index]; - // return Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Column( - // children: [ - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // Expanded( - // child: specialServicesCard.serviceSelectedId!.selectedOption.toText(fontSize: 16, isBold: true), - // ), - // Align( - // alignment: Alignment.topRight, - // child: MyAssets.closeWithOrangeBg.buildSvg( - // fit: BoxFit.fill, - // height: 30, - // width: 30, - // ), - // ).onPress(() => adVM.removeSpecialServiceCard(index)) - // ], - // ), - // Builder(builder: (context) { - // return Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // if (specialServicesCard.serviceSelectedId!.selectedId != 1 && specialServicesCard.serviceSelectedId!.selectedId != 3) ...[ - // // Row( - // // crossAxisAlignment: CrossAxisAlignment.start, - // // children: [ - // // "Duration: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), - // // (specialServicesCard.duration ?? "").toText(fontSize: 12, isBold: true).expand(), - // // ], - // // ), - // 8.height, - // Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // "Description: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), - // (specialServicesCard.description ?? "").toText(fontSize: 12, isBold: true).expand(), - // ], - // ), - // ], - // if (specialServicesCard.serviceSelectedId!.selectedId == 1 || specialServicesCard.serviceSelectedId!.selectedId == 3) ...[ - // Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // const Icon( - // weight: 2, - // Icons.location_on_outlined, - // color: MyColors.primaryColor, - // size: 17, - // ), - // "${specialServicesCard.duration} km".toText(fontSize: 10, color: MyColors.primaryColor), - // ], - // ), - // 8.height, - // Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // "Branch Address: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), - // (specialServicesCard.address ?? "").toText(fontSize: 12, isBold: true).expand(), - // ], - // ), - // Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // "Appointment Time: ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), - // "${specialServicesCard.serviceDate} - ${specialServicesCard.serviceTime}".toText(fontSize: 12, isBold: true).expand(), - // ], - // ), - // ], - // 6.height, - // Row( - // crossAxisAlignment: CrossAxisAlignment.end, - // children: [ - // (specialServicesCard.serviceSelectedId!.itemPrice).toText(fontSize: 20, isBold: true), - // 2.width, - // "SAR".toText(color: MyColors.lightTextColor, fontSize: 14), - // ], - // ), - // ], - // ); - // }), - // 3.height, - // const Divider(thickness: 1.5) - // ], - // ), - // 10.height, - // ], - // ); - // }, - // ), + ).horPaddingMain(), + 10.height, + ListView.builder( + shrinkWrap: true, + itemCount: appointmentsVM.serviceAppointmentScheduleList.length, + itemBuilder: (BuildContext context, int scheduleIndex) { + ServiceAppointmentScheduleModel scheduleData = appointmentsVM.serviceAppointmentScheduleList[scheduleIndex]; + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: "Schedule: ${scheduleIndex + 1}".toText(fontSize: 20, isBold: true), + ), + Align( + alignment: Alignment.topRight, + child: Icon( + Icons.delete_outline, + size: 28, + ), + ).onPress(() {}), + ], + ), + if (true) ...[ + 5.height, + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ("Available Dates").toText(fontSize: 14, isBold: true), + ], + ), + 5.height, + SizedBox( + width: double.infinity, + child: BuildTimeSlots( + timeSlots: scheduleData.availableDates!, + onPressed: (index) => null, + ), + ), + ], + if (scheduleData.selectedDate!.isNotEmpty) ...[ + 5.height, + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ("Available Slots").toText(fontSize: 14, isBold: true), + ], + ), + 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)); + }, + ).expand(), + Row( + children: [ + Expanded( + child: ShowFillButton( + txtColor: MyColors.black, + maxHeight: 55, + title: "Cancel", + onPressed: () {}, + backgroundColor: MyColors.greyButtonColor, + ), + ), + 12.width, + Expanded( + child: ShowFillButton( + maxHeight: 55, + title: "Review", + onPressed: () { + navigateWithName(context, AppRoutes.reviewAppointmentView); + }, + backgroundColor: MyColors.darkPrimaryColor, + ), + ) + ], + ).paddingAll(21) ], ); }, - ).horPaddingMain()); + )); } } diff --git a/lib/views/appointments/book_appointments_item_view.dart b/lib/views/appointments/book_appointments_item_view.dart new file mode 100644 index 0000000..2620166 --- /dev/null +++ b/lib/views/appointments/book_appointments_item_view.dart @@ -0,0 +1,147 @@ +import 'package:car_customer_app/view_models/appointments_view_model.dart'; +import 'package:car_customer_app/views/appointments/widgets/service_item_with_price_checkbox.dart'; +import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/config/routes.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/models/services/item_model.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/common_widgets/app_bar.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:provider/provider.dart'; + +class BookAppointmentsItemView extends StatelessWidget { + const BookAppointmentsItemView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar( + title: "Select Services", + 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: [ + Container( + height: 40, + width: double.infinity, + color: MyColors.darkTextColor, + alignment: Alignment.centerLeft, + child: "${appointmentsVM.selectedSubServicesCounter} Item(s) Selected".toText(fontSize: 16, color: MyColors.white).horPaddingMain(), + ), + 16.height, + Column( + children: [ + "Few services are not available on home location. Change the location to workshop to full access the services".toText(fontSize: 12, isItalic: true, color: MyColors.lightTextColor), + 8.height, + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Change location or service: ".toText(fontSize: 14, isBold: true), + "Edit".toText(fontSize: 14, isBold: true, isUnderLine: true, color: MyColors.adPendingStatusColor), + 5.width, + MyAssets.icEdit.buildSvg(width: 17), + ], + ).onPress(() {}), + 16.height, + Divider(), + ], + ).horPaddingMain(), + appointmentsVM.serviceItems.isEmpty + ? Expanded(child: Center(child: "No Items to show.".toText(fontSize: 16, color: MyColors.lightTextColor))) + : ListView.separated( + separatorBuilder: (BuildContext context, int index) => Divider(), + itemCount: appointmentsVM.serviceItems.length, + itemBuilder: (BuildContext context, int index) { + ItemData itemData = appointmentsVM.serviceItems[index]; + return ServiceItemWithPriceCheckBox( + description: "Some description about the sub-services", + title: itemData.name!, + isSelected: itemData.isUpdateOrSelected!, + onSelection: (bool value) { + appointmentsVM.onItemUpdateOrSelected(index, !itemData.isUpdateOrSelected!, itemData.id!); + }, + priceWidget: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // TODO: This Price will be decided according to the service selected + itemData.price!.split(".").first.toText(fontSize: 30, isBold: true), + " SAR".toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor).paddingOnly(bottom: 5), + ], + ), + ); + }, + ).expand(), + Column( + children: [ + Divider( + height: 1, + thickness: 0.7, + ), + 8.height, + if (appointmentsVM.selectSubServicesError != "") + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + appointmentsVM.selectSubServicesError.toText(fontSize: 14, color: Colors.red), + ], + ).paddingOnly(right: 10), + 8.height, + Row( + children: [ + Expanded( + child: ShowFillButton( + txtColor: MyColors.black, + maxHeight: 55, + title: "Cancel", + onPressed: () { + appointmentsVM.resetCategorySelectionBottomSheet(); + pop(context); + }, + backgroundColor: MyColors.greyButtonColor, + ), + ), + 12.width, + Expanded( + child: Builder( + builder: (BuildContext context) { + return ShowFillButton( + maxHeight: 55, + title: "Next", + onPressed: () async { + bool resp = appointmentsVM.onSubServicesNextPressed(); + if (resp) { + Utils.showLoading(context); + await appointmentsVM.mergeServiceIntoAvailableSchedules(); + appointmentsVM.resetCategorySelectionBottomSheet(); + Navigator.of(context).pushNamedAndRemoveUntil(AppRoutes.bookAppointmenServicesView, (Route route) => false); + + // appointmentsVM.mergeServiceInAvailableSchedule(); + } + }, + backgroundColor: !appointmentsVM.isServiceSelectionValidated() ? MyColors.lightTextColor.withOpacity(0.6) : MyColors.primaryColor, + ); + }, + ), + ), + ], + ).horPaddingMain(), + 16.height, + ], + ), + ], + ); + }, + )); + } +} diff --git a/lib/views/appointments/review_appointment_view.dart b/lib/views/appointments/review_appointment_view.dart new file mode 100644 index 0000000..841f4c0 --- /dev/null +++ b/lib/views/appointments/review_appointment_view.dart @@ -0,0 +1,325 @@ +import 'package:car_customer_app/view_models/appointments_view_model.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/service_schedule_model.dart'; +import 'package:mc_common_app/models/services/item_model.dart'; +import 'package:mc_common_app/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/dropdown/dropdow_field.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; +import 'package:provider/provider.dart'; + +class ReviewAppointment extends StatelessWidget { + const ReviewAppointment({Key? key}) : super(key: key); + + Widget buildBranchInfoCard({required BuildContext context}) { + AppointmentsVM appointmentsVM = context.read(); + return Padding( + padding: const EdgeInsets.only( + bottom: 10, + left: 21, + right: 21, + ), + child: Row( + children: [ + Image.asset( + MyAssets.bnCar, + width: 80, + height: 60, + fit: BoxFit.cover, + ), + 12.width, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + (appointmentsVM.selectedBranchModel!.branchName ?? "").toText(fontSize: 16, isBold: true), + Row( + children: [ + LocaleKeys.location.tr().toText(color: MyColors.lightTextColor, fontSize: 12), + 2.width, + ": ${appointmentsVM.selectedBranchModel!.branchDescription ?? ""}".toText(fontSize: 12), + ], + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + "4.9".toText( + isUnderLine: true, + isBold: true, + fontSize: 12, + ), + 2.width, + MyAssets.starIcon.buildSvg(width: 12), + ], + ), + ], + ), + ], + ), + ), + ], + ).onPress(() {}).toWhiteContainer(width: double.infinity, allPading: 12)); + } + + Widget buildServicesInfoCard({required BuildContext context}) { + AppointmentsVM appointmentsVM = context.read(); + return ListView.builder( + shrinkWrap: true, + itemCount: appointmentsVM.serviceAppointmentScheduleList.length, + itemBuilder: (BuildContext context, int scheduleIndex) { + ServiceAppointmentScheduleModel scheduleData = appointmentsVM.serviceAppointmentScheduleList[scheduleIndex]; + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: "Schedule: ${scheduleIndex + 1}".toText(fontSize: 20, isBold: true), + ), + ], + ), + 10.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), + ], + ); + }), + ), + ], + ], + ); + }, + separatorBuilder: (BuildContext context, int index) => Divider(thickness: 2), + ).paddingOnly(bottom: 10), + ], + ), + ], + ).toWhiteContainer(width: double.infinity, allPading: 12, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 10)); + }, + ); + // AppointmentsVM appointmentsVM = context.read(); + // 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() { + return 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), + "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(); + List 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, + children: [ + "${allSelectedItems[index].name}".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), + "${allSelectedItems[index].price} SAR".toText(fontSize: 12, isBold: true), + ], + ), + ), + ), + 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( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + "120".toText(fontSize: 29, isBold: true), + 2.width, + "SAR".toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5), + ], + ) + ], + ), + 10.height, + ], + ).toWhiteContainer(width: double.infinity, allPading: 12, margin: EdgeInsets.only(left: 21, right: 21, top: 10, bottom: 21)); + } + + Widget buildNextButtonFooter() { + return Container( + height: 90, + color: MyColors.white, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Divider(thickness: 0.7, height: 3), + 10.height, + SizedBox( + width: double.infinity, + child: ShowFillButton( + maxHeight: 55, + backgroundColor: MyColors.primaryColor, + title: "Book Appointment", + onPressed: () {}, + ).paddingOnly(bottom: 12, left: 21, right: 21), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar( + title: "Review Appointment", + isRemoveBackButton: false, + isDrawerEnabled: false, + actions: [MyAssets.searchIcon.buildSvg().paddingOnly(right: 21)], + onBackButtonTapped: () => Navigator.pop(context), + ), + body: Stack( + alignment: Alignment.bottomCenter, + children: [ + SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildBranchInfoCard(context: context), + buildServicesInfoCard(context: context), + buildTimeAndLocationInfoCard(), + buildChargesBreakDown(context: context), + ], + ), + ), + buildNextButtonFooter(), + ], + ), + ); + } +} diff --git a/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart b/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart new file mode 100644 index 0000000..4bab6e6 --- /dev/null +++ b/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart @@ -0,0 +1,194 @@ +import 'package:car_customer_app/view_models/appointments_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:mc_common_app/config/routes.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/models/widgets_models.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/widgets/button/show_fill_button.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/txt_field.dart'; +import 'package:provider/provider.dart'; + +class AppointmentServicePickBottomSheet extends StatelessWidget { + const AppointmentServicePickBottomSheet({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + AppointmentsVM appointmentsVM = context.watch(); + return SizedBox( + height: MediaQuery.of(context).size.height * 0.85, + child: Column( + children: [ + Container( + margin: const EdgeInsets.all(8), + height: 8, + width: 60, + decoration: const BoxDecoration(color: MyColors.lightTextColor, borderRadius: BorderRadius.all(Radius.circular(20))), + ), + 12.height, + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + "Select Category".toText(fontSize: 24, isBold: true), + ], + ), + 30.height, + Expanded( + child: ListView( + children: [ + Builder( + builder: (context) { + return DropdownField( + (DropValue value) => appointmentsVM.updateProviderCategoryId(SelectionModel(selectedId: value.id, selectedOption: value.value, itemPrice: value.subValue)), + list: appointmentsVM.branchCategories, + hint: "Select Category", + dropdownValue: appointmentsVM.branchSelectedCategoryId.selectedId != -1 + ? DropValue(appointmentsVM.branchSelectedCategoryId.selectedId, appointmentsVM.branchSelectedCategoryId.selectedOption, "") + : null, + ); + }, + ), + if (appointmentsVM.isFetchingServices) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [const CircularProgressIndicator().paddingAll(10)], + ), + ] else if (appointmentsVM.branchServices.isNotEmpty) ...[ + 8.height, + Builder( + builder: (context) { + List serviceCategories = []; + for (var element in appointmentsVM.branchServices) { + if (element.categoryId == appointmentsVM.branchSelectedCategoryId.selectedId) { + serviceCategories.add(DropValue( + element.serviceProviderServiceId ?? 0, + element.serviceDescription!, + "", + )); + } + } + + return DropdownField( + (DropValue value) => appointmentsVM.updateBranchServiceId(SelectionModel(selectedId: value.id, selectedOption: value.value, itemPrice: value.subValue)), + list: serviceCategories, + hint: "Select Service", + dropdownValue: appointmentsVM.branchSelectedServiceId.selectedId != -1 + ? DropValue(appointmentsVM.branchSelectedServiceId.selectedId, appointmentsVM.branchSelectedServiceId.selectedOption, "") + : null, + ); + }, + ), + ], + if (appointmentsVM.branchSelectedServiceId.selectedId != -1 && !appointmentsVM.isFetchingServices) ...[ + 16.height, + Row( + children: [ + "Select Service Location".toText( + fontSize: 16, + isBold: true, + color: MyColors.black, + ), + ], + ), + 8.height, + Row( + children: [ + Expanded( + child: ShowFillButton( + isFilled: appointmentsVM.isHomeTapped, + maxHeight: 48, + title: "Home", + txtColor: appointmentsVM.isHomeTapped ? MyColors.white : MyColors.darkTextColor, + onPressed: () => appointmentsVM.updateIsHomeTapped(true), + ), + ), + 12.width, + Expanded( + child: ShowFillButton( + isFilled: !appointmentsVM.isHomeTapped, + txtColor: !appointmentsVM.isHomeTapped ? MyColors.white : MyColors.darkTextColor, + maxHeight: 48, + title: "Workshop", + onPressed: () => appointmentsVM.updateIsHomeTapped(false), + ), + ), + ], + ), + if (appointmentsVM.isHomeTapped) ...[ + 8.height, + TxtField( + hint: 'Pick Home Location', + errorValue: appointmentsVM.pickHomeLocationError, + value: appointmentsVM.pickedHomeLocation, + isNeedClickAll: true, + postfixData: Icons.location_on, + postFixDataColor: MyColors.darkTextColor, + onTap: () { + //TODO: open the place picked to pick the location and save it in provider. + appointmentsVM.updatePickedHomeLocation("PM58+F97, Al Olaya, Riyadh 12333"); + }, + ), + 14.height, + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Icon( + Icons.warning, + color: MyColors.adPendingStatusColor, + size: 19, + ).paddingOnly(bottom: 2), + 3.width, + "Some services are not available on home location.".toText( + color: MyColors.adPendingStatusColor, + fontSize: 12, + isItalic: true, + ), + ], + ), + ] + ], + ], + ), + ), + SizedBox( + width: double.infinity, + child: Column( + children: [ + if (appointmentsVM.isHomeTapped && !appointmentsVM.isFetchingServices) ...[ + const Divider(thickness: 1, height: 1), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // TODO: This Price will be decided according to the service selected + 150.toString().toText(fontSize: 30, isBold: true), + "SAR".toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor).paddingOnly(bottom: 5), + ], + ), + "These charges are additional to the actual service charges. For heavy items the charges may vary.".toText(fontSize: 12, color: MyColors.lightTextColor), + 22.height, + ], + SizedBox( + width: double.infinity, + child: ShowFillButton( + maxHeight: 55, + backgroundColor: !appointmentsVM.isServiceSelectionValidated() ? MyColors.lightTextColor.withOpacity(0.6) : MyColors.primaryColor, + title: "Next", + onPressed: () { + bool isValidated = appointmentsVM.isServiceSelectionValidated(); + if (isValidated) { + appointmentsVM.getServiceItems(appointmentsVM.branchSelectedServiceId.selectedId); + navigateWithName(context, AppRoutes.bookAppointmentsItemView); + } + }, + ), + ), + ], + ), + ).paddingOnly(bottom: 20) + ], + )).horPaddingMain(); + } +} diff --git a/lib/views/appointments/widgets/service_item_with_price_checkbox.dart b/lib/views/appointments/widgets/service_item_with_price_checkbox.dart new file mode 100644 index 0000000..f8269c6 --- /dev/null +++ b/lib/views/appointments/widgets/service_item_with_price_checkbox.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; + +class ServiceItemWithPriceCheckBox extends StatelessWidget { + final bool isSelected; + final String title, description; + final Widget priceWidget; + + final Function(bool) onSelection; + + const ServiceItemWithPriceCheckBox({ + required this.isSelected, + required this.title, + required this.description, + this.priceWidget = const SizedBox(), + required this.onSelection, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Checkbox( + value: isSelected, + onChanged: (bool? v) { + onSelection(v ?? false); + }, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + title.toText(fontSize: 16, isBold: true).paddingOnly(top: 10), + description.toText(fontSize: 12, color: MyColors.lightTextColor), + priceWidget, + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/dashboard/dashboard_page.dart b/lib/views/dashboard/dashboard_page.dart index 1c9fa86..7145d88 100644 --- a/lib/views/dashboard/dashboard_page.dart +++ b/lib/views/dashboard/dashboard_page.dart @@ -2,16 +2,17 @@ import 'dart:async'; 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/providers_view_model.dart'; import 'package:car_customer_app/views/dashboard/fragments/ads_fragment.dart'; import 'package:car_customer_app/views/dashboard/fragments/appointments_fragment.dart'; import 'package:car_customer_app/views/dashboard/fragments/home_fragment.dart'; -import 'package:car_customer_app/views/dashboard/fragments/providers_fragment.dart'; import 'package:car_customer_app/views/dashboard/fragments/settings_fragment.dart'; import 'package:car_customer_app/views/dashboard/widgets/bottom_nav_bar.dart'; import 'package:car_customer_app/views/dashboard/widgets/drawer_widget.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/config/routes.dart'; +import 'package:mc_common_app/models/widgets_models.dart'; +import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; @@ -36,9 +37,9 @@ class _DashboardPageState extends State { fetchUsername(); scheduleMicrotask(() { context.read().populateAppointmentsFilterList(); - context.read().populateProvidersFilterList(); + context.read().populateProvidersFilterList(); context.read().populateAdsFilterList(); - context.read().getAllNearBranches(); + context.read().getAllNearBranches(); _onRefresh(); }); } @@ -55,6 +56,22 @@ class _DashboardPageState extends State { if (adVm.exploreAds.isEmpty) { await adVm.getExploreAds(); } + + final adVM = context.read(); + if (adVM.vehicleTypes.isEmpty) { + await adVM.getVehicleTypes(); + } + + if (adVM.vehicleAdsDurations.isEmpty) { + await adVM.getVehicleAdsDuration(); + } + adVM.updateVehicleAdDurationId( + SelectionModel( + selectedId: adVM.vehicleAdsDurations.first.id ?? 0, + selectedOption: "${adVM.vehicleAdsDurations.first.days} Days", + itemPrice: adVM.vehicleAdsDurations.first.price!.toInt().toString(), + ), + ); } fetchUsername() async {} @@ -69,7 +86,7 @@ class _DashboardPageState extends State { String getPageTitle(int index) { if (index == 0) { - return "Branchs"; + return "Branches"; } if (index == 1) { return "Appointments"; @@ -89,6 +106,7 @@ class _DashboardPageState extends State { @override Widget build(BuildContext context) { bool isHomePage = context.watch().selectedNavbarBarIndex == 2; + bool isAdsPage = context.watch().selectedNavbarBarIndex == 3; return Scaffold( appBar: CustomAppBar( backgroundColor: null, @@ -97,7 +115,15 @@ class _DashboardPageState extends State { profileImageUrl: MyAssets.bnCar, isRemoveBackButton: true, isDrawerEnabled: isHomePage ? true : false, - actions: [(isHomePage ? MyAssets.notificationsBellIcon : MyAssets.searchIcon).buildSvg().paddingOnly(right: 21)], + actions: [ + (isHomePage + ? MyAssets.notificationsBellIcon.buildSvg() + : InkWell( + onTap: () => navigateWithName(context, AppRoutes.adsSearchFilterScreen), + child: MyAssets.searchIcon.buildSvg(), + )) + .paddingOnly(right: 21) + ], ), drawer: CustomDrawer(dashboardVM: context.watch()), bottomNavigationBar: CustomBottomNavbar(), diff --git a/lib/views/dashboard/fragments/ads_fragment.dart b/lib/views/dashboard/fragments/ads_fragment.dart index d13952c..0bfc170 100644 --- a/lib/views/dashboard/fragments/ads_fragment.dart +++ b/lib/views/dashboard/fragments/ads_fragment.dart @@ -1,8 +1,13 @@ 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/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/advertisment_models/ad_details_model.dart'; +import 'package:mc_common_app/models/widgets_models.dart'; import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/views/advertisement/ads_list.dart'; @@ -20,6 +25,16 @@ class AdsFragment extends StatelessWidget { await adVM.getMyAds(); } + List getAdsList(AdVM adVM) { + if (adVM.isExploreAdsTapped) { + return adVM.exploreAdsFilteredList; + } + if (adVM.myAdsFilteredList.isNotEmpty) { + return adVM.myAdsFilteredList; + } + return []; + } + @override Widget build(BuildContext context) { return Scaffold( @@ -62,18 +77,21 @@ class AdsFragment extends StatelessWidget { ), ], ).horPaddingMain(), - if (adVM.isExploreAdsTapped && adVM.exploreAds.isNotEmpty) ...[ + if (adVM.isExploreAdsTapped) ...[ 16.height, - FiltersList(filterList: adVM.exploreAdsFilterOptions, onFilterTapped: (index, selectedFilterId) => adVM.applyFilterOnExploreAds(index: index), needLeftPadding: false) + FiltersList( + filterList: adVM.exploreAdsFilterOptions, + onFilterTapped: (index, selectedFilterId) => adVM.applyFilterOnExploreAds(index: index, createdByRoleFilter: selectedFilterId.toCreatedByRoleEnum()), + needLeftPadding: false) .paddingOnly(left: 21), ], - if (!adVM.isExploreAdsTapped && adVM.myAds.isNotEmpty) ...[ + if (!adVM.isExploreAdsTapped) ...[ 16.height, FiltersList( - filterList: adVM.myAdsFilterOptions, - onFilterTapped: (index, selectedFilterId) => adVM.applyFilterOnMyAds(index: index, selectedFilterId: selectedFilterId), - needLeftPadding: false) - .paddingOnly(left: 21), + filterList: adVM.myAdsFilterOptions, + onFilterTapped: (index, selectedFilterId) => adVM.applyFilterOnMyAds(index: index, adPostStatusEnum: selectedFilterId.toAdPostEnum()), + needLeftPadding: false, + ).paddingOnly(left: 21), ], ], ); @@ -85,15 +103,7 @@ class AdsFragment extends StatelessWidget { onRefresh: () => onRefreshAds(context), child: Consumer( builder: (BuildContext context, AdVM adVM, Widget? child) { - return BuildAdsList( - isAdsFragment: true, - shouldShowAdStatus: !adVM.isExploreAdsTapped, - adsList: adVM.isExploreAdsTapped && adVM.exploreAdsFilteredList.isNotEmpty - ? adVM.exploreAdsFilteredList - : !adVM.isExploreAdsTapped && adVM.myAdsFilteredList.isNotEmpty - ? adVM.myAdsFilteredList - : [], - ); + return BuildAdsList(isAdsFragment: true, shouldShowAdStatus: !adVM.isExploreAdsTapped, adsList: getAdsList(adVM)); }, ), ), @@ -102,10 +112,8 @@ class AdsFragment extends StatelessWidget { ), ), floatingActionButton: FloatingActionButton( - onPressed: () { - context.read().getVehicleTypes(); - - navigateWithName(context, AppRoutes.createAdView); + onPressed: () async { + navigateWithName(context, AppRoutes.selectAdTypeView, arguments: injector.get().currentAppType == AppType.provider); }, backgroundColor: MyColors.darkPrimaryColor, child: Icon( diff --git a/lib/views/dashboard/fragments/appointments_fragment.dart b/lib/views/dashboard/fragments/appointments_fragment.dart index 137ae1e..aff1704 100644 --- a/lib/views/dashboard/fragments/appointments_fragment.dart +++ b/lib/views/dashboard/fragments/appointments_fragment.dart @@ -41,7 +41,11 @@ class AppointmentsFragment extends StatelessWidget { itemCount: appointmentsVM.myAppointments.length, itemBuilder: (BuildContext context, int index) { return BuildAppointmentContainerForCustomer( - onTapped: () => navigateWithName(context, AppRoutes.appointmentDetailView, arguments: appointmentsVM.myAppointments[index]), + onTapped: () => navigateWithName( + context, + AppRoutes.appointmentDetailView, + arguments: appointmentsVM.myAppointments[index], + ), appointmentListModel: appointmentsVM.myAppointments[index], ); }, diff --git a/lib/views/dashboard/fragments/branches_fragment.dart b/lib/views/dashboard/fragments/branches_fragment.dart index d0ab34c..792202f 100644 --- a/lib/views/dashboard/fragments/branches_fragment.dart +++ b/lib/views/dashboard/fragments/branches_fragment.dart @@ -1,17 +1,18 @@ -import 'package:car_customer_app/view_models/providers_view_model.dart'; +import 'package:car_customer_app/view_models/appointments_view_model.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; - import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/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/widgets/common_widgets/categories_list.dart'; import 'package:mc_common_app/widgets/common_widgets/provider_details_card.dart'; import 'package:provider/provider.dart'; -import 'package:easy_localization/easy_localization.dart'; class BranchesFragment extends StatelessWidget { const BranchesFragment({Key? key}) : super(key: key); @@ -23,53 +24,53 @@ class BranchesFragment extends StatelessWidget { width: double.infinity, height: double.infinity, child: Consumer( - builder: (BuildContext context, ProvidersVM providersVM, Widget? child) { + builder: (BuildContext context, AppointmentsVM appointmentsVM, Widget? child) { return Column( children: [ 16.height, FiltersList( - filterList: providersVM.providersFilterOptions, - onFilterTapped: (index, selectedFilterId) => providersVM.applyFilterOnProviders(index: index), + filterList: appointmentsVM.providersFilterOptions, + onFilterTapped: (index, selectedFilterId) => appointmentsVM.applyFilterOnProviders(index: index), ), 16.height, Expanded( child: RefreshIndicator( onRefresh: () async { - context.read().getAllNearBranches(isNeedToRebuild: true); + context.read().getAllNearBranches(isNeedToRebuild: true); }, child: SingleChildScrollView( child: Container( width: double.infinity, height: MediaQuery.of(context).size.height / 1.37, - child: Consumer( + child: Consumer( builder: (context, model, _) { if (model.state == ViewState.busy) { return const Center(child: CircularProgressIndicator()); } else { - return model.branches?.data == null - ? const Center(child: Text("No Branch Found")) - : model.branches!.data!.isEmpty - ? Center(child: Text(LocaleKeys.no_branch.tr())) - : ListView.separated( - itemBuilder: (context, index) { - return ProviderDetailsCard( - onCardTapped: () { - navigateWithName(context, AppRoutes.branchDetailPage, arguments: model.branches!.data![index]); - }, - providerImageUrl: MyAssets.bnCar, - title: model.branches!.data![index].branchName ?? "", - providerLocation: model.branches!.data![index].distanceKm.toString() + " KM", - providerName: model.branches!.data![index].serviceProviderName ?? "", - providerRatings: "4.9", - items: model.branches!.data![index].branchServices, - ); - }, - separatorBuilder: (context, index) { - return 12.height; + return model.nearbyBranches.isEmpty + ? Center(child: LocaleKeys.no_branch.tr().toText(fontSize: 16, color: MyColors.lightTextColor)) + : ListView.separated( + itemCount: model.nearbyBranches.length, + itemBuilder: (context, index) { + BranchDetailModel branchDetailModel = model.nearbyBranches[index]; + + return ProviderDetailsCard( + onCardTapped: () { + navigateWithName(context, AppRoutes.branchDetailPage, arguments: branchDetailModel); }, - itemCount: model.branches!.data!.length, - padding: const EdgeInsets.all(12), + providerImageUrl: MyAssets.bnCar, + title: branchDetailModel.branchName ?? "", + providerLocation: branchDetailModel.distanceKm.toString() + " KM", + providerName: branchDetailModel.serviceProviderName ?? "", + providerRatings: "4.9", + services: branchDetailModel.branchServices, ); + }, + separatorBuilder: (context, index) { + return 12.height; + }, + padding: const EdgeInsets.all(12), + ); } }, ), diff --git a/lib/views/dashboard/fragments/home_fragment.dart b/lib/views/dashboard/fragments/home_fragment.dart index c7c4d6b..6a219cc 100644 --- a/lib/views/dashboard/fragments/home_fragment.dart +++ b/lib/views/dashboard/fragments/home_fragment.dart @@ -1,10 +1,10 @@ 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/providers_view_model.dart'; import 'package:car_customer_app/views/appointments/widgets/customer_appointment_slider_widget.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/utils/enums.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/views/advertisement/ads_list.dart'; import 'package:mc_common_app/widgets/common_widgets/my_service_provider.dart'; @@ -43,7 +43,7 @@ class HomeFragment extends StatelessWidget { onSubtitleTapped: () { print("value: ${AppState().getUser.data!.accessToken}"); context.read().onNavbarTapped(0); - context.read().applyFilterOnProviders(index: 0); + context.read().applyFilterOnProviders(index: 0); }).horPaddingMain(), const ServiceProviderWidget().horPaddingMain(), Consumer( @@ -80,7 +80,7 @@ class HomeFragment extends StatelessWidget { onSubtitleTapped: () { context.read().onNavbarTapped(3); context.read().updateIsExploreAds(true); - context.read().applyFilterOnExploreAds(index: 0); + context.read().applyFilterOnExploreAds(index: 0, createdByRoleFilter: CreatedByRoleEnum.allAds); }, ).horPaddingMain(), BuildAdsList( diff --git a/lib/views/dashboard/fragments/providers_fragment.dart b/lib/views/dashboard/fragments/providers_fragment.dart index 09e6dab..f2f7593 100644 --- a/lib/views/dashboard/fragments/providers_fragment.dart +++ b/lib/views/dashboard/fragments/providers_fragment.dart @@ -1,61 +1,60 @@ -import 'package:car_customer_app/view_models/appointments_view_model.dart'; -import 'package:car_customer_app/view_models/providers_view_model.dart'; -import 'package:flutter/material.dart'; -import 'package:mc_common_app/classes/consts.dart'; -import 'package:mc_common_app/config/routes.dart'; -import 'package:mc_common_app/extensions/int_extensions.dart'; -import 'package:mc_common_app/theme/colors.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/provider_details_card.dart'; -import 'package:provider/provider.dart'; - -class ProvidersFragment extends StatelessWidget { - const ProvidersFragment({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - color: MyColors.backgroundColor, - width: double.infinity, - height: double.infinity, - child: Consumer( - builder: (BuildContext context, ProvidersVM providersVM, Widget? child) { - return Column( - children: [ - 16.height, - FiltersList( - filterList: providersVM.providersFilterOptions, - onFilterTapped: (index, selectedFilterId) => providersVM.applyFilterOnProviders(index: index), - ), - 16.height, - Expanded( - child: Container( - child: ListView.builder( - shrinkWrap: true, - itemCount: 30, - itemBuilder: (BuildContext context, int index) { - return ProviderDetailsCard( - onCardTapped: () { - if (context.read().providerCategories.isEmpty) { - context.read().getProviderCategories(); - } - navigateWithName(context, AppRoutes.bookAppointmenServicesView); - }, - providerImageUrl: MyAssets.bnCar, - title:"", - providerLocation: " 3km", - providerName: "Al Ahmed Maintenance", - providerRatings: "4.9", - ); - }, - ), - ), - ), - ], - ); - }, - ), - ); - } -} +// import 'package:car_customer_app/view_models/appointments_view_model.dart'; +// import 'package:flutter/material.dart'; +// import 'package:mc_common_app/classes/consts.dart'; +// import 'package:mc_common_app/config/routes.dart'; +// import 'package:mc_common_app/extensions/int_extensions.dart'; +// import 'package:mc_common_app/theme/colors.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/provider_details_card.dart'; +// import 'package:provider/provider.dart'; +// +// class ProvidersFragment extends StatelessWidget { +// const ProvidersFragment({Key? key}) : super(key: key); +// +// @override +// Widget build(BuildContext context) { +// return Container( +// color: MyColors.backgroundColor, +// width: double.infinity, +// height: double.infinity, +// child: Consumer( +// builder: (BuildContext context, AppointmentsVM appointmentsVM, Widget? child) { +// return Column( +// children: [ +// 16.height, +// FiltersList( +// filterList: appointmentsVM.providersFilterOptions, +// onFilterTapped: (index, selectedFilterId) => appointmentsVM.applyFilterOnProviders(index: index), +// ), +// 16.height, +// Expanded( +// child: Container( +// child: ListView.builder( +// shrinkWrap: true, +// itemCount: 30, +// itemBuilder: (BuildContext context, int index) { +// return ProviderDetailsCard( +// onCardTapped: () { +// if (context.read().branchCategories.isEmpty) { +// context.read().getBranchCategories(); +// } +// navigateWithName(context, AppRoutes.bookAppointmenServicesView); +// }, +// providerImageUrl: MyAssets.bnCar, +// title: "", +// providerLocation: " 3km", +// providerName: "Al Ahmed Maintenance", +// providerRatings: "4.9", +// ); +// }, +// ), +// ), +// ), +// ], +// ); +// }, +// ), +// ); +// } +// } diff --git a/lib/views/provider/branch_detail_page.dart b/lib/views/provider/branch_detail_page.dart index dbace34..34092a3 100644 --- a/lib/views/provider/branch_detail_page.dart +++ b/lib/views/provider/branch_detail_page.dart @@ -1,24 +1,25 @@ +import 'package:car_customer_app/view_models/appointments_view_model.dart'; import 'package:car_customer_app/views/provider/sheet/items_list_sheet.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; -import 'package:mc_common_app/models/services/branch_model.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:mc_common_app/models/services/service_model.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/navigator.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/extensions/extensions_widget.dart'; +import 'package:provider/provider.dart'; class BranchDetailPage extends StatefulWidget { - BranchModel branchModel; + final BranchDetailModel branchDetailModel; - BranchDetailPage({required this.branchModel}); + BranchDetailPage({required this.branchDetailModel}); @override State createState() => _BranchDetailPageState(); @@ -29,7 +30,7 @@ class _BranchDetailPageState extends State { void initState() { // TODO: implement initState super.initState(); - if (widget.branchModel.branchServices!.length > 0) widget.branchModel.branchServices?.first.isExpanded = true; + if (widget.branchDetailModel.branchServices!.length > 0) widget.branchDetailModel.branchServices?.first.isExpandedOrSelected = true; } @override @@ -52,7 +53,7 @@ class _BranchDetailPageState extends State { children: [ Image.asset(MyAssets.bnCar), 12.height, - "${widget.branchModel.branchName} | ${widget.branchModel.serviceProviderName}".toString().toText( + "${widget.branchDetailModel.branchName} | ${widget.branchDetailModel.serviceProviderName}".toString().toText( fontSize: 16, isBold: true, ), @@ -60,14 +61,14 @@ class _BranchDetailPageState extends State { children: [ (LocaleKeys.location.tr() + ":").toText(color: MyColors.lightTextColor, fontSize: 12), 4.width, - (widget.branchModel.distanceKm.toString() + " km").toText(fontSize: 12, isBold: true), + (widget.branchDetailModel.distanceKm.toString() + " km").toText(fontSize: 12, isBold: true), ], ), Row( children: [ ("Time" + ":").toText(color: MyColors.lightTextColor, fontSize: 12), 4.width, - "${widget.branchModel.openTime} - ${widget.branchModel.closeTime}".toText(fontSize: 12, isBold: true), + "${widget.branchDetailModel.openTime} - ${widget.branchDetailModel.closeTime}".toText(fontSize: 12, isBold: true), ], ), 2.height, @@ -123,7 +124,7 @@ class _BranchDetailPageState extends State { ) .onPress( () { - navigateWithName(context, AppRoutes.providerProfilePage, arguments: widget.branchModel.serviceProviderId); + navigateWithName(context, AppRoutes.providerProfilePage, arguments: widget.branchDetailModel.serviceProviderId); }, ), ], @@ -134,7 +135,7 @@ class _BranchDetailPageState extends State { color: MyColors.lightTextColor, isBold: true, ), - if (widget.branchModel.branchServices!.length == 0) "No Services Available".toText(fontSize: 12, isBold: true), + if (widget.branchDetailModel.branchServices!.length == 0) "No Services Available".toText(fontSize: 12, isBold: true), showServicesList(), ], ).toWhiteContainer( @@ -150,11 +151,11 @@ class _BranchDetailPageState extends State { title: "Book Appointment", maxWidth: double.infinity, margin: EdgeInsets.all(21), - onPressed: () {}, - ).toContainer( - paddingAll: 0, - backgroundColor: Colors.white, - ), + onPressed: () { + navigateWithName(context, AppRoutes.bookAppointmenServicesView); + context.read().updateSelectedBranch(widget.branchDetailModel); + }, + ).toContainer(paddingAll: 0, backgroundColor: Colors.white), ], ), ); @@ -165,7 +166,7 @@ class _BranchDetailPageState extends State { itemBuilder: (context, index) { return ExpansionTile( tilePadding: EdgeInsets.zero, - title: (widget.branchModel.branchServices![index].serviceDescription ?? "").toText( + title: (widget.branchDetailModel.branchServices![index].serviceDescription ?? "").toText( fontSize: 16, isBold: true, ), @@ -173,11 +174,11 @@ class _BranchDetailPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - showItem("Allowing home service:", (widget.branchModel.branchServices![index].isAllowAppointment ?? false) ? "Yes" : "No", valueColor: Colors.green), - showItem("Home service range", widget.branchModel.branchServices![index].customerLocationRange.toString() + "KM"), - showItem("Charges per KM", widget.branchModel.branchServices![index].customerLocationRange.toString() + "SAR"), + showItem("Allowing home service:", (widget.branchDetailModel.branchServices![index].isAllowAppointment ?? false) ? "Yes" : "No", valueColor: Colors.green), + showItem("Home service range", widget.branchDetailModel.branchServices![index].customerLocationRange.toString() + "KM"), + showItem("Charges per KM", widget.branchDetailModel.branchServices![index].customerLocationRange.toString() + "SAR"), 8.height, - (widget.branchModel.branchServices![index].itemsCount.toString() + "+ items").toText( + (widget.branchDetailModel.branchServices![index].itemsCount.toString() + "+ items").toText( fontSize: 12, isBold: true, color: MyColors.primaryColor, @@ -186,18 +187,18 @@ class _BranchDetailPageState extends State { 20.height, ], ).onPress(() { - showMyBottomSheet(context, child: ItemsListSheet(widget.branchModel.branchServices![index].serviceProviderServiceId ?? 0)); + showMyBottomSheet(context, child: ItemsListSheet(widget.branchDetailModel.branchServices![index].serviceProviderServiceId ?? 0)); }), ], onExpansionChanged: (value) { setState(() { - widget.branchModel.branchServices![index].isExpanded = value; + widget.branchDetailModel.branchServices![index].isExpandedOrSelected = value; }); }, backgroundColor: Colors.transparent, collapsedBackgroundColor: Colors.transparent, - initiallyExpanded: widget.branchModel.branchServices![index].isExpanded, - trailing: widget.branchModel.branchServices![index].isExpanded ? Icon(Icons.keyboard_arrow_up) : Icon(Icons.keyboard_arrow_down), + initiallyExpanded: widget.branchDetailModel.branchServices![index].isExpandedOrSelected, + trailing: widget.branchDetailModel.branchServices![index].isExpandedOrSelected ? Icon(Icons.keyboard_arrow_up) : Icon(Icons.keyboard_arrow_down), ); }, separatorBuilder: (context, index) { @@ -205,7 +206,7 @@ class _BranchDetailPageState extends State { height: 1, ); }, - itemCount: widget.branchModel.branchServices!.length, + itemCount: widget.branchDetailModel.branchServices!.length, physics: NeverScrollableScrollPhysics(), shrinkWrap: true, ); diff --git a/lib/views/provider/provider_profile_page.dart b/lib/views/provider/provider_profile_page.dart index a16f39f..3071156 100644 --- a/lib/views/provider/provider_profile_page.dart +++ b/lib/views/provider/provider_profile_page.dart @@ -1,4 +1,5 @@ -import 'package:car_customer_app/view_models/providers_view_model.dart'; +import 'package:car_customer_app/view_models/appointments_view_model.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/config/routes.dart'; @@ -12,10 +13,9 @@ import 'package:mc_common_app/widgets/common_widgets/provider_details_card.dart' import 'package:mc_common_app/widgets/empty_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:provider/provider.dart'; -import 'package:easy_localization/easy_localization.dart'; class ProviderProfilePage extends StatefulWidget { - int providerId; + final int providerId; ProviderProfilePage({required this.providerId}); @@ -27,7 +27,7 @@ class _ProviderProfilePageState extends State { @override void initState() { super.initState(); - context.read().getBranchAndServices(widget.providerId ?? 0); + context.read().getBranchAndServices(widget.providerId); } @override @@ -40,11 +40,11 @@ class _ProviderProfilePageState extends State { width: double.infinity, height: double.infinity, padding: EdgeInsets.all(21), - child: Consumer( + child: Consumer( builder: (context, model, _) { - return model.providerModel == null + return model.providerProfileModel == null ? const Center(child: CircularProgressIndicator()) - : model.providerModel!.data == null + : model.providerProfileModel == null ? const EmptyWidget() : SingleChildScrollView( child: Column( @@ -55,7 +55,7 @@ class _ProviderProfilePageState extends State { //TODO: company logo/banner not added form provider app yet Image.asset(MyAssets.bnCar), 12.height, - model.providerModel!.data!.companyName.toString().toText( + model.providerProfileModel!.companyName.toString().toText( fontSize: 16, isBold: true, ), @@ -77,28 +77,28 @@ class _ProviderProfilePageState extends State { allPading: 12, ), 12.height, - model.providerModel!.data!.serviceProviderBranch == null + model.providerProfileModel!.serviceProviderBranch == null ? const Center(child: Text("No Branch Found")) - : model.providerModel!.data!.serviceProviderBranch!.isEmpty + : model.providerProfileModel!.serviceProviderBranch!.isEmpty ? Center(child: Text(LocaleKeys.no_branch.tr())) : ListView.separated( itemBuilder: (context, index) { return ProviderDetailsCard( onCardTapped: () { - navigateWithName(context, AppRoutes.branchDetailPage, arguments: model.providerModel!.data!.serviceProviderBranch![index]); + navigateWithName(context, AppRoutes.branchDetailPage, arguments: model.providerProfileModel!.serviceProviderBranch![index]); }, providerImageUrl: MyAssets.bnCar, - title: model.providerModel!.data!.serviceProviderBranch![index].branchName ?? "", - providerLocation: model.providerModel!.data!.serviceProviderBranch![index].distanceKm.toString() + " KM", - providerName: model.providerModel!.data!.serviceProviderBranch![index].serviceProviderName ?? "", + title: model.providerProfileModel!.serviceProviderBranch![index].branchName ?? "", + providerLocation: model.providerProfileModel!.serviceProviderBranch![index].distanceKm.toString() + " KM", + providerName: model.providerProfileModel!.serviceProviderBranch![index].serviceProviderName ?? "", providerRatings: "4.9", - items: model.providerModel!.data!.serviceProviderBranch![index].branchServices, + services: model.providerProfileModel!.serviceProviderBranch![index].branchServices, ); }, separatorBuilder: (context, index) { return 12.height; }, - itemCount: model.providerModel!.data!.serviceProviderBranch!.length, + itemCount: model.providerProfileModel!.serviceProviderBranch!.length, physics: NeverScrollableScrollPhysics(), shrinkWrap: true, ) diff --git a/lib/views/provider/sheet/items_list_sheet.dart b/lib/views/provider/sheet/items_list_sheet.dart index 63a8fbf..4738e2d 100644 --- a/lib/views/provider/sheet/items_list_sheet.dart +++ b/lib/views/provider/sheet/items_list_sheet.dart @@ -1,19 +1,15 @@ -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:car_customer_app/view_models/appointments_view_model.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/services/item_model.dart'; import 'package:mc_common_app/theme/colors.dart'; -import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/widgets/empty_widget.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; import 'package:provider/provider.dart'; -import '../../../view_models/providers_view_model.dart'; - class ItemsListSheet extends StatefulWidget { - int serviceId; + final int serviceId; ItemsListSheet(this.serviceId); @@ -25,74 +21,70 @@ class _ItemsListSheetState extends State { @override void initState() { super.initState(); - context.read().getServiceItems(widget.serviceId ?? 0); + context.read().getServiceItems(widget.serviceId); } @override Widget build(BuildContext context) { return SizedBox( height: MediaQuery.of(context).size.height / 1.2, - child: Consumer( - builder: (context, model, _) { - return model.serviceItems == null - ? const Center(child: CircularProgressIndicator()) - : model.serviceItems!.data!.isEmpty - ? const EmptyWidget() - : ListView.separated( - itemBuilder: (BuildContext context, int index) { - return SizedBox( - width: double.infinity, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, + child: Consumer( + builder: (context, appointmentsVM, _) { + return appointmentsVM.serviceItems.isEmpty + ? const EmptyWidget() + : ListView.separated( + itemCount: appointmentsVM.serviceItems.length, + itemBuilder: (BuildContext context, int index) { + ItemData serviceItemModel = appointmentsVM.serviceItems[index]; + return SizedBox( + width: double.infinity, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + serviceItemModel.toString().toText(fontSize: 16, isBold: true), + 4.height, + showItem("Available for appointment:", (serviceItemModel.isAllowAppointment ?? false) ? "Yes" : "No", valueColor: Colors.green), + showItem("Allowing Workshop service:", (serviceItemModel.isAppointmentCompanyLoc ?? false) ? "Yes" : "No", valueColor: Colors.green), + showItem("Allowing home service:", (serviceItemModel.isAppointmentCustomerLoc ?? false) ? "Yes" : "No", valueColor: Colors.green), + 12.height, + "Service Amount".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), + Row( + crossAxisAlignment: CrossAxisAlignment.end, children: [ - model.serviceItems!.data![index].name.toString().toText(fontSize: 16, isBold: true), - 4.height, - showItem("Available for appointment:", (model.serviceItems!.data![index].isAllowAppointment ?? false) ? "Yes" : "No", valueColor: Colors.green), - showItem("Allowing Workshop service:", (model.serviceItems!.data![index].isAppointmentCompanyLoc ?? false) ? "Yes" : "No", valueColor: Colors.green), - showItem("Allowing home service:", (model.serviceItems!.data![index].isAppointmentCustomerLoc ?? false) ? "Yes" : "No", valueColor: Colors.green), - 12.height, - "Service Amount".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - model.serviceItems!.data![index].price!.toText(fontSize: 22, isBold: true), - 2.width, - Padding( - padding: const EdgeInsets.only(bottom: 4), - child: "SAR".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), - ), - ], + serviceItemModel.price!.toText(fontSize: 22, isBold: true), + 2.width, + Padding( + padding: const EdgeInsets.only(bottom: 4), + child: "SAR".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), ), ], ), - ), - // Padding( - // padding: const EdgeInsets.all(4.0), - // child: SvgPicture.asset( - // MyAssets.icEdit, - // width: 16, - // height: 16, - // ), - // ) - ], + ], + ), ), - ).toWhiteContainer( - width: double.infinity, - allPading: 12, - ); - }, - separatorBuilder: (BuildContext context, int index) { - return 12.height; - }, - padding: const EdgeInsets.all(20), - itemCount: model.serviceItems!.data!.length, - ); + // Padding( + // padding: const EdgeInsets.all(4.0), + // child: SvgPicture.asset( + // MyAssets.icEdit, + // width: 16, + // height: 16, + // ), + // ) + ], + ), + ).toWhiteContainer(width: double.infinity, allPading: 12); + }, + separatorBuilder: (BuildContext context, int index) { + return 12.height; + }, + padding: const EdgeInsets.all(20), + ); }, ), ); diff --git a/pubspec.yaml b/pubspec.yaml index 46981f4..3c4aa16 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: cupertino_icons: ^1.0.2 mc_common_app: - path: C:/Users/mirza.shafique/AndroidStudioProjects/mc_common_app + path: /Volumes/Data/Projects/Flutter/car_common_app dev_dependencies: flutter_test: @@ -74,7 +74,7 @@ flutter: # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a + # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: