From c0d88f314c4962f3b23fefae5080251bf659ba4e Mon Sep 17 00:00:00 2001 From: devshafique Date: Thu, 12 Oct 2023 09:24:53 +0300 Subject: [PATCH] Request Management 2.0 --- lib/config/dependencies.dart | 3 + .../profile/services.dart | 1 + lib/models/requests/request_model.dart | 138 +++++++++ lib/models/services/service_model.dart | 7 +- lib/repositories/request_repo.dart | 45 +++ lib/view_models/requests_view_model.dart | 272 ++++++++++++++++++ lib/views/requests/widget/request_item.dart | 120 ++++++++ .../requests/widget/request_status_chip.dart | 30 ++ lib/views/user/login_with_password_page.dart | 11 +- 9 files changed, 624 insertions(+), 3 deletions(-) create mode 100644 lib/models/requests/request_model.dart create mode 100644 lib/repositories/request_repo.dart create mode 100644 lib/view_models/requests_view_model.dart create mode 100644 lib/views/requests/widget/request_item.dart create mode 100644 lib/views/requests/widget/request_status_chip.dart diff --git a/lib/config/dependencies.dart b/lib/config/dependencies.dart index ec7fa03..32b19c1 100644 --- a/lib/config/dependencies.dart +++ b/lib/config/dependencies.dart @@ -11,6 +11,8 @@ import 'package:mc_common_app/repositories/user_repo.dart'; import 'package:mc_common_app/services/common_services.dart'; import 'package:mc_common_app/services/payments_service.dart'; +import '../repositories/request_repo.dart'; + Injector injector = Injector.appInstance; class AppDependencies { @@ -26,5 +28,6 @@ class AppDependencies { injector.registerSingleton(() => CommonRepoImp()); injector.registerSingleton(() => AdsRepoImp()); injector.registerSingleton(() => PaymentsRepoImp()); + injector.registerSingleton(() => RequestRepoImp()); } } diff --git a/lib/models/provider_branches_models/profile/services.dart b/lib/models/provider_branches_models/profile/services.dart index d457b4d..f45abcb 100644 --- a/lib/models/provider_branches_models/profile/services.dart +++ b/lib/models/provider_branches_models/profile/services.dart @@ -59,6 +59,7 @@ class ServicesData { dynamic? categoryName; bool? isSelected; + factory ServicesData.fromJson(Map json) => ServicesData( id: json["id"], diff --git a/lib/models/requests/request_model.dart b/lib/models/requests/request_model.dart new file mode 100644 index 0000000..1bfa418 --- /dev/null +++ b/lib/models/requests/request_model.dart @@ -0,0 +1,138 @@ +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/utils/enums.dart'; + +class RequestModel { + int requestType; + String requestTypeName; + String requestStatusName; + RequestStatus requestStatus; + String cityName; + String vehicleTypeName; + String countryName; + String customerName; + dynamic serviceProviders; + int offerCount; + int id; + int customerId; + dynamic customer; + String brand; + String model; + int year; + bool isNew; + String description; + List requestImages; + int cityId; + dynamic city; + double price; + int paymentStatus; + int vehicleTypeId; + int countryId; + List requestProviderItem; + bool isActive; + int createdBy; + DateTime createdOn; + dynamic modifiedBy; + dynamic modifiedOn; + + RequestModel({ + required this.requestType, + required this.requestTypeName, + required this.requestStatusName, + required this.requestStatus, + required this.cityName, + required this.vehicleTypeName, + required this.countryName, + required this.customerName, + required this.serviceProviders, + required this.offerCount, + required this.id, + required this.customerId, + required this.customer, + required this.brand, + required this.model, + required this.year, + required this.isNew, + required this.description, + required this.requestImages, + required this.cityId, + required this.city, + required this.price, + required this.paymentStatus, + required this.vehicleTypeId, + required this.countryId, + required this.requestProviderItem, + required this.isActive, + required this.createdBy, + required this.createdOn, + required this.modifiedBy, + required this.modifiedOn, + }); + + factory RequestModel.fromJson(Map json) => RequestModel( + requestType: json["requestType"], + requestTypeName: json["requestTypeName"], + requestStatusName: json["requestStatusName"], + requestStatus: (json['requestStatus'] as int).toRequestStatusEnum(), + cityName: json["cityName"], + vehicleTypeName: json["vehicleTypeName"], + countryName: json["countryName"], + customerName: json["customerName"], + serviceProviders: json["serviceProviders"], + offerCount: json["offerCount"], + id: json["id"], + customerId: json["customerID"], + customer: json["customer"], + brand: json["brand"], + model: json["model"], + year: json["year"], + isNew: json["isNew"], + description: json["description"], + requestImages: List.from(json["requestImages"].map((x) => x)), + cityId: json["cityID"], + city: json["city"], + price: json["price"], + paymentStatus: json["paymentStatus"], + vehicleTypeId: json["vehicleTypeID"], + countryId: json["countryID"], + requestProviderItem: List.from(json["requestProviderItem"].map((x) => x)), + isActive: json["isActive"], + createdBy: json["createdBy"], + createdOn: DateTime.parse(json["createdOn"]), + modifiedBy: json["modifiedBy"], + modifiedOn: json["modifiedOn"], + ); + + Map toJson() => { + "requestType": requestType, + "requestTypeName": requestTypeName, + "requestStatusName": requestStatusName, + "requestStatus": requestStatus, + "cityName": cityName, + "vehicleTypeName": vehicleTypeName, + "countryName": countryName, + "customerName": customerName, + "serviceProviders": serviceProviders, + "offerCount": offerCount, + "id": id, + "customerID": customerId, + "customer": customer, + "brand": brand, + "model": model, + "year": year, + "isNew": isNew, + "description": description, + "requestImages": List.from(requestImages.map((x) => x)), + "cityID": cityId, + "city": city, + "price": price, + "paymentStatus": paymentStatus, + "vehicleTypeID": vehicleTypeId, + "countryID": countryId, + "requestProviderItem": List.from(requestProviderItem.map((x) => x)), + "isActive": isActive, + "createdBy": createdBy, + "createdOn": createdOn.toIso8601String(), + "modifiedBy": modifiedBy, + "modifiedOn": modifiedOn, + }; +} diff --git a/lib/models/services/service_model.dart b/lib/models/services/service_model.dart index 5306dd5..fc6b8c2 100644 --- a/lib/models/services/service_model.dart +++ b/lib/models/services/service_model.dart @@ -1,4 +1,3 @@ - import 'package:mc_common_app/models/services/item_model.dart'; class ServiceModel { @@ -18,6 +17,8 @@ class ServiceModel { final int? itemsCount; List? serviceItems; bool isExpandedOrSelected; + int providerServiceId; + String providerServiceName; ServiceModel({ this.serviceProviderServiceId, @@ -36,6 +37,8 @@ class ServiceModel { this.itemsCount, this.serviceItems, required this.isExpandedOrSelected, + required this.providerServiceId, + required this.providerServiceName, }); factory ServiceModel.fromJson(Map json) => @@ -56,6 +59,8 @@ class ServiceModel { itemsCount: json["itemsCount"], serviceItems: json["branchServiceItems"] == null ? [] : List.from(json["branchServiceItems"]!.map((x) => ItemData.fromJson(x))), isExpandedOrSelected: false, + providerServiceId: 0, + providerServiceName: "", ); Map toJson() => diff --git a/lib/repositories/request_repo.dart b/lib/repositories/request_repo.dart new file mode 100644 index 0000000..6365983 --- /dev/null +++ b/lib/repositories/request_repo.dart @@ -0,0 +1,45 @@ +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/requests/request_model.dart'; + +abstract class RequestRepo { + Future createRequest(Map map); + + Future> getRequests(Map postParams); +} + +class RequestRepoImp implements RequestRepo { + ApiClient apiClient = injector.get(); + AppState appState = injector.get(); + + @override + Future createRequest(Map postParams) async { + GenericRespModel enumGenericModel = await apiClient.postJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.createRequest, + postParams, + token: appState.getUser.data!.accessToken, + ); + return enumGenericModel; + } + + @override + Future> getRequests(Map postParams) async { + GenericRespModel enumGenericModel = await apiClient.postJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.getRequest, + postParams, + token: appState.getUser.data!.accessToken, + ); + List requests = List.generate( + enumGenericModel.data.length, + (index) => RequestModel.fromJson( + enumGenericModel.data[index], + ), + ); + return requests; + } +} diff --git a/lib/view_models/requests_view_model.dart b/lib/view_models/requests_view_model.dart new file mode 100644 index 0000000..5094264 --- /dev/null +++ b/lib/view_models/requests_view_model.dart @@ -0,0 +1,272 @@ +import 'dart:io'; + +import 'package:mc_common_app/classes/app_state.dart'; +import 'package:mc_common_app/models/advertisment_models/vehicle_details_models.dart'; +import 'package:mc_common_app/models/enums.dart'; +import 'package:mc_common_app/models/generic_resp_model.dart'; +import 'package:mc_common_app/models/requests/request_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/utils.dart'; +import 'package:mc_common_app/view_models/base_view_model.dart'; + +import '../repositories/request_repo.dart'; + +class RequestsVM extends BaseVM { + final CommonAppServices commonServices; + final CommonRepo commonRepo; + final RequestRepo requestRepo; + + RequestsVM({required this.commonServices, required this.commonRepo, required this.requestRepo}); + + List requestsFilterOptions = []; + + populateRequestsFilterList() { + requestsFilterOptions.clear(); + requestsFilterOptions = [ + FilterListModel(title: "Cars", isSelected: true, id: 1), + FilterListModel(title: "Spare Parts", isSelected: false, id: 2), + ]; + notifyListeners(); + } + + applyFilterOnRequestsVM({required int index}) { + if (requestsFilterOptions.isEmpty) return; + for (var value in requestsFilterOptions) { + value.isSelected = false; + } + requestsFilterOptions[index].isSelected = true; + notifyListeners(); + } + + List pickedVehicleImages = []; + String vehicleImageError = ""; + + void removeImageFromList(String filePath) { + int index = pickedVehicleImages.indexWhere((element) => element.path == filePath); + if (index == -1) { + return; + } + pickedVehicleImages.removeAt(index); + notifyListeners(); + } + + void pickMultipleImages() async { + List Images = await commonServices.pickMultipleImages(); + pickedVehicleImages.addAll(Images); + if (pickedVehicleImages.isNotEmpty) vehicleImageError = ""; + notifyListeners(); + } + + bool isFetchingRequestType = false; + bool isFetchingVehicleType = true; + bool isFetchingVehicleDetail = false; + List requestTypes = []; + List vehicleTypes = []; + VehicleDetailsModel? vehicleDetails; + List vehicleBrands = []; + List vehicleModels = []; + List vehicleModelYears = []; + List vehicleCountries = []; + List vehicleCities = []; + + SelectionModel requestTypeId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + + getRequestTypes() async { + requestTypeId.selectedId = -1; + isFetchingRequestType = true; + requestTypes = await commonRepo.getEnumTypeValues(enumTypeID: 16); //TODO: 16 is to get Request types + isFetchingRequestType = false; + notifyListeners(); + } + + void updateSelectionRequestTypeId(SelectionModel id) async { + requestTypeId = id; + getVehicleTypes(); + notifyListeners(); + } + + SelectionModel vehicleTypeId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + + Future getVehicleTypes() async { + reset(); + isFetchingVehicleType = true; + vehicleTypes = await commonRepo.getVehicleTypes(); + isFetchingVehicleType = false; + notifyListeners(); + } + + reset() { + vehicleTypeId.selectedId = -1; + vehicleBrandId.selectedId = -1; + vehicleModelId.selectedId = -1; + vehicleModelYearId.selectedId = -1; + vehicleCountryId.selectedId = -1; + vehicleCityId.selectedId = -1; + } + + void updateSelectionVehicleTypeId(SelectionModel id) async { + vehicleTypeId = id; + getVehicleBrandsByVehicleTypeId(); + notifyListeners(); + } + + Future getVehicleBrandsByVehicleTypeId() async { + // if (vehicleBrandId.selectedId == -1) { + // return; + // } + isFetchingVehicleDetail = true; + notifyListeners(); + vehicleDetails = await commonRepo.getVehicleDetails(vehicleTypeId: vehicleTypeId.selectedId); + + if (vehicleDetails != null) { + vehicleBrands = vehicleDetails!.vehicleBrands!; + vehicleModels = vehicleDetails!.vehicleModels!; + vehicleModelYears = vehicleDetails!.vehicleModelYears!; + vehicleCountries = vehicleDetails!.vehicleCountries!; + } + isFetchingVehicleDetail = false; + notifyListeners(); + } + + SelectionModel vehicleBrandId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + + void updateSelectionVehicleBrandId(SelectionModel id) { + vehicleBrandId = id; + vehicleModelId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + vehicleModelYearId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + notifyListeners(); + } + + SelectionModel vehicleModelId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + + void updateSelectionVehicleModelId(SelectionModel id) { + vehicleModelId = id; + vehicleModelYearId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + notifyListeners(); + } + + SelectionModel vehicleModelYearId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + + void updateSelectionVehicleModelYearId(SelectionModel id) { + vehicleModelYearId = id; + notifyListeners(); + } + + bool isShippingDeliveryEnabled = false; + + void updateShippingDeliverEnabled(bool v) { + isShippingDeliveryEnabled = v; + notifyListeners(); + } + + bool isCountryFetching = false; + SelectionModel vehicleCountryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + + void updateSelectionVehicleCountryId(SelectionModel id) async { + vehicleCountryId = id; + isCountryFetching = true; + notifyListeners(); + vehicleCities = await commonRepo.getVehicleCities(countryId: vehicleCountryId.selectedId); + isCountryFetching = false; + notifyListeners(); + } + + SelectionModel vehicleCityId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + + void updateSelectionVehicleCityId(SelectionModel id) { + vehicleCityId = id; + notifyListeners(); + } + + //Request Management + String price = "", description = ""; + + updatePrice(String v) { + price = v; + } + + updateDescription(String v) { + description = v; + } + + Future createRequest() async { + if (validate()) { + Map m = { + "customerID": AppState().getUser.data!.userInfo!.customerId ?? 0, + "requestType": requestTypeId.selectedId, + "vehicleTypeID": vehicleTypeId.selectedId, + "brand": vehicleBrandId.selectedOption, + "model": vehicleModelId.selectedOption, + "year": vehicleModelYearId.selectedOption, + "isNew": true, + "countryID": vehicleCountryId.selectedId, + "cityID": vehicleCityId.selectedId, + "price": price, + "description": description, + "isSpecialServiceNeeded": false, + "requestImages": [] + }; + GenericRespModel respModel = await requestRepo.createRequest(m); + return respModel; + } else { + return null; + } + } + + bool validate() { + bool isValid = true; + if (requestTypeId.selectedId == -1) { + Utils.showToast("Please select valid Request Type"); + isValid = false; + } else if (vehicleTypeId.selectedId == -1) { + Utils.showToast("Please select valid Vehicle Type"); + isValid = false; + } else if (vehicleBrandId.selectedId == -1) { + Utils.showToast("Please select valid Brand"); + isValid = false; + } else if (vehicleModelId.selectedId == -1) { + Utils.showToast("Please select valid Model"); + isValid = false; + } else if (vehicleModelYearId.selectedId == -1) { + Utils.showToast("Please select valid Year"); + isValid = false; + } else if (vehicleCountryId.selectedId == -1) { + Utils.showToast("Please select valid Country"); + isValid = false; + } else if (vehicleCityId.selectedId == -1) { + Utils.showToast("Please select valid City"); + isValid = false; + } else if (price.isEmpty) { + Utils.showToast("Please add valid Price"); + isValid = false; + } else if (description.isEmpty) { + Utils.showToast("Please add valid Description"); + isValid = false; + } + return isValid; + } + + bool isRequestLoading = true; + List requests = []; + + getRequests() async { + isRequestLoading = true; + notifyListeners(); + + int selectedRequestType; + // Find the FilterListModel with isSelected equal to true + + requests = await requestRepo.getRequests( + { + "customerID": AppState().getUser.data!.userInfo!.customerId, + "pageSize": 100, + "pageIndex": 0, + "requestType": requestsFilterOptions.firstWhere((element) => element.isSelected).id, + }, + ); + isRequestLoading = false; + notifyListeners(); + } +} diff --git a/lib/views/requests/widget/request_item.dart b/lib/views/requests/widget/request_item.dart new file mode 100644 index 0000000..df5731e --- /dev/null +++ b/lib/views/requests/widget/request_item.dart @@ -0,0 +1,120 @@ +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/requests/request_model.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/views/requests/widget/request_status_chip.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; + + +class RequestItem extends StatelessWidget { + RequestModel request; + + RequestItem(this.request); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RequestStatusChip(request), + 6.height, + "${request.brand} ${request.model}".toText(fontSize: 16, isBold: true), + showItem("Model:", "${request.year}"), + showItem("Customer Name:", "${request.customerName}"), + ], + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Center( + child: "${request.offerCount}".toText( + color: Colors.white, + isBold: true, + fontSize: 10, + ), + ).toContainer( + backgroundColor: MyColors.redColor, + borderRadius: 100, + paddingAll: 1, + width: 20, + height: 20, + ), + 2.height, + request.cityName.toText( + color: MyColors.lightTextColor, + ), + "9 Hours Ago".toText( + color: MyColors.lightTextColor, + ) + ], + ) + ], + ), + showItem("Description: ", request.description), + showItem("Price Range: ", ""), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + request.price.toString().toText( + fontSize: 20, + color: Colors.black, + isBold: true, + ), + 2.width, + "SAR" + .toText( + color: MyColors.lightTextColor, + fontSize: 10, + ) + .paddingOnly( + bottom: 3, + ), + ], + ), + Icon( + Icons.arrow_forward, + color: MyColors.darkIconColor, + size: 18, + ), + ], + ), + ], + ) + .toContainer( + isShadowEnabled: true, + ) + .onPress(() { + navigateWithName(context, AppRoutes.offersListPage); + }); + } + + Widget showItem(String title, String value) { + return Row( + children: [ + title.toText( + color: MyColors.lightTextColor, + ), + 2.width, + value.toText( + isBold: true, + ), + ], + ); + } +} diff --git a/lib/views/requests/widget/request_status_chip.dart b/lib/views/requests/widget/request_status_chip.dart new file mode 100644 index 0000000..a028dfc --- /dev/null +++ b/lib/views/requests/widget/request_status_chip.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/models/requests/request_model.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; + +class RequestStatusChip extends StatelessWidget { + RequestModel request; + + RequestStatusChip(this.request, {super.key}); + + @override + Widget build(BuildContext context) { + return request.requestStatusName + .toText( + fontSize: 8, + color: Colors.white, + ) + .toContainer( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + backgroundColor: request.requestStatus == RequestStatus.pending + ? MyColors.adPendingStatusColor + : request.requestStatus == RequestStatus.cancelled + ? MyColors.redColor + : MyColors.greenColor, + borderRadius: 100, + ); + } +} diff --git a/lib/views/user/login_with_password_page.dart b/lib/views/user/login_with_password_page.dart index 0d74b32..79f10a7 100644 --- a/lib/views/user/login_with_password_page.dart +++ b/lib/views/user/login_with_password_page.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:mc_common_app/classes/app_state.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'; @@ -17,6 +18,8 @@ import 'package:mc_common_app/widgets/tab/login_email_tab.dart'; import 'package:mc_common_app/widgets/txt_field.dart'; import 'package:provider/provider.dart'; +import '../../utils/enums.dart'; + class LoginWithPassword extends StatefulWidget { const LoginWithPassword({Key? key}) : super(key: key); @@ -28,6 +31,7 @@ class _LoginWithPasswordState extends State { int otpType = 1; ClassType type = ClassType.EMAIL; + //TODO: ONLY FOR DEVELOPMENT PURPOSE String phoneNum = "966504278213", password = "Fa@1234"; String email = ""; @@ -39,7 +43,10 @@ class _LoginWithPasswordState extends State { @override void initState() { super.initState(); - + if (AppState().currentAppType == AppType.provider) { + phoneNum = "966580816976"; + password = "123@Shf"; + } scheduleMicrotask(() { userVM = Provider.of(context, listen: false); getCountryList(); @@ -54,7 +61,7 @@ class _LoginWithPasswordState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: CustomAppBar(isRemoveBackButton: true, title: ""), + appBar: const CustomAppBar(isRemoveBackButton: true, title: ""), body: Container( width: double.infinity, height: double.infinity,