From dc145661c82a6192540611651e9a511fef784723 Mon Sep 17 00:00:00 2001 From: FaizHashmiCS22 Date: Mon, 1 Jan 2024 09:09:39 +0300 Subject: [PATCH 1/6] chat debugging changes --- .../chat_models/chat_message_model.dart | 3 +++ lib/view_models/ad_view_model.dart | 7 +++--- lib/view_models/appointments_view_model.dart | 2 +- lib/view_models/chat_view_model.dart | 3 ++- lib/view_models/requests_view_model.dart | 3 ++- lib/views/advertisement/ads_detail_view.dart | 23 +++++++++---------- lib/views/advertisement/ads_list.dart | 2 +- .../customer_appointment_slider_widget.dart | 2 +- 8 files changed, 25 insertions(+), 20 deletions(-) diff --git a/lib/models/chat_models/chat_message_model.dart b/lib/models/chat_models/chat_message_model.dart index a9d3f6c..91834f4 100644 --- a/lib/models/chat_models/chat_message_model.dart +++ b/lib/models/chat_models/chat_message_model.dart @@ -7,6 +7,7 @@ import 'package:mc_common_app/utils/enums.dart'; class ChatMessageModel { int? id; String? senderUserID; + String? receiverUserID; String? senderName; int? messageType; ChatMessageTypeEnum? chatMessageTypeEnum; @@ -23,6 +24,7 @@ class ChatMessageModel { ChatMessageModel({ this.id, this.senderUserID, + this.receiverUserID, this.senderName, this.messageType, this.chatMessageTypeEnum, @@ -46,6 +48,7 @@ class ChatMessageModel { final myUserId = AppState().getUser.data!.userInfo!.userId.toString().toUpperCase(); id = json['id']; senderUserID = json['senderUserID']; + receiverUserID = json['receiverUserID'] ?? ""; senderName = json['senderName']; messageType = json['messageType']; chatMessageTypeEnum = (json['messageType'] as int).toChatMessageTypeEnum(); diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index 3120c45..6fbe9fd 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -15,9 +15,9 @@ import 'package:mc_common_app/models/advertisment_models/special_service_model.d import 'package:mc_common_app/models/advertisment_models/ss_car_check_schedule_model.dart'; import 'package:mc_common_app/models/advertisment_models/ss_photo_schedule_model.dart'; import 'package:mc_common_app/models/advertisment_models/vehicle_details_models.dart'; +import 'package:mc_common_app/models/appointments_models/service_schedule_model.dart'; import 'package:mc_common_app/models/general_models/enums_model.dart'; import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; -import 'package:mc_common_app/models/appointments_models/service_schedule_model.dart'; import 'package:mc_common_app/models/general_models/widgets_models.dart'; import 'package:mc_common_app/repositories/ads_repo.dart'; import 'package:mc_common_app/repositories/common_repo.dart'; @@ -863,6 +863,7 @@ class AdVM extends BaseVM { isValidated = false; } else { element.partSelectedId!.errorValue = ""; + element.partSelectedId!.errorValue = ""; } } @@ -977,11 +978,9 @@ class AdVM extends BaseVM { int status = await createNewAd(); if (status != 1) { Utils.hideLoading(context); - Utils.showToast("Something went wrong!"); return; } Utils.hideLoading(context); - Utils.showToast("A new ads has been created."); currentProgressStep = AdCreationSteps.vehicleDetails; resetValues(); updateIsExploreAds(false); @@ -1313,6 +1312,8 @@ class AdVM extends BaseVM { AdsCreationPayloadModel adsCreationPayloadModel = AdsCreationPayloadModel(ads: ads, vehiclePosting: vehiclePosting); GenericRespModel respModel = await adsRepo.createNewAd(adsCreationPayloadModel: adsCreationPayloadModel); + Utils.showToast(respModel.message.toString()); + return Future.value(respModel.messageStatus); } diff --git a/lib/view_models/appointments_view_model.dart b/lib/view_models/appointments_view_model.dart index b0739bb..4f0f233 100644 --- a/lib/view_models/appointments_view_model.dart +++ b/lib/view_models/appointments_view_model.dart @@ -193,7 +193,7 @@ class AppointmentsVM extends BaseVM { Utils.showToast("${genericRespModel.message.toString()}"); return; } - if (genericRespModel.data == 1) { + if (genericRespModel.messageStatus == 1) { context.read().onNavbarTapped(1); applyFilterOnAppointmentsVM( appointmentStatusEnum: AppointmentStatusEnum.cancelled); diff --git a/lib/view_models/chat_view_model.dart b/lib/view_models/chat_view_model.dart index d3be4c2..dfeef20 100644 --- a/lib/view_models/chat_view_model.dart +++ b/lib/view_models/chat_view_model.dart @@ -105,7 +105,8 @@ class ChatVM extends ChangeNotifier { } } else { for (var msg in messages) { - int providerIndex = context.read().myFilteredRequests.indexWhere((element) => element.customerUserID == msg.senderUserID); + int providerIndex = context.read().myFilteredRequests.indexWhere((element) => element.customerUserID == msg.receiverUserID); + log("here is it: $providerIndex"); log("here is it: ${msg.senderUserID}"); log("here is it: ${context.read().myFilteredRequests.first.customerUserID.toString()}"); if (providerIndex != -1) { diff --git a/lib/view_models/requests_view_model.dart b/lib/view_models/requests_view_model.dart index 616a17b..4a9dcca 100644 --- a/lib/view_models/requests_view_model.dart +++ b/lib/view_models/requests_view_model.dart @@ -404,7 +404,7 @@ class RequestsVM extends BaseVM { notifyListeners(); } - Future onSendOfferPressed({ + Future onSendOfferPressed({ required BuildContext context, required String receiverId, required String message, @@ -437,6 +437,7 @@ class RequestsVM extends BaseVM { messageType: ChatMessageTypeEnum.freeText.getIdFromChatMessageTypeEnum(), senderName: senderName, senderUserID: senderId, + receiverUserID: receiverId ); context.read().onNewMessageReceived(messages: [chatMessageModel], context: context); if (!isFromChatScreen) { diff --git a/lib/views/advertisement/ads_detail_view.dart b/lib/views/advertisement/ads_detail_view.dart index 8b609a0..47058c8 100644 --- a/lib/views/advertisement/ads_detail_view.dart +++ b/lib/views/advertisement/ads_detail_view.dart @@ -1,9 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; -import 'package:mc_common_app/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'; @@ -441,8 +439,10 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { width: 55, alignment: Alignment.center, decoration: BoxDecoration(border: Border.all(color: MyColors.black, width: 2)), - child: MyAssets.whatsAppIcon.buildSvg() - ).onPress(() { + child: MyAssets.whatsAppIcon.buildSvg( + height: 35, + width: 35, + )).onPress(() { Utils.openNumberViaWhatsApp(phoneNumber: adDetailsModel.whatsAppNo ?? ""); }), ], @@ -484,13 +484,12 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { if (adDetailsModel.whatsAppNo == null) ...[ 8.width, Container( - height: 55, - width: 55, - alignment: Alignment.center, - decoration: BoxDecoration(border: Border.all(color: MyColors.black, width: 2)), - //TODO: It Will be replaced by a WhatsApp Icon - child: MyAssets.whatsAppIcon.buildSvg() - ).onPress(() { + height: 55, + width: 55, + alignment: Alignment.center, + decoration: BoxDecoration(border: Border.all(color: MyColors.black, width: 2)), + child: MyAssets.whatsAppIcon.buildSvg(height: 33, width: 35)) + .onPress(() { Utils.openNumberViaWhatsApp(phoneNumber: adDetailsModel.whatsAppNo ?? ""); }), ], @@ -531,7 +530,7 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { builder: (BuildContext context) { return Consumer(builder: (BuildContext context, AdVM adVM, Widget? child) { return InfoBottomSheet( - title: "Set Date and Time".toText(fontSize: 28, isBold: true, letterSpacing: -1.44, height: 1.2), + title: "Set Date and Time".toText(fontSize: 16, isBold: true, letterSpacing: -1.44, height: 1.2), description: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/views/advertisement/ads_list.dart b/lib/views/advertisement/ads_list.dart index 9e8e0a6..fcc35ec 100644 --- a/lib/views/advertisement/ads_list.dart +++ b/lib/views/advertisement/ads_list.dart @@ -69,7 +69,7 @@ class AdCard extends StatelessWidget { Row( children: [ Image.network( - adDetails.vehicle!.image!.first.imageUrl!, + adDetails.vehicle!.image == null || adDetails.vehicle!.image!.isEmpty ? "" : adDetails.vehicle!.image!.first.imageUrl!, errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { return const SizedBox( width: 80, diff --git a/lib/views/appointments/widgets/customer_appointment_slider_widget.dart b/lib/views/appointments/widgets/customer_appointment_slider_widget.dart index a237e4e..d6c0eaa 100644 --- a/lib/views/appointments/widgets/customer_appointment_slider_widget.dart +++ b/lib/views/appointments/widgets/customer_appointment_slider_widget.dart @@ -33,7 +33,7 @@ class CustomerAppointmentSliderWidget extends StatelessWidget { return CustomAddButton( needsBorder: true, bgColor: MyColors.white, - onTap: () => context.read().onNavbarTapped(1), + onTap: () => context.read().onNavbarTapped(0), text: "Add New Appointment", icon: Container( height: 24, From d029656866167338b00efe703b90bb256d08dc97 Mon Sep 17 00:00:00 2001 From: Faiz Hashmi Date: Mon, 1 Jan 2024 14:42:51 +0300 Subject: [PATCH 2/6] debugging changes --- lib/view_models/appointments_view_model.dart | 2 +- lib/view_models/chat_view_model.dart | 13 +++++--- lib/view_models/requests_view_model.dart | 34 ++++++++++++-------- lib/views/chat/chat_view.dart | 9 ++++-- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/lib/view_models/appointments_view_model.dart b/lib/view_models/appointments_view_model.dart index 4f0f233..8235c54 100644 --- a/lib/view_models/appointments_view_model.dart +++ b/lib/view_models/appointments_view_model.dart @@ -973,7 +973,7 @@ class AppointmentsVM extends BaseVM { Utils.showToast("${genericRespModel.message.toString()}"); return; } - if (genericRespModel.data == 1) { + if (genericRespModel.messageStatus == 1) { context.read().onNavbarTapped(1); applyFilterOnAppointmentsVM( appointmentStatusEnum: AppointmentStatusEnum.cancelled); diff --git a/lib/view_models/chat_view_model.dart b/lib/view_models/chat_view_model.dart index dfeef20..f3d58f3 100644 --- a/lib/view_models/chat_view_model.dart +++ b/lib/view_models/chat_view_model.dart @@ -94,10 +94,9 @@ class ChatVM extends ChangeNotifier { } Future onNewMessageReceived({required List messages, required BuildContext context}) async { + log("message I received in onNewMessageReceived ${messages.first.toString()}"); if (AppState().currentAppType == AppType.customer) { for (var msg in messages) { - log("printing senderUserID: ${msg.senderUserID}"); - log("printing providerUserId: ${serviceProviderOffersList.first.providerUserId}"); int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == msg.senderUserID); if (providerIndex != -1) { serviceProviderOffersList[providerIndex].chatMessages!.add(msg); @@ -105,9 +104,11 @@ class ChatVM extends ChangeNotifier { } } else { for (var msg in messages) { - int providerIndex = context.read().myFilteredRequests.indexWhere((element) => element.customerUserID == msg.receiverUserID); + int providerIndex = context.read().myFilteredRequests.indexWhere( + (element) => element.customerUserID == msg.receiverUserID, + ); // Where we need to call this function for saving a message in chat we will use receiverUserID and in those cases where received ID is null , it means it is a signal R call log("here is it: $providerIndex"); - log("here is it: ${msg.senderUserID}"); + log("here is it: ${msg.receiverUserID}"); log("here is it: ${context.read().myFilteredRequests.first.customerUserID.toString()}"); if (providerIndex != -1) { context.read().addChatMessagesInRequestsModel(msg: msg, index: providerIndex); @@ -222,6 +223,8 @@ class ChatVM extends ChangeNotifier { requestID: requestId, senderName: name, senderUserID: userId, + chatMessageTypeEnum: ChatMessageTypeEnum.freeText, + receiverUserID: receiverId, ); if (AppState().currentAppType == AppType.customer) { @@ -230,7 +233,7 @@ class ChatVM extends ChangeNotifier { serviceProviderOffersList[providerIndex].chatMessages!.add(chatMessageModel); } } else { - int providerIndex = context.read().myFilteredRequests.indexWhere((element) => element.customerID == receiverId); + int providerIndex = context.read().myFilteredRequests.indexWhere((element) => element.customerUserID == receiverId); if (providerIndex != -1) { context.read().addChatMessagesInRequestsModel(msg: chatMessageModel, index: providerIndex); } diff --git a/lib/view_models/requests_view_model.dart b/lib/view_models/requests_view_model.dart index 4a9dcca..1774784 100644 --- a/lib/view_models/requests_view_model.dart +++ b/lib/view_models/requests_view_model.dart @@ -241,8 +241,7 @@ class RequestsVM extends BaseVM { } //Request Management - String price = "", - description = ""; + String price = "", description = ""; updatePrice(String v) { price = v; @@ -255,9 +254,7 @@ class RequestsVM extends BaseVM { Future convertFileToRequestPostingImages({required File file}) async { List imageBytes = await file.readAsBytes(); String image = base64Encode(imageBytes); - String fileName = file.path - .split('/') - .last; + String fileName = file.path.split('/').last; VehiclePostingImages vehiclePostingImages = VehiclePostingImages( imageName: fileName, imageStr: image, @@ -404,7 +401,7 @@ class RequestsVM extends BaseVM { notifyListeners(); } - Future onSendOfferPressed({ + Future onSendOfferPressed({ required BuildContext context, required String receiverId, required String message, @@ -425,20 +422,29 @@ class RequestsVM extends BaseVM { context: context, ); - if (status) { final senderName = AppState().getUser.data!.userInfo!.firstName; final senderId = AppState().getUser.data!.userInfo!.userId; // resetSendOfferBottomSheet(); Navigator.pop(context); ChatMessageModel chatMessageModel = ChatMessageModel( - isMyMessage: true, - chatText: message, - messageType: ChatMessageTypeEnum.freeText.getIdFromChatMessageTypeEnum(), - senderName: senderName, - senderUserID: senderId, - receiverUserID: receiverId - ); + isMyMessage: true, + chatText: message, + messageType: ChatMessageTypeEnum.offer.getIdFromChatMessageTypeEnum(), + senderName: senderName, + senderUserID: senderId, + receiverUserID: receiverId, + chatMessageTypeEnum: ChatMessageTypeEnum.offer, + requestID: requestModel.id, + offerStatus: RequestOfferStatusEnum.offer.getIdFromRequestOfferStatusEnum(), + reqOffer: ReqOffer( + offerStatus: RequestOfferStatusEnum.offer.getIdFromRequestOfferStatusEnum(), + requestID: requestModel.id, + price: double.parse(offerPrice), + requestOfferStatusEnum: RequestOfferStatusEnum.offer, + comment: message, + offerStatusText: "", + )); context.read().onNewMessageReceived(messages: [chatMessageModel], context: context); if (!isFromChatScreen) { ChatViewArguments chatViewArguments = ChatViewArguments( diff --git a/lib/views/chat/chat_view.dart b/lib/views/chat/chat_view.dart index a27b0d3..ce0a20e 100644 --- a/lib/views/chat/chat_view.dart +++ b/lib/views/chat/chat_view.dart @@ -146,12 +146,12 @@ class ChatView extends StatelessWidget { size: 30, ).onPress( () async { - log("chatViewArguments.receiverId:${chatViewArguments.receiverId}"); + log("chatViewArguments.requestId:${chatViewArguments.requestId}"); final status = await chatVM.onTextMessageSend( context: context, receiverId: chatViewArguments.receiverId, message: chatVM.chatMessageText, - requestId: chatViewArguments.chatTypeEnum == ChatTypeEnum.requestOffer ? chatViewArguments.requestId ?? 0 : 0, + requestId: chatViewArguments.requestId ?? 0, offerPrice: "0.0", chatMessageType: ChatMessageTypeEnum.freeText, ); @@ -253,7 +253,7 @@ class _ChatMessageCustomWidgetState extends State { Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - "${chatMessageModel.reqOffer!.price}".toText(fontSize: 19, isBold: true, color: MyColors.white), + "${chatMessageModel.reqOffer!.price}".toText(fontSize: 19, isBold: true, color: AppState().currentAppType == AppType.provider ? MyColors.white : MyColors.darkTextColor), 5.width, "SAR".toText(color: MyColors.lightTextColor, height: 2.2, fontSize: 10, isBold: true), ], @@ -361,6 +361,9 @@ class _ChatMessageCustomWidgetState extends State { @override Widget build(BuildContext context) { + log("here message type : ${widget.chatMessageModel.chatMessageTypeEnum}"); + log("here message type : ${widget.chatMessageModel.chatText}"); + return Directionality( textDirection: (widget.chatMessageModel.isMyMessage ?? false) ? TextDirection.rtl : TextDirection.ltr, // textDirection: (widget.chatMessageModel.isMyMessage ?? false) ? TextDirection.rtl : TextDirection.ltr, From 3f7527f4b2493c4d89ee94014e8bf06086e95d91 Mon Sep 17 00:00:00 2001 From: FaizHashmiCS22 Date: Mon, 1 Jan 2024 17:49:19 +0300 Subject: [PATCH 3/6] Appointment Flow Testing --- lib/extensions/string_extensions.dart | 14 +- lib/repositories/appointment_repo.dart | 155 +++---- lib/services/payments_service.dart | 7 +- lib/utils/enums.dart | 1 + lib/view_models/appointments_view_model.dart | 387 ++++++------------ lib/view_models/payment_view_model.dart | 14 +- lib/views/advertisement/ads_detail_view.dart | 2 + .../appointments/appointment_detail_view.dart | 91 ++-- 8 files changed, 260 insertions(+), 411 deletions(-) diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index afff381..1a8125d 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -3,7 +3,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/utils/enums.dart'; -import 'package:mc_common_app/utils/enums.dart'; extension EmailValidator on String { Widget toText( @@ -25,10 +24,7 @@ extension EmailValidator on String { style: TextStyle( fontStyle: isItalic ? FontStyle.italic : null, height: height, - decoration: isUnderLine - ? TextDecoration.underline - : textDecoration ?? TextDecoration.none, - + decoration: isUnderLine ? TextDecoration.underline : textDecoration ?? TextDecoration.none, fontSize: fontSize ?? 10, fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.w600, color: color ?? MyColors.darkTextColor, @@ -37,9 +33,7 @@ extension EmailValidator on String { ); bool isValidEmail() { - return RegExp( - r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') - .hasMatch(this); + return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this); } bool isNum() { @@ -428,6 +422,10 @@ extension PaymentTypesToInt on PaymentTypes { case PaymentTypes.request: return 5; + case PaymentTypes.extendAds: + return 6; + case PaymentTypes.partialAppointment: + return 7; default: return 0; diff --git a/lib/repositories/appointment_repo.dart b/lib/repositories/appointment_repo.dart index 540d25a..126bc6e 100644 --- a/lib/repositories/appointment_repo.dart +++ b/lib/repositories/appointment_repo.dart @@ -4,18 +4,16 @@ 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/appointments_models/schedule_model.dart'; +import 'package:mc_common_app/models/appointments_models/service_schedule_model.dart'; import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/models/general_models/m_response.dart'; import 'package:mc_common_app/models/provider_branches_models/profile/services.dart'; -import 'package:mc_common_app/models/appointments_models/schedule_model.dart'; -import 'package:mc_common_app/models/appointments_models/service_schedule_model.dart'; -import 'package:mc_common_app/utils/enums.dart'; import '../models/appointments_models/appointment_list_model.dart'; abstract class AppointmentRepo { - Future> getMyAppointments( - Map map); + Future> getMyAppointments(Map map); Future updateAppointmentStatus(Map map); @@ -37,20 +35,14 @@ abstract class AppointmentRepo { Future updateServicesInSchedule(Map map); - Future> - mergeServiceIntoAvailableSchedules({ + Future> mergeServiceIntoAvailableSchedules({ required List serviceItemIdsForHome, required List serviceItemIdsForWorkshop, }); - Future createServiceAppointment( - {required List schedules, - required int serviceProviderID}); + Future createServiceAppointment({required List schedules, required int serviceProviderID}); - Future cancelOrRescheduleServiceAppointment( - {required int serviceAppointmentID, - required int serviceSlotID, - required int appointmentScheduleAction}); + Future cancelOrRescheduleServiceAppointment({required int serviceAppointmentID, required int serviceSlotID, required int appointmentScheduleAction}); } class AppointmentRepoImp implements AppointmentRepo { @@ -58,25 +50,19 @@ class AppointmentRepoImp implements AppointmentRepo { 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); + 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); + 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); + return await injector.get().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.createGroup, map, token: t); } @override @@ -84,36 +70,29 @@ class AppointmentRepoImp implements AppointmentRepo { Map map = {"ServiceProviderBranchID": branchId}; String t = AppState().getUser.data!.accessToken ?? ""; - GenericRespModel adsGenericModel = - await injector.get().getJsonForObject( + GenericRespModel adsGenericModel = await injector.get().getJsonForObject( (json) => GenericRespModel.fromJson(json), - ApiConsts.getSchedule, - token: t, - queryParameters: map, - ); + ApiConsts.getSchedule, + token: t, + queryParameters: map, + ); - return List.generate(adsGenericModel.data.length, - (index) => ScheduleData.fromJson(adsGenericModel.data[index])); + 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); + 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); + return await injector.get().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.updateGroup, map, token: t); } - Future> - mergeServiceIntoAvailableSchedules({ + Future> mergeServiceIntoAvailableSchedules({ required List serviceItemIdsForHome, required List serviceItemIdsForWorkshop, }) async { @@ -128,29 +107,21 @@ class AppointmentRepoImp implements AppointmentRepo { "ServiceItemIDs": serviceItemIdsForWorkshop, } ]; - GenericRespModel adsGenericModel = - await injector.get().postJsonForObject( + GenericRespModel adsGenericModel = await injector.get().postJsonForObject( (json) => GenericRespModel.fromJson(json), - ApiConsts.GetServiceItemAppointmentScheduleSlots, - queryParameters, - token: t, - ); + ApiConsts.GetServiceItemAppointmentScheduleSlots, + queryParameters, + token: t, + ); if (adsGenericModel.data == null) { return []; } List serviceAppointmentScheduleModel = - List.generate( - adsGenericModel.data.length, - (index) => - ServiceAppointmentScheduleModel.fromJson( - adsGenericModel.data[index], - isForAppointment: true)); + List.generate(adsGenericModel.data.length, (index) => ServiceAppointmentScheduleModel.fromJson(adsGenericModel.data[index], isForAppointment: true)); return serviceAppointmentScheduleModel; } - Future createServiceAppointment( - {required List schedules, - required int serviceProviderID}) async { + Future createServiceAppointment({required List schedules, required int serviceProviderID}) async { String t = AppState().getUser.data!.accessToken ?? ""; int customerId = AppState().getUser.data!.userInfo!.customerId ?? 0; @@ -169,23 +140,20 @@ class AppointmentRepoImp implements AppointmentRepo { "serviceItemID": serviceItemIds, }); }); + log("maplist: ${mapList.toString() }"); - GenericRespModel adsGenericModel = - await injector.get().postJsonForObject( + GenericRespModel adsGenericModel = await injector.get().postJsonForObject( (json) => GenericRespModel.fromJson(json), - ApiConsts.ServiceProvidersAppointmentCreate, - mapList, - token: t, - ); + ApiConsts.ServiceProvidersAppointmentCreate, + mapList, + token: t, + ); return adsGenericModel; } @override - Future cancelOrRescheduleServiceAppointment( - {required int serviceAppointmentID, - required int serviceSlotID, - required int appointmentScheduleAction}) async { + Future cancelOrRescheduleServiceAppointment({required int serviceAppointmentID, required int serviceSlotID, required int appointmentScheduleAction}) async { String t = AppState().getUser.data!.accessToken ?? ""; final payload = { @@ -194,33 +162,27 @@ class AppointmentRepoImp implements AppointmentRepo { "appointmentScheduleAction": appointmentScheduleAction, }; - GenericRespModel adsGenericModel = - await injector.get().postJsonForObject( + GenericRespModel adsGenericModel = await injector.get().postJsonForObject( (json) => GenericRespModel.fromJson(json), - ApiConsts.ServiceProviderAppointmentRescheduleCancelAppointment, - payload, - token: t, - ); + ApiConsts.ServiceProviderAppointmentRescheduleCancelAppointment, + payload, + token: t, + ); return adsGenericModel; } @override - Future> getMyAppointments( - Map map) async { + Future> getMyAppointments(Map map) async { String t = AppState().getUser.data!.accessToken ?? ""; - GenericRespModel genericRespModel = - await injector.get().getJsonForObject( - token: t, + GenericRespModel genericRespModel = await injector.get().getJsonForObject( + token: t, (json) => GenericRespModel.fromJson(json), - queryParameters: map, - ApiConsts.serviceProvidersAppointmentGet, - ); - List appointmentList = List.generate( - genericRespModel.data.length, - (index) => - AppointmentListModel.fromJson(genericRespModel.data[index])); + queryParameters: map, + ApiConsts.serviceProvidersAppointmentGet, + ); + List appointmentList = List.generate(genericRespModel.data.length, (index) => AppointmentListModel.fromJson(genericRespModel.data[index])); return appointmentList; } @@ -228,42 +190,31 @@ class AppointmentRepoImp implements AppointmentRepo { Future getAppointmentSlots(Map map) async { String t = AppState().getUser.data!.accessToken ?? ""; - MResponse adsGenericModel = - await injector.get().getJsonForObject( + MResponse adsGenericModel = await injector.get().getJsonForObject( (json) => MResponse.fromJson(json), - ApiConsts.getAppointmentSlots, - token: t, - queryParameters: map, - ); + ApiConsts.getAppointmentSlots, + token: t, + queryParameters: map, + ); return adsGenericModel; } @override - Future updateAppointmentPaymentStatus( - Map map) async { + Future updateAppointmentPaymentStatus(Map map) async { String t = AppState().getUser.data!.accessToken ?? ""; - return await injector.get().postJsonForObject( - (json) => MResponse.fromJson(json), - ApiConsts.updateAppointmentPaymentStatus, map, - token: t); + return await injector.get().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.updateAppointmentPaymentStatus, map, token: t); } @override Future updateAppointmentStatus(Map map) async { String t = AppState().getUser.data!.accessToken ?? ""; - return await injector.get().postJsonForObject( - (json) => MResponse.fromJson(json), - ApiConsts.updateAppointmentStatus, map, - token: t); + return await injector.get().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.updateAppointmentStatus, map, token: t); } @override Future createMergeAppointment(Map map) async { String t = AppState().getUser.data!.accessToken ?? ""; - return await injector.get().postJsonForObject( - (json) => MResponse.fromJson(json), - ApiConsts.createMergeAppointment, map, - token: t); + return await injector.get().postJsonForObject((json) => MResponse.fromJson(json), ApiConsts.createMergeAppointment, map, token: t); } } diff --git a/lib/services/payments_service.dart b/lib/services/payments_service.dart index 917a7cf..aed50b8 100644 --- a/lib/services/payments_service.dart +++ b/lib/services/payments_service.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:developer'; import 'package:flutter/material.dart'; @@ -8,7 +7,6 @@ import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/services/my_in_app_browser.dart'; import 'package:mc_common_app/utils/enums.dart'; -import 'package:mc_common_app/utils/utils.dart'; abstract class PaymentService { Future placePayment({ @@ -30,11 +28,11 @@ class PaymentServiceImp implements PaymentService { MyInAppBrowser? myInAppBrowser; var inAppBrowserOptions = InAppBrowserClassOptions( inAppWebViewGroupOptions: - InAppWebViewGroupOptions(crossPlatform: InAppWebViewOptions(useShouldOverrideUrlLoading: true, transparentBackground: false), ios: IOSInAppWebViewOptions(applePayAPIEnabled: true)), + InAppWebViewGroupOptions(crossPlatform: InAppWebViewOptions(useShouldOverrideUrlLoading: true, transparentBackground: false), ios: IOSInAppWebViewOptions(applePayAPIEnabled: true)), crossPlatform: InAppBrowserOptions(hideUrlBar: true, toolbarTopBackgroundColor: Colors.black), android: AndroidInAppBrowserOptions(), ios: - IOSInAppBrowserOptions(hideToolbarBottom: true, toolbarBottomBackgroundColor: Colors.white, closeButtonColor: Colors.white, presentationStyle: IOSUIModalPresentationStyle.OVER_FULL_SCREEN)); + IOSInAppBrowserOptions(hideToolbarBottom: true, toolbarBottomBackgroundColor: Colors.white, closeButtonColor: Colors.white, presentationStyle: IOSUIModalPresentationStyle.OVER_FULL_SCREEN)); @override Future placePayment({ @@ -51,6 +49,7 @@ class PaymentServiceImp implements PaymentService { urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&OrderProviderSubscriptionID=$id"; break; case PaymentTypes.appointment: + case PaymentTypes.partialAppointment: String appointIds = ''; for (int i = 0; i < appointmentIds!.length; i++) { var element = appointmentIds[i]; diff --git a/lib/utils/enums.dart b/lib/utils/enums.dart index 8fcfa8a..b70f540 100644 --- a/lib/utils/enums.dart +++ b/lib/utils/enums.dart @@ -82,6 +82,7 @@ enum PaymentTypes { ads, request, extendAds, + partialAppointment } enum AdCreationSteps { diff --git a/lib/view_models/appointments_view_model.dart b/lib/view_models/appointments_view_model.dart index 8235c54..118ccc6 100644 --- a/lib/view_models/appointments_view_model.dart +++ b/lib/view_models/appointments_view_model.dart @@ -4,15 +4,15 @@ import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/models/appointments_models/appointment_list_model.dart'; +import 'package:mc_common_app/models/appointments_models/service_schedule_model.dart'; import 'package:mc_common_app/models/general_models/enums_model.dart'; import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/models/general_models/m_response.dart'; +import 'package:mc_common_app/models/general_models/widgets_models.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/appointments_models/service_schedule_model.dart'; import 'package:mc_common_app/models/services_models/item_model.dart'; import 'package:mc_common_app/models/services_models/service_model.dart'; -import 'package:mc_common_app/models/general_models/widgets_models.dart'; import 'package:mc_common_app/repositories/appointment_repo.dart'; import 'package:mc_common_app/repositories/common_repo.dart'; import 'package:mc_common_app/repositories/provider_repo.dart'; @@ -39,10 +39,7 @@ class AppointmentsVM extends BaseVM { final ProviderRepo providerRepo; final AppointmentRepo scheduleRepo; - AppointmentsVM({required this.commonServices, - required this.scheduleRepo, - required this.providerRepo, - required this.commonRepo}); + AppointmentsVM({required this.commonServices, required this.scheduleRepo, required this.providerRepo, required this.commonRepo}); bool isUpcommingEnabled = true; bool isFetchingLists = false; @@ -69,8 +66,7 @@ class AppointmentsVM extends BaseVM { List serviceAppointmentScheduleList = []; bool ifItemAlreadySelected(int id) { - int indexFound = allSelectedItemsInAppointments - .indexWhere((element) => element.id == id); + int indexFound = allSelectedItemsInAppointments.indexWhere((element) => element.id == id); if (indexFound != -1) { return true; } @@ -81,25 +77,17 @@ class AppointmentsVM extends BaseVM { setupProviderAppointmentFilter() { appointmentsFilterOptions.clear(); - appointmentsFilterOptions.add( - FilterListModel(id: 0, title: "All Appointments", isSelected: true)); - appointmentsFilterOptions - .add(FilterListModel(id: 2, title: "Confirmed", isSelected: false)); - appointmentsFilterOptions - .add(FilterListModel(id: 3, title: "Arrived", isSelected: false)); - appointmentsFilterOptions - .add( - FilterListModel(id: 7, title: "Work In Progress", isSelected: false)); - appointmentsFilterOptions - .add(FilterListModel(id: 8, title: "Completed", isSelected: false)); - appointmentsFilterOptions - .add(FilterListModel(id: 4, title: "Canceled", isSelected: false)); + appointmentsFilterOptions.add(FilterListModel(id: 0, title: "All Appointments", isSelected: true)); + appointmentsFilterOptions.add(FilterListModel(id: 2, title: "Confirmed", isSelected: false)); + appointmentsFilterOptions.add(FilterListModel(id: 3, title: "Arrived", isSelected: false)); + appointmentsFilterOptions.add(FilterListModel(id: 7, title: "Work In Progress", isSelected: false)); + appointmentsFilterOptions.add(FilterListModel(id: 8, title: "Completed", isSelected: false)); + appointmentsFilterOptions.add(FilterListModel(id: 4, title: "Canceled", isSelected: false)); } Future onItemsSelectedInService() async { if (currentServiceSelection != null) { - int index = servicesInCurrentAppointment.indexWhere((element) => - element.serviceId == currentServiceSelection!.serviceId!); + int index = servicesInCurrentAppointment.indexWhere((element) => element.serviceId == currentServiceSelection!.serviceId!); if (index == -1) { double totalPrice = 0.0; @@ -115,47 +103,49 @@ class AppointmentsVM extends BaseVM { } } + Future onPayNowPressedForAppointment({required BuildContext context, required int appointmentID}) async { + context.read().updateAppointmentIdsForPayment(ids: [appointmentID]); + navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.partialAppointment); + } + Future onBookAppointmentPressed(BuildContext context) async { Utils.showLoading(context); bool isSuccess = false; List appointmentIdsList = []; try { - GenericRespModel genericRespModel = - await scheduleRepo.createServiceAppointment( + GenericRespModel genericRespModel = await scheduleRepo.createServiceAppointment( schedules: serviceAppointmentScheduleList, serviceProviderID: selectedBranchModel!.serviceProviderId ?? 0, ); - if (genericRespModel.messageStatus == 2 || - genericRespModel.data == null) { + if (genericRespModel.data.isEmpty) { Utils.hideLoading(context); Utils.showToast("${genericRespModel.message.toString()}"); return; } - if (genericRespModel.data != null) { + + if (genericRespModel.data != null && genericRespModel.data.isNotEmpty) { genericRespModel.data.forEach((element) { if (element['appointmentID'] != 0) { appointmentIdsList.add(element['appointmentID']); isSuccess = true; } else { isSuccess = false; + Utils.showToast(element['message']); + return; } }); } context.read().onNavbarTapped(1); - applyFilterOnAppointmentsVM( - appointmentStatusEnum: AppointmentStatusEnum.booked); + applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.booked); Utils.hideLoading(context); resetAfterBookingAppointment(); if (isSuccess) { if (amountToPayForAppointment > 0) { - context - .read() - .updateAppointmentIdsForPayment(ids: appointmentIdsList); - navigateWithName(context, AppRoutes.paymentMethodsView, - arguments: PaymentTypes.appointment); + context.read().updateAppointmentIdsForPayment(ids: appointmentIdsList); + navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.appointment); } else { Utils.showToast("Your appointment has been booked successfully!"); getMyAppointments(); @@ -167,36 +157,28 @@ class AppointmentsVM extends BaseVM { } } - Future onConfirmAppointmentPressed( - {required BuildContext context, required appointmentId}) async { - context - .read() - .updateAppointmentIdsForPayment(ids: [appointmentId]); - navigateWithName(context, AppRoutes.paymentMethodsView, - arguments: PaymentTypes.appointment); + Future onConfirmAppointmentPressed({required BuildContext context, required appointmentId}) async { + context.read().updateAppointmentIdsForPayment(ids: [appointmentId]); + navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.appointment); } - Future onCancelAppointmentPressed({required BuildContext context, - required AppointmentListModel appointmentListModel}) async { + Future onCancelAppointmentPressed({required BuildContext context, required AppointmentListModel appointmentListModel}) async { Utils.showLoading(context); try { - GenericRespModel genericRespModel = - await scheduleRepo.cancelOrRescheduleServiceAppointment( + GenericRespModel genericRespModel = await scheduleRepo.cancelOrRescheduleServiceAppointment( serviceAppointmentID: appointmentListModel.id ?? 0, serviceSlotID: appointmentListModel.serviceSlotID ?? 0, appointmentScheduleAction: 2, // 1 for Reschedule and 2 for Cancel ); - if (genericRespModel.messageStatus == 2 || - genericRespModel.data == null) { + if (genericRespModel.messageStatus == 2 || genericRespModel.data == null) { Utils.hideLoading(context); Utils.showToast("${genericRespModel.message.toString()}"); return; } if (genericRespModel.messageStatus == 1) { context.read().onNavbarTapped(1); - applyFilterOnAppointmentsVM( - appointmentStatusEnum: AppointmentStatusEnum.cancelled); + applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.cancelled); Utils.showToast("${genericRespModel.message.toString()}"); await getMyAppointments(); Utils.hideLoading(context); @@ -227,8 +209,7 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - SelectionModel branchSelectedCategoryId = - SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + SelectionModel branchSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); void updateProviderCategoryId(SelectionModel id) { branchSelectedCategoryId = id; @@ -247,37 +228,30 @@ class AppointmentsVM extends BaseVM { void updateBranchServiceId(SelectionModel id) async { branchSelectedServiceId = id; - currentServiceSelection = branchServices.firstWhere( - (element) => element.serviceProviderServiceId == id.selectedId); + currentServiceSelection = branchServices.firstWhere((element) => element.serviceProviderServiceId == id.selectedId); notifyListeners(); } void removeServiceInCurrentAppointment(int index) { - int serviceId = servicesInCurrentAppointment - .elementAt(index) - .serviceProviderServiceId ?? - -1; - allSelectedItemsInAppointments.removeWhere( - (element) => element.serviceProviderServiceId == serviceId); + int serviceId = servicesInCurrentAppointment.elementAt(index).serviceProviderServiceId ?? -1; + allSelectedItemsInAppointments.removeWhere((element) => element.serviceProviderServiceId == serviceId); servicesInCurrentAppointment.removeAt(index); notifyListeners(); } resetCategorySelectionBottomSheet() { selectedSubServicesCounter = 0; - branchSelectedCategoryId = - SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + branchSelectedCategoryId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); isHomeTapped = false; - branchSelectedServiceId = - SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + branchSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); currentServiceSelection = null; } resetAfterBookingAppointment() { - allSelectedItemsInAppointments.clear(); - // servicesInCurrentAppointment.clear(); - serviceAppointmentScheduleList.clear(); + // allSelectedItemsInAppointments.clear(); + servicesInCurrentAppointment.clear(); + // serviceAppointmentScheduleList.clear(); } List myAppointmentsEnum = []; @@ -285,32 +259,28 @@ class AppointmentsVM extends BaseVM { populateAppointmentsFilterList() async { appointmentsFilterOptions.clear(); - myAppointmentsEnum = await commonRepo.getEnumTypeValues( - enumTypeID: 13); //TODO: 13 is to get Appointments Filter Enums + myAppointmentsEnum = await commonRepo.getEnumTypeValues(enumTypeID: 13); //TODO: 13 is to get Appointments Filter Enums for (int i = 0; i < myAppointmentsEnum.length; i++) { - appointmentsFilterOptions.add(FilterListModel( - title: myAppointmentsEnum[i].enumValueStr, - isSelected: false, - id: myAppointmentsEnum[i].enumValue)); + appointmentsFilterOptions.add(FilterListModel(title: myAppointmentsEnum[i].enumValueStr, isSelected: false, id: myAppointmentsEnum[i].enumValue)); } - appointmentsFilterOptions.insert( - 0, FilterListModel(title: "All Appointments", isSelected: true, id: 0)); + appointmentsFilterOptions.insert(0, FilterListModel(title: "All Appointments", isSelected: true, id: 0)); + + // TODO: THIS SHOULD REMOVED AND ADDED IN THE ENUMS API + appointmentsFilterOptions.add(FilterListModel(title: "Work In Progress", isSelected: false, id: 7)); + appointmentsFilterOptions.add(FilterListModel(title: "Visit Completed", isSelected: false, id: 8)); notifyListeners(); } - applyFilterOnAppointmentsVM( - {required AppointmentStatusEnum appointmentStatusEnum, - bool isNeedCustomerFilter = false}) { + applyFilterOnAppointmentsVM({required AppointmentStatusEnum appointmentStatusEnum, bool isNeedCustomerFilter = false}) { if (appointmentsFilterOptions.isEmpty) return; for (var value in appointmentsFilterOptions) { value.isSelected = false; } appointmentsFilterOptions.forEach((element) { - if (element.id == - appointmentStatusEnum.getIdFromAppointmentStatusEnum()) { + if (element.id == appointmentStatusEnum.getIdFromAppointmentStatusEnum()) { element.isSelected = true; } }); @@ -325,16 +295,11 @@ class AppointmentsVM extends BaseVM { return; } - myFilteredAppointments = myAppointments - .where((element) => - element.appointmentStatusID! == - appointmentStatusEnum.getIdFromAppointmentStatusEnum()) - .toList(); + myFilteredAppointments = myAppointments.where((element) => element.appointmentStatusID! == appointmentStatusEnum.getIdFromAppointmentStatusEnum()).toList(); if (isNeedCustomerFilter) findAppointmentsBasedOnCustomers(); notifyListeners(); } - findAppointmentsBasedOnCustomers() { // Use a Set to ensure uniqueness of customerIDs Set uniqueCustomerIDs = Set(); @@ -346,9 +311,7 @@ class AppointmentsVM extends BaseVM { // Create a list of CustomerData instances myFilteredAppointments2 = uniqueCustomerIDs.map((id) { - List list = myFilteredAppointments - .where((item) => item.customerID == id) - .toList(); + List list = myFilteredAppointments.where((item) => item.customerID == id).toList(); AppointmentListModel model = list.first; model.customerAppointmentList = list; return model; @@ -373,10 +336,7 @@ class AppointmentsVM extends BaseVM { myAppointments = await commonRepo.getMyAppointments(); myFilteredAppointments = myAppointments; - myUpComingAppointments = myAppointments - .where((element) => - element.appointmentStatusEnum == AppointmentStatusEnum.confirmed) - .toList(); + myUpComingAppointments = myAppointments.where((element) => element.appointmentStatusEnum == AppointmentStatusEnum.confirmed).toList(); setState(ViewState.idle); // applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments); notifyListeners(); @@ -384,9 +344,7 @@ class AppointmentsVM extends BaseVM { AppointmentSlots? appointmentSlots; - Future getAppointmentSlotsInfo({required Map map, - required BuildContext context, - bool isNeedToRebuild = false}) async { + Future getAppointmentSlotsInfo({required Map map, required BuildContext context, bool isNeedToRebuild = false}) async { if (isNeedToRebuild) setState(ViewState.busy); try { MResponse genericRespModel = await scheduleRepo.getAppointmentSlots(map); @@ -400,20 +358,14 @@ class AppointmentsVM extends BaseVM { } } - Future getProviderMyAppointments(Map map, - {bool isNeedToRebuild = false}) async { + Future getProviderMyAppointments(Map map, {bool isNeedToRebuild = false}) async { if (isNeedToRebuild) setState(ViewState.busy); myAppointments = await scheduleRepo.getMyAppointments(map); myFilteredAppointments = myAppointments; - myUpComingAppointments = myAppointments - .where((element) => - element.appointmentStatusEnum == AppointmentStatusEnum.booked) - .toList(); - - applyFilterOnAppointmentsVM( - appointmentStatusEnum: AppointmentStatusEnum.allAppointments, - isNeedCustomerFilter: true); + myUpComingAppointments = myAppointments.where((element) => element.appointmentStatusEnum == AppointmentStatusEnum.booked).toList(); + + applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.allAppointments, isNeedCustomerFilter: true); setState(ViewState.idle); } @@ -423,12 +375,10 @@ class AppointmentsVM extends BaseVM { notifyListeners(); } - updateAppointmentStatus(Map map, - {bool isNeedToRebuild = false}) async { + updateAppointmentStatus(Map map, {bool isNeedToRebuild = false}) async { if (isNeedToRebuild) setState(ViewState.busy); try { - MResponse genericRespModel = - await scheduleRepo.updateAppointmentStatus(map); + MResponse genericRespModel = await scheduleRepo.updateAppointmentStatus(map); if (genericRespModel.messageStatus == 1) { Utils.showToast("appointment status updated"); @@ -440,12 +390,10 @@ class AppointmentsVM extends BaseVM { } } - updateAppointmentPaymentStatus(Map map, - {bool isNeedToRebuild = false}) async { + updateAppointmentPaymentStatus(Map map, {bool isNeedToRebuild = false}) async { if (isNeedToRebuild) setState(ViewState.busy); try { - MResponse genericRespModel = - await scheduleRepo.updateAppointmentPaymentStatus(map); + MResponse genericRespModel = await scheduleRepo.updateAppointmentPaymentStatus(map); if (genericRespModel.messageStatus == 1) { Utils.showToast("payment status updated"); @@ -457,11 +405,9 @@ class AppointmentsVM extends BaseVM { } } - Future createMergeAppointment(Map map, - {bool isNeedToRebuild = false}) async { + Future createMergeAppointment(Map map, {bool isNeedToRebuild = false}) async { if (isNeedToRebuild) setState(ViewState.busy); - MResponse genericRespModel = - await scheduleRepo.createMergeAppointment(map); + MResponse genericRespModel = await scheduleRepo.createMergeAppointment(map); return genericRespModel; } @@ -469,16 +415,10 @@ class AppointmentsVM extends BaseVM { bool inNeedToEnableMergeButton = false; updateCheckBoxInMergeRequest(int currentIndex) { - myFilteredAppointments2[selectedAppointmentIndex] - .customerAppointmentList![currentIndex] - .isSelected = !(myFilteredAppointments2[selectedAppointmentIndex] - .customerAppointmentList?[currentIndex] - .isSelected ?? - false); - - int count = countSelected(myFilteredAppointments2[selectedAppointmentIndex] - .customerAppointmentList ?? - []); + myFilteredAppointments2[selectedAppointmentIndex].customerAppointmentList![currentIndex].isSelected = + !(myFilteredAppointments2[selectedAppointmentIndex].customerAppointmentList?[currentIndex].isSelected ?? false); + + int count = countSelected(myFilteredAppointments2[selectedAppointmentIndex].customerAppointmentList ?? []); if (count > 1) inNeedToEnableMergeButton = true; else @@ -487,61 +427,35 @@ class AppointmentsVM extends BaseVM { } int countSelected(List appointments) { - return appointments - .where((appointment) => appointment.isSelected == true) - .toList() - .length; + return appointments.where((appointment) => appointment.isSelected == true).toList().length; } - updateSelectedAppointmentDate( - {required int dateIndex, required int scheduleIndex}) { - for (var element in serviceAppointmentScheduleList[scheduleIndex] - .customTimeDateSlotList!) { + updateSelectedAppointmentDate({required int dateIndex, required int scheduleIndex}) { + for (var element in serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList!) { element.date!.isSelected = false; } - serviceAppointmentScheduleList[scheduleIndex] - .customTimeDateSlotList![dateIndex] - .date! - .isSelected = true; + serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![dateIndex].date!.isSelected = true; serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex = dateIndex; final date = TimeSlotModel( - date: serviceAppointmentScheduleList[scheduleIndex] - .customTimeDateSlotList![dateIndex] - .date! - .date, - slotId: serviceAppointmentScheduleList[scheduleIndex] - .customTimeDateSlotList![dateIndex] - .date! - .slotId, + date: serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![dateIndex].date!.date, + slotId: serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![dateIndex].date!.slotId, isSelected: true, slot: "", ); - serviceAppointmentScheduleList[scheduleIndex] - .selectedCustomTimeDateSlotModel = CustomTimeDateSlotModel(date: date); + serviceAppointmentScheduleList[scheduleIndex].selectedCustomTimeDateSlotModel = CustomTimeDateSlotModel(date: date); notifyListeners(); } - updateSelectedAppointmentSlotByDate( - {required int scheduleIndex, required int slotIndex}) { - for (var element in serviceAppointmentScheduleList[scheduleIndex] - .customTimeDateSlotList!) { + updateSelectedAppointmentSlotByDate({required int scheduleIndex, required int slotIndex}) { + for (var element in serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList!) { for (var element in element.availableSlots!) { element.isSelected = false; } } - int index = - serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex!; - serviceAppointmentScheduleList[scheduleIndex] - .customTimeDateSlotList![index] - .availableSlots![slotIndex] - .isSelected = true; - serviceAppointmentScheduleList[scheduleIndex] - .selectedCustomTimeDateSlotModel! - .availableSlots = - serviceAppointmentScheduleList[scheduleIndex] - .customTimeDateSlotList![index] - .availableSlots!; + int index = serviceAppointmentScheduleList[scheduleIndex].selectedDateIndex!; + serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![index].availableSlots![slotIndex].isSelected = true; + serviceAppointmentScheduleList[scheduleIndex].selectedCustomTimeDateSlotModel!.availableSlots = serviceAppointmentScheduleList[scheduleIndex].customTimeDateSlotList![index].availableSlots!; notifyListeners(); } @@ -555,9 +469,7 @@ class AppointmentsVM extends BaseVM { int selectedSubServicesCounter = 0; onItemUpdateOrSelected(int index, bool selected, int itemId) { - int serviceIndex = servicesInCurrentAppointment.indexWhere( - (element) => - element.serviceId == currentServiceSelection!.serviceId!); + int serviceIndex = servicesInCurrentAppointment.indexWhere((element) => element.serviceId == currentServiceSelection!.serviceId!); // print("servicesInCurrentAppointment: ${servicesInCurrentAppointment.length}"); // if (serviceIndex == -1) { // return; @@ -572,28 +484,19 @@ class AppointmentsVM extends BaseVM { allSelectedItemsInAppointments.add(serviceItemsFromApi[index]); for (var element in allSelectedItemsInAppointments) { if (!ifItemAlreadySelected(element.id!)) { - servicesInCurrentAppointment[serviceIndex] - .serviceItems! - .add(serviceItemsFromApi[index]); + servicesInCurrentAppointment[serviceIndex].serviceItems!.add(serviceItemsFromApi[index]); servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice = - servicesInCurrentAppointment[serviceIndex] - .currentTotalServicePrice + - double.parse((serviceItemsFromApi[index].price) ?? "0.0"); + servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice + double.parse((serviceItemsFromApi[index].price) ?? "0.0"); } } } if (!selected) { selectedSubServicesCounter = selectedSubServicesCounter - 1; - currentServiceSelection!.serviceItems! - .removeWhere((element) => element.id == itemId); - allSelectedItemsInAppointments - .removeWhere((element) => element.id == itemId); + currentServiceSelection!.serviceItems!.removeWhere((element) => element.id == itemId); + allSelectedItemsInAppointments.removeWhere((element) => element.id == itemId); servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice = - servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice - - double.parse((serviceItemsFromApi[index].price) ?? "0.0"); - servicesInCurrentAppointment[serviceIndex] - .serviceItems! - .removeWhere((element) => element.id == itemId); + servicesInCurrentAppointment[serviceIndex].currentTotalServicePrice - double.parse((serviceItemsFromApi[index].price) ?? "0.0"); + servicesInCurrentAppointment[serviceIndex].serviceItems!.removeWhere((element) => element.id == itemId); } notifyListeners(); } @@ -649,8 +552,7 @@ class AppointmentsVM extends BaseVM { String pickHomeLocationError = ""; String selectSubServicesError = ""; - SelectionModel branchSelectedServiceId = - SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + SelectionModel branchSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); bool isCategoryAlreadyPresent(int id) { final contain = branchCategories.where((element) => element.id == id); @@ -663,16 +565,14 @@ class AppointmentsVM extends BaseVM { void getBranchCategories() async { for (var value in selectedBranchModel!.branchServices!) { if (!isCategoryAlreadyPresent(value.categoryId!)) { - branchCategories - .add(DropValue(value.categoryId!, value.categoryName!, "")); + branchCategories.add(DropValue(value.categoryId!, value.categoryName!, "")); } } notifyListeners(); } getBranchServices({required int categoryId}) async { - branchSelectedServiceId = - SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); + branchSelectedServiceId = SelectionModel(selectedOption: "", selectedId: -1, errorValue: ""); isHomeTapped = false; pickedHomeLocation = ""; pickHomeLocationError = ""; @@ -685,9 +585,7 @@ class AppointmentsVM extends BaseVM { } List getFilteredBranchServices({required int categoryId}) { - List filteredServices = selectedBranchModel!.branchServices! - .where((element) => element.categoryId == categoryId) - .toList(); + List filteredServices = selectedBranchModel!.branchServices!.where((element) => element.categoryId == categoryId).toList(); return filteredServices; } @@ -729,8 +627,7 @@ class AppointmentsVM extends BaseVM { return totalPrice.toString(); } - void openTheAddServiceBottomSheet(BuildContext context, - AppointmentsVM appointmentsVM) { + void openTheAddServiceBottomSheet(BuildContext context, AppointmentsVM appointmentsVM) { showModalBottomSheet( context: context, isScrollControlled: true, @@ -741,8 +638,7 @@ class AppointmentsVM extends BaseVM { ); } - void priceBreakDownClicked(BuildContext context, - ServiceModel selectedService) { + void priceBreakDownClicked(BuildContext context, ServiceModel selectedService) { showModalBottomSheet( context: context, isScrollControlled: true, @@ -758,27 +654,19 @@ class AppointmentsVM extends BaseVM { Column( children: List.generate( selectedService.serviceItems!.length, - (index) => - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "${selectedService.serviceItems![index].name}" - .toText( - fontSize: 12, - color: MyColors.lightTextColor, - isBold: true), - "${selectedService.serviceItems![index] - .price} SAR" - .toText(fontSize: 12, isBold: true), - ], - ), + (index) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${selectedService.serviceItems![index].name}".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), + "${selectedService.serviceItems![index].price} SAR".toText(fontSize: 12, isBold: true), + ], + ), ), ), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - "${selectedService.currentTotalServicePrice} SAR" - .toText(fontSize: 16, isBold: true), + "${selectedService.currentTotalServicePrice} SAR".toText(fontSize: 16, isBold: true), ], ), if (selectedService.isHomeSelected) ...[ @@ -787,20 +675,15 @@ class AppointmentsVM extends BaseVM { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "${totalKms}km ".toText( - fontSize: 12, - color: MyColors.lightTextColor, - isBold: true), - "${selectedService.rangePricePerKm} x $totalKms" - .toText(fontSize: 12, isBold: true), + "${totalKms}km ".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), + "${selectedService.rangePricePerKm} x $totalKms".toText(fontSize: 12, isBold: true), ], ), 8.height, Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - "${selectedService.rangePricePerKm ?? 0 * totalKms} SAR" - .toText(fontSize: 16, isBold: true), + "${selectedService.rangePricePerKm ?? 0 * totalKms} SAR".toText(fontSize: 16, isBold: true), ], ), ], @@ -813,18 +696,11 @@ class AppointmentsVM extends BaseVM { crossAxisAlignment: CrossAxisAlignment.end, children: [ (selectedService.isHomeSelected - ? "${(selectedService.currentTotalServicePrice) + - (double.parse((selectedService.rangePricePerKm ?? - "0.0")) * totalKms)}" - : "${selectedService.currentTotalServicePrice}") + ? "${(selectedService.currentTotalServicePrice) + (double.parse((selectedService.rangePricePerKm ?? "0.0")) * totalKms)}" + : "${selectedService.currentTotalServicePrice}") .toText(fontSize: 29, isBold: true), 2.width, - "SAR" - .toText( - color: MyColors.lightTextColor, - fontSize: 16, - isBold: true) - .paddingOnly(bottom: 5), + "SAR".toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5), ], ) ], @@ -844,8 +720,7 @@ class AppointmentsVM extends BaseVM { isValidated = false; break; } - if (schedule.selectedCustomTimeDateSlotModel!.date == null || - !schedule.selectedCustomTimeDateSlotModel!.date!.isSelected) { + if (schedule.selectedCustomTimeDateSlotModel!.date == null || !schedule.selectedCustomTimeDateSlotModel!.date!.isSelected) { isValidated = false; break; } else { @@ -853,9 +728,7 @@ class AppointmentsVM extends BaseVM { isValidated = false; break; } else { - TimeSlotModel slot = schedule - .selectedCustomTimeDateSlotModel!.availableSlots! - .firstWhere((element) => element.isSelected); + TimeSlotModel slot = schedule.selectedCustomTimeDateSlotModel!.availableSlots!.firstWhere((element) => element.isSelected); if (slot.date.isNotEmpty) { isValidated = true; break; @@ -864,8 +737,7 @@ class AppointmentsVM extends BaseVM { } } if (!isValidated) { - Utils.showToast( - "You must select appointment time for each schedule's appointment."); + Utils.showToast("You must select appointment time for each schedule's appointment."); return; } navigateWithName(context, AppRoutes.reviewAppointmentView); @@ -884,36 +756,30 @@ class AppointmentsVM extends BaseVM { } } - serviceAppointmentScheduleList = - await scheduleRepo.mergeServiceIntoAvailableSchedules( + serviceAppointmentScheduleList = await scheduleRepo.mergeServiceIntoAvailableSchedules( serviceItemIdsForHome: serviceItemIdsForHome, serviceItemIdsForWorkshop: serviceItemIdsForWorkshop, ); if (serviceAppointmentScheduleList.isEmpty) { Utils.hideLoading(context); - Utils.showToast( - "There are no available appointments for selected Items."); + Utils.showToast("There are no available appointments for selected Items."); return; } totalAmount = 0.0; amountToPayForAppointment = 0.0; for (var schedule in serviceAppointmentScheduleList) { - amountToPayForAppointment = - amountToPayForAppointment + (schedule.amountToPay ?? 0.0); + amountToPayForAppointment = amountToPayForAppointment + (schedule.amountToPay ?? 0.0); totalAmount = totalAmount + (schedule.amountTotal ?? 0.0); } Utils.hideLoading(context); - navigateWithName(context, AppRoutes.bookAppointmenSchedulesView, - arguments: ScreenArgumentsForAppointmentDetailPage( - routeFlag: 1, appointmentId: 0)); // 1 For Creating an Appointment + navigateWithName(context, AppRoutes.bookAppointmenSchedulesView, arguments: ScreenArgumentsForAppointmentDetailPage(routeFlag: 1, appointmentId: 0)); // 1 For Creating an Appointment notifyListeners(); } - Future onRescheduleAppointmentPressed({required BuildContext context, - required AppointmentListModel appointmentListModel}) async { + Future onRescheduleAppointmentPressed({required BuildContext context, required AppointmentListModel appointmentListModel}) async { Utils.showLoading(context); List serviceItemIdsForHome = []; @@ -930,16 +796,14 @@ class AppointmentsVM extends BaseVM { } } - serviceAppointmentScheduleList = - await scheduleRepo.mergeServiceIntoAvailableSchedules( + serviceAppointmentScheduleList = await scheduleRepo.mergeServiceIntoAvailableSchedules( serviceItemIdsForHome: serviceItemIdsForHome, serviceItemIdsForWorkshop: serviceItemIdsForWorkshop, ); if (serviceAppointmentScheduleList.isEmpty) { Utils.hideLoading(context); - Utils.showToast( - "There are no available appointments for selected Items."); + Utils.showToast("There are no available appointments for selected Items."); return; } Utils.hideLoading(context); @@ -947,36 +811,29 @@ class AppointmentsVM extends BaseVM { navigateWithName( context, AppRoutes.bookAppointmenSchedulesView, - arguments: ScreenArgumentsForAppointmentDetailPage( - routeFlag: 2, appointmentId: appointmentListModel.id ?? 0), + arguments: ScreenArgumentsForAppointmentDetailPage(routeFlag: 2, appointmentId: appointmentListModel.id ?? 0), ); // 2 For Rescheduling an Appointment notifyListeners(); } - Future onRescheduleAppointmentConfirmPressed( - {required BuildContext context, - required int appointmentId, - required int selectedSlotId}) async { + Future onRescheduleAppointmentConfirmPressed({required BuildContext context, required int appointmentId, required int selectedSlotId}) async { Utils.showLoading(context); try { - GenericRespModel genericRespModel = - await scheduleRepo.cancelOrRescheduleServiceAppointment( + GenericRespModel genericRespModel = await scheduleRepo.cancelOrRescheduleServiceAppointment( serviceAppointmentID: appointmentId, serviceSlotID: selectedSlotId, appointmentScheduleAction: 1, // 1 for Reschedule and 2 for Cancel ); - if (genericRespModel.messageStatus == 2 || - genericRespModel.data == null) { + if (genericRespModel.messageStatus == 2 || genericRespModel.data == null) { Utils.hideLoading(context); Utils.showToast("${genericRespModel.message.toString()}"); return; } if (genericRespModel.messageStatus == 1) { context.read().onNavbarTapped(1); - applyFilterOnAppointmentsVM( - appointmentStatusEnum: AppointmentStatusEnum.cancelled); + applyFilterOnAppointmentsVM(appointmentStatusEnum: AppointmentStatusEnum.cancelled); Utils.showToast("${genericRespModel.message.toString()}"); getMyAppointments(); Utils.hideLoading(context); diff --git a/lib/view_models/payment_view_model.dart b/lib/view_models/payment_view_model.dart index 539731b..c3f9f9d 100644 --- a/lib/view_models/payment_view_model.dart +++ b/lib/view_models/payment_view_model.dart @@ -76,7 +76,7 @@ class PaymentVM extends ChangeNotifier { await Future.delayed(const Duration(seconds: 2)); Utils.hideLoading(context); - print("payOrderDetailRespModel: ${payOrderDetailRespModel.toString()}"); + log("payOrderDetailRespModel: ${payOrderDetailRespModel.toString()}"); if (payOrderDetailRespModel.isPaid == null || !payOrderDetailRespModel.isPaid!) { Utils.showToast("Payment Failed!"); @@ -96,7 +96,7 @@ class PaymentVM extends ChangeNotifier { await Future.delayed(const Duration(seconds: 2)); Utils.hideLoading(context); - print("payOrderDetailRespModel: ${payOrderDetailRespModel.toString()}"); + log("payOrderDetailRespModel: ${payOrderDetailRespModel.toString()}"); if (payOrderDetailRespModel.isPaid == null || !payOrderDetailRespModel.isPaid!) { Utils.showToast("Payment Failed!"); @@ -114,6 +114,7 @@ class PaymentVM extends ChangeNotifier { case PaymentTypes.subscription: return orderProviderSubscriptionId; case PaymentTypes.appointment: + case PaymentTypes.partialAppointment: return -1; case PaymentTypes.request: return requestId; @@ -151,10 +152,15 @@ class PaymentVM extends ChangeNotifier { break; case PaymentTypes.extendAds: // TODO: Handle this case. + break; + case PaymentTypes.partialAppointment: + log("Partial Appointment Payment has been Failed!!"); + break; } }, onSuccess: () async { + // TOD0: we have to take payment confirmation methods from Backend team and make success callbacks like onAdsPaymentSuccess switch (paymentTypeEnum) { case PaymentTypes.subscription: break; @@ -168,6 +174,9 @@ class PaymentVM extends ChangeNotifier { case PaymentTypes.extendAds: await onAdsPaymentSuccess(context: context, paymentTypeId: paymentTypeEnum.getIdFromPaymentTypesEnum(), currentAdId: currentAdId); break; + case PaymentTypes.partialAppointment: + log("Partial Appointment Payment has been Succeeded"); + break; } }, ); @@ -177,6 +186,7 @@ class PaymentVM extends ChangeNotifier { currentPaymentType = paymentType; switch (currentPaymentType) { case PaymentTypes.appointment: + case PaymentTypes.partialAppointment: if (appointmentIdsForPayment.isEmpty) return; await placeThePayment(context: context, paymentTypeEnum: paymentType); break; diff --git a/lib/views/advertisement/ads_detail_view.dart b/lib/views/advertisement/ads_detail_view.dart index 47058c8..959eb15 100644 --- a/lib/views/advertisement/ads_detail_view.dart +++ b/lib/views/advertisement/ads_detail_view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/consts.dart'; @@ -40,6 +41,7 @@ class AdsDetailView extends StatefulWidget { class _AdsDetailViewState extends State { @override void initState() { + log("ad: ${widget.adDetails.id}"); scheduleMicrotask(() { onAdDetailsLoaded(); }); diff --git a/lib/views/appointments/appointment_detail_view.dart b/lib/views/appointments/appointment_detail_view.dart index 1c1afe6..65402f0 100644 --- a/lib/views/appointments/appointment_detail_view.dart +++ b/lib/views/appointments/appointment_detail_view.dart @@ -33,6 +33,7 @@ class AppointmentDetailView extends StatelessWidget { } Widget getArrivedBottomActionButton({required BuildContext context, required AppointmentPaymentStatusEnum appointmentPaymentStatusEnum}) { + final appointmentVM = context.read(); switch (appointmentPaymentStatusEnum) { case AppointmentPaymentStatusEnum.defaultStatus: case AppointmentPaymentStatusEnum.paid: @@ -42,19 +43,34 @@ class AppointmentDetailView extends StatelessWidget { alignment: Alignment.bottomCenter, child: Row( children: [ - getBaseActionButtonWidget(color: MyColors.grey98Color.withOpacity(0.3), textColor: MyColors.lightTextColor, onPressed: () {}, text: "In Progress"), + getBaseActionButtonWidget( + color: MyColors.grey98Color.withOpacity(0.3), + textColor: MyColors.lightTextColor, + onPressed: () {}, + text: "Work In Progress", + ), ], ), ); case AppointmentPaymentStatusEnum.payNow: - return Expanded( - child: ShowFillButton( - maxHeight: 55, - title: "Pay Now", - onPressed: () {}, - backgroundColor: MyColors.darkPrimaryColor, - txtColor: MyColors.white, - fontSize: 18, + return Align( + alignment: Alignment.bottomCenter, + child: Row( + children: [ + getBaseActionButtonWidget( + color: MyColors.darkPrimaryColor, + textColor: MyColors.white, + onPressed: () { + if (appointmentListModel.remainingAmount != null && appointmentListModel.remainingAmount! > 0.0) { + appointmentVM.onPayNowPressedForAppointment( + context: context, + appointmentID: appointmentListModel.id ?? 0, + ); + } + }, + text: "Pay Now", + ), + ], ), ); } @@ -87,7 +103,23 @@ class AppointmentDetailView extends StatelessWidget { ], ), ); + case AppointmentStatusEnum.visitCompleted: + return Align( + alignment: Alignment.bottomCenter, + child: Row( + children: [ + getBaseActionButtonWidget( + color: MyColors.grey98Color.withOpacity(0.3), + textColor: MyColors.lightTextColor, + onPressed: () {}, + text: "Visit Completed", + ), + ], + ), + ); + case AppointmentStatusEnum.arrived: + case AppointmentStatusEnum.workStarted: return getArrivedBottomActionButton(appointmentPaymentStatusEnum: appointmentListModel.appointmentPaymentStatusEnum ?? AppointmentPaymentStatusEnum.defaultStatus, context: context); case AppointmentStatusEnum.cancelled: return Align( @@ -223,10 +255,7 @@ class AppointmentDetailView extends StatelessWidget { ((service.currentTotalServicePrice).toString()).toText(fontSize: 25, isBold: true), 2.width, "SAR".toText(color: MyColors.lightTextColor, fontSize: 16, isBold: true).paddingOnly(bottom: 5), - Icon( - Icons.arrow_drop_down, - size: 30, - ) + const Icon(Icons.arrow_drop_down, size: 30) ], ).onPress(() => appointmentsVM.priceBreakDownClicked(context, service)), ], @@ -235,28 +264,30 @@ class AppointmentDetailView extends StatelessWidget { ), ], 15.height, - Row( - children: [ - CardButtonWithIcon( - title: "Reschedule Appointment", - onCardTapped: () { - context.read().onRescheduleAppointmentPressed(context: context, appointmentListModel: appointmentListModel); - }, - icon: MyAssets.scheduleAppointmentIcon.buildSvg(), - ), - if (appointmentListModel.appointmentStatusEnum == AppointmentStatusEnum.booked) ...[ - 10.width, + if (appointmentListModel.appointmentStatusEnum != AppointmentStatusEnum.workStarted && appointmentListModel.appointmentStatusEnum != AppointmentStatusEnum.visitCompleted) ...[ + Row( + children: [ CardButtonWithIcon( - title: "Pay for Appointment", + title: "Reschedule Appointment", onCardTapped: () { - context.read().onConfirmAppointmentPressed(context: context, appointmentId: appointmentListModel.id); + context.read().onRescheduleAppointmentPressed(context: context, appointmentListModel: appointmentListModel); }, - icon: MyAssets.creditCardIcon.buildSvg(), + icon: MyAssets.scheduleAppointmentIcon.buildSvg(), ), + if (appointmentListModel.appointmentStatusEnum == AppointmentStatusEnum.booked) ...[ + 10.width, + CardButtonWithIcon( + title: "Pay for Appointment", + onCardTapped: () { + context.read().onConfirmAppointmentPressed(context: context, appointmentId: appointmentListModel.id); + }, + icon: MyAssets.creditCardIcon.buildSvg(), + ), + ], ], - ], - ), - 15.height, + ), + 15.height, + ], ], ).toWhiteContainer(width: double.infinity, allPading: 12), buildBottomActionButton(appointmentStatusEnum: appointmentListModel.appointmentStatusEnum!, context: context), From 6c416ef7a0a6e6e1c3079ea536a9cef2e9c28a59 Mon Sep 17 00:00:00 2001 From: FaizHashmiCS22 Date: Tue, 2 Jan 2024 12:00:48 +0300 Subject: [PATCH 4/6] Appointment Flow Testing --- lib/models/general_models/generic_resp_model.dart | 5 +++++ .../ad_creation_steps/ad_review_containers.dart | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/models/general_models/generic_resp_model.dart b/lib/models/general_models/generic_resp_model.dart index a221dc4..83ed015 100644 --- a/lib/models/general_models/generic_resp_model.dart +++ b/lib/models/general_models/generic_resp_model.dart @@ -114,6 +114,11 @@ class Ads { data['isMCHandled'] = isMCHandled; return data; } + + @override + String toString() { + return 'Ads{id: $id, adsDurationID: $adsDurationID, startDate: $startDate, countryId: $countryId, specialServiceIDs: $specialServiceIDs, isMCHandled: $isMCHandled}'; + } } class VehiclePosting { diff --git a/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart b/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart index 193ab0c..e256a9e 100644 --- a/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart +++ b/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart @@ -18,7 +18,7 @@ class ReviewAd extends StatelessWidget { VehicleDetailsReview().toWhiteContainer(width: double.infinity, allPading: 12, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 4)), DamagePartsReview().toWhiteContainer(width: double.infinity, allPading: 12, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 4)), AdDurationReview().toWhiteContainer(width: double.infinity, allPading: 12, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 4)), - AdContactDetailsReview().toWhiteContainer(width: double.infinity, allPading: 12, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 4)), + AdContactDetailsReview(), ], ); } @@ -309,7 +309,7 @@ class AdContactDetailsReview extends StatelessWidget { ], ), ], - ) + ).toWhiteContainer(width: double.infinity, allPading: 12, margin: const EdgeInsets.symmetric(horizontal: 21, vertical: 4)) : const SizedBox.shrink(); } } From f970e4a540099cb4e3495fb65eb22763e36f52b8 Mon Sep 17 00:00:00 2001 From: Faiz Hashmi Date: Wed, 21 Feb 2024 09:43:06 +0300 Subject: [PATCH 5/6] Added Account Screens as per new Design --- lib/classes/consts.dart | 13 +- lib/config/routes.dart | 6 + lib/extensions/string_extensions.dart | 6 + .../reserved_ads_models.dart | 15 +- .../chat_models/chat_message_model.dart | 4 +- .../general_models/generic_resp_model.dart | 56 ++-- lib/repositories/ads_repo.dart | 65 ++++- lib/services/common_services.dart | 32 ++- lib/utils/AppPermissionHandler.dart | 67 ----- lib/utils/app_permission_handler.dart | 97 +++++++ lib/utils/dialogs_and_bottomsheets.dart | 2 +- lib/utils/enums.dart | 3 + lib/utils/location/Location.dart | 2 +- lib/view_models/ad_view_model.dart | 84 +++++- lib/view_models/appointments_view_model.dart | 4 +- lib/view_models/chat_view_model.dart | 49 +++- lib/view_models/payment_view_model.dart | 2 +- lib/view_models/requests_view_model.dart | 5 +- .../ad_duration_container.dart | 8 +- .../ad_review_containers.dart | 12 +- .../damage_parts_container.dart | 6 +- .../vehicle_details_container.dart | 6 +- lib/views/advertisement/ads_detail_view.dart | 271 ++++++++++++++---- .../picked_images_container.dart | 70 +++-- ...appointment_service_pick_bottom_sheet.dart | 4 +- lib/views/chat/chat_view.dart | 4 - lib/views/profile/profile_view.dart | 270 +++++++++++++++++ lib/views/requests/create_request_page.dart | 6 +- lib/views/requests/offer_list_page.dart | 3 +- lib/views/requests/widget/request_item.dart | 2 +- .../setting_options_invite_friends.dart | 20 +- .../setting_options_language.dart | 11 +- .../widgets/custom_setting_options_tile.dart | 35 +++ lib/views/user/login_with_password_page.dart | 3 +- .../common_widgets/confirm_dialog.dart | 67 +++++ pubspec.yaml | 5 +- 36 files changed, 1023 insertions(+), 292 deletions(-) delete mode 100644 lib/utils/AppPermissionHandler.dart create mode 100644 lib/utils/app_permission_handler.dart create mode 100644 lib/views/profile/profile_view.dart create mode 100644 lib/widgets/common_widgets/confirm_dialog.dart diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 40039f3..06438d3 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -61,6 +61,11 @@ class ApiConsts { static String ServiceProviderAppointmentRescheduleCancelAppointment = "${baseUrlServices}api/ServiceProviders/ServiceProviderAppointment_RescheduleCancelAppointment"; static String AddNewServicesInAppointment = "${baseUrlServices}api/ServiceProviders/ServiceProviderAppointment_ServiceItemAdd"; + static String getAppointmentSlots = "${baseUrlServices}api/ServiceProviders/ScheduleSlotsInfo_Get"; + static String updateAppointmentStatus = "${baseUrlServices}api/ServiceProviders/ServiceProvidersAppointmentStatus_Update"; + static String updateAppointmentPaymentStatus = "${baseUrlServices}api/ServiceProviders/ServiceProviderAppointmentServiceItemPaymentStatus_Update"; + static String createMergeAppointment = "${baseUrlServices}api/ServiceProviders/ServiceProviderMergeAppointment_Create"; + //ServiceProvidersServiceID as params // static String servicesGet = "${baseUrlServices}api/ServiceProviders/Services_Get"; @@ -105,10 +110,7 @@ class ApiConsts { static String adsPhotoOfficeAppointmentScheduleSlotGet = "${baseUrlServices}api/Advertisement/PhotoOfficeAppointmentScheduleSlot_Get"; static String adsPhotoOfficeAppointmentCreate = "${baseUrlServices}api/Advertisement/PhotoOfficeAppointment_Create"; static String adsMCBankAccountAdGet = "${baseUrlServices}api/Advertisement/MCBankAccountAd_Get"; - static String getAppointmentSlots = "${baseUrlServices}api/ServiceProviders/ScheduleSlotsInfo_Get"; - static String updateAppointmentStatus = "${baseUrlServices}api/ServiceProviders/ServiceProvidersAppointmentStatus_Update"; - static String updateAppointmentPaymentStatus = "${baseUrlServices}api/ServiceProviders/ServiceProviderAppointmentServiceItemPaymentStatus_Update"; - static String createMergeAppointment = "${baseUrlServices}api/ServiceProviders/ServiceProviderMergeAppointment_Create"; + static String adsReserveCreate = "${baseUrlServices}api/Advertisement/AdsReserve_Create"; //Subscription static String getAllSubscriptions = "${baseUrlServices}api/Common/Subscription_Get"; @@ -158,6 +160,7 @@ class GlobalConsts { static String welcomeVideoUrl = "welcomeVideoUrl"; static String doNotShowWelcomeVideo = "doNotShowWelcomeVideo"; static String demandAmountError = "Amount Cannot be Empty"; + static String reservationCancelError = "Cancellation Reason Cannot be Empty"; static String descriptionError = "Description Cannot be Empty"; static String vehicleVinError = "Vehicle VIN Cannot be Empty"; static String vehicleTitleError = "Vehicle Title Cannot be Empty"; @@ -262,8 +265,6 @@ class MyAssets { static String tamaraEngPng = "${assetPath}icons/payments/tamara_en.png"; static String visaPng = "${assetPath}icons/payments/visa.png"; - - static String whatsAppIcon = "${assetPath}icons/whatsapp_icon.svg"; } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 30ea3a5..8d500e4 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -3,6 +3,7 @@ import 'package:mc_common_app/models/requests_models/provider_offers_model.dart' import 'package:mc_common_app/models/requests_models/request_model.dart'; import 'package:mc_common_app/models/user_models/register_user.dart'; import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/views/profile/profile_view.dart'; import 'package:mc_common_app/views/user/change_email_page.dart'; import 'package:mc_common_app/views/user/change_mobile_page.dart'; @@ -83,6 +84,10 @@ class AppRoutes { static const String settingOptionsLanguages = "/settingOptionsLanguages"; static const String settingOptionsInviteFriends = "/settingOptionsInviteFriends"; + //Profile Screen + static const String profileView = "/profileView"; + + //Chat static const String chatView = "/chatView"; @@ -107,6 +112,7 @@ class AppRoutes { changeMobilePage: (context) => ChangeMobilePage(), changeEmailPage: (context) => const ChangeEmailPage(), editAccountPage: (context) => const EditAccountPage(), + profileView: (context) => const ProfileScreen(), }; } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 1a8125d..36172f3 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -460,6 +460,12 @@ extension AdReserveStatusEnum on int { return AdReserveStatus.cancelledByOwner; } else if (this == 3) { return AdReserveStatus.cancelledByAdmin; + } else if (this == 4) { + return AdReserveStatus.timeOver; + } else if (this == 5) { + return AdReserveStatus.dealDone; + } else if (this == 6) { + return AdReserveStatus.fullPaymentVerified; } else { return AdReserveStatus.defaultStatus; } diff --git a/lib/models/advertisment_models/reserved_ads_models.dart b/lib/models/advertisment_models/reserved_ads_models.dart index 83c793f..c6f7441 100644 --- a/lib/models/advertisment_models/reserved_ads_models.dart +++ b/lib/models/advertisment_models/reserved_ads_models.dart @@ -3,18 +3,30 @@ class MyReservedAdsRespModel { int? adsID; int? customerID; int? paymentStatus; + int? adsReserveStatus; int? reservationTimeID; double? reservationBasePrice; double? refundAmount; String? refundDate; - MyReservedAdsRespModel({this.id, this.adsID, this.customerID, this.paymentStatus, this.reservationTimeID, this.reservationBasePrice, this.refundAmount, this.refundDate}); + MyReservedAdsRespModel({ + this.id, + this.adsID, + this.customerID, + this.paymentStatus, + this.adsReserveStatus, + this.reservationTimeID, + this.reservationBasePrice, + this.refundAmount, + this.refundDate, + }); MyReservedAdsRespModel.fromJson(Map json) { id = json['id']; adsID = json['adsID']; customerID = json['customerID']; paymentStatus = json['paymentStatus']; + paymentStatus = json['adsReserveStatus']; reservationTimeID = json['reservationTimeID']; reservationBasePrice = json['reservationBasePrice']; refundAmount = json['refundAmount']; @@ -27,6 +39,7 @@ class MyReservedAdsRespModel { data['adsID'] = adsID; data['customerID'] = customerID; data['paymentStatus'] = paymentStatus; + data['adsReserveStatus'] = adsReserveStatus; data['reservationTimeID'] = reservationTimeID; data['reservationBasePrice'] = reservationBasePrice; data['refundAmount'] = refundAmount; diff --git a/lib/models/chat_models/chat_message_model.dart b/lib/models/chat_models/chat_message_model.dart index 91834f4..7702053 100644 --- a/lib/models/chat_models/chat_message_model.dart +++ b/lib/models/chat_models/chat_message_model.dart @@ -60,8 +60,7 @@ class ChatMessageModel { reqOffer = json['reqOffer'] != null ? ReqOffer.fromJson(json['reqOffer']) : null; isRead = json['isRead']; readOn = json['readOn']; - isMyMessage = (json['senderUserID']).toString().toUpperCase()== myUserId; - + isMyMessage = (json['senderUserID']).toString().toUpperCase() == myUserId; } } @@ -87,7 +86,6 @@ class ReqOffer { }); ReqOffer.fromJson(Map json) { - log("the json: $json"); id = json['id']; requestID = json['requestID']; serviceProviderID = json['serviceProviderID']; diff --git a/lib/models/general_models/generic_resp_model.dart b/lib/models/general_models/generic_resp_model.dart index 83ed015..ffd31e8 100644 --- a/lib/models/general_models/generic_resp_model.dart +++ b/lib/models/general_models/generic_resp_model.dart @@ -11,17 +11,14 @@ class GenericRespModel { int? totalItemsCount; String? message; - - factory GenericRespModel.fromJson(Map json) => - GenericRespModel( + factory GenericRespModel.fromJson(Map json) => GenericRespModel( data: json["data"], messageStatus: json["messageStatus"], totalItemsCount: json["totalItemsCount"], message: json["message"], ); - Map toJson() => - { + Map toJson() => { "data": data, "messageStatus": messageStatus, "totalItemsCount": totalItemsCount, @@ -29,39 +26,6 @@ class GenericRespModel { }; } -var json = { - "ads": {"id": 0, "adsDurationID": 1, "startDate": "2023-04-12T10:10:20.905Z", "countryId": 1, "specialServiceIDs": [], "isMCHandled": false}, - "vehiclePosting": { - "id": 0, - "userID": "1A1597B3-D5A0-433A-098B-08DB189E51EC", - "vehicleType": 1, - "vehicleModelID": 1, - "vehicleModelYearID": 1, - "vehicleColorID": 2, - "vehicleCategoryID": 1, - "vehicleConditionID": 1, - "vehicleMileageID": 1, - "vehicleTransmissionID": 1, - "vehicleSellerTypeID": 1, - "cityID": 1, - "price": 33, - "vehicleVIN": "fdfd", - "vehicleDescription": "dsd", - "vehicleTitle": "fsfs", - "vehicleDescriptionN": "dsdds", - "isFinanceAvailable": true, - "warantyYears": 2, - "demandAmount": 34, - "adStatus": 1, - "vehiclePostingImages": [ - {"id": 0, "imageName": "onon", "imageUrl": "string", "imageStr": null, "vehiclePostingID": 0, "vehiclePosting": null} - ], - "vehiclePostingDamageParts": [ - {"id": 0, "comment": "hhsa", "vehicleImageBase64": null, "vehicleDamagePartID": 1, "vehiclePostingID": 0, "isActive": true} - ] - } -}; - class AdsCreationPayloadModel { Ads? ads; VehiclePosting? vehiclePosting; @@ -282,6 +246,11 @@ class VehiclePostingImages { data['vehiclePosting'] = vehiclePosting; return data; } + + // @override + // String toString() { + // return 'VehiclePostingImages{id: $id, imageName: $imageName, imageUrl: $imageUrl, imageStr: $imageStr, vehiclePostingID: $vehiclePostingID, vehiclePosting: $vehiclePosting}'; + // } } class RequestPostingImages { @@ -304,8 +273,12 @@ class RequestPostingImages { data['requestID'] = requestID; return data; } -} + @override + String toString() { + return 'RequestPostingImages{id: $id, requestImage: $requestImage, requestID: $requestID}'; + } +} class VehiclePostingDamageParts { int? id; @@ -336,4 +309,9 @@ class VehiclePostingDamageParts { data['isActive'] = isActive; return data; } + + // @override + // String toString() { + // return 'VehiclePostingDamageParts{id: $id, comment: $comment, vehicleImageBase64: $vehicleImageBase64, vehicleDamagePartID: $vehicleDamagePartID, vehiclePostingID: $vehiclePostingID, isActive: $isActive}'; + // } } diff --git a/lib/repositories/ads_repo.dart b/lib/repositories/ads_repo.dart index 0f419fa..dcd130e 100644 --- a/lib/repositories/ads_repo.dart +++ b/lib/repositories/ads_repo.dart @@ -51,7 +51,7 @@ abstract class AdsRepo { Future> getAllAds({required bool isMyAds}); - Future> getMyReservedAds(); + Future> getMyReservedAds(); Future> getMyAds(); @@ -63,7 +63,9 @@ abstract class AdsRepo { Future deleteAd({required int adId}); - Future cancelMyAdReservation({required int adId}); + Future cancelMyAdReservation({required int adId, required int adsReserveStatus, required String reason}); + + Future createReserveAd({required int adId}); } class AdsRepoImp implements AdsRepo { @@ -344,6 +346,7 @@ class AdsRepoImp implements AdsRepo { } }; + log("posting this ads: ${postParams.toString()}"); String token = appState.getUser.data!.accessToken ?? ""; GenericRespModel adsGenericModel = await apiClient.postJsonForObject( (json) => GenericRespModel.fromJson(json), @@ -376,7 +379,7 @@ class AdsRepoImp implements AdsRepo { } @override - Future> getMyReservedAds() async { + Future> getMyReservedAds() async { var params = { "userID": appState.getUser.data!.userInfo!.userId ?? "", }; @@ -387,7 +390,31 @@ class AdsRepoImp implements AdsRepo { ApiConsts.myAdsReserveGet, queryParameters: params, ); - List vehicleAdsDetails = List.generate(adsGenericModel.data.length, (index) => MyReservedAdsRespModel.fromJson(adsGenericModel.data[index])); + List reservedAds = List.generate(adsGenericModel.data.length, (index) => MyReservedAdsRespModel.fromJson(adsGenericModel.data[index])); + List selectedIdsString = reservedAds.map((component) => component.adsID.toString()).toList(); + + if (selectedIdsString.isEmpty) { + return []; + } + + return await getAdsPerSpecificIds(ids: selectedIdsString, reservedAds: reservedAds); + } + + Future> getAdsPerSpecificIds({required List ids, required List reservedAds}) async { + var params = { + "AdsIDs": ids, + }; + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( + token: appState.getUser.data!.accessToken, + (json) => GenericRespModel.fromJson(json), + queryParameters: params, + ApiConsts.vehicleAdsGet, + ); + List vehicleAdsDetails = List.generate(adsGenericModel.data.length, (index) => AdDetailsModel.fromJson(adsGenericModel.data[index], true)); + + for (int i = 0; i < vehicleAdsDetails.length; i++) { + vehicleAdsDetails[i].adReserveStatus = (reservedAds[i].adsReserveStatus ?? 0).toAdRserveStatusEnum(); + } return vehicleAdsDetails; } @@ -463,9 +490,14 @@ class AdsRepoImp implements AdsRepo { } @override - Future cancelMyAdReservation({required int adId}) async { + Future cancelMyAdReservation({required int adId, required int adsReserveStatus, required String reason}) async { + int customerID = AppState().getUser.data!.userInfo!.customerId ?? 0; + var postParams = { - "adID": adId, + "adsID": adId, + "customerID": customerID, + "adsReserveStatus": adsReserveStatus, + "comment": reason, }; String token = appState.getUser.data!.accessToken ?? ""; @@ -479,6 +511,27 @@ class AdsRepoImp implements AdsRepo { return Future.value(adsGenericModel); } + @override + Future createReserveAd({required int adId}) async { + int customerID = AppState().getUser.data!.userInfo!.customerId ?? 0; + + var postParams = { + "adsID": adId, + "customerID": customerID, + "adsReserveStatus": 0, + }; + + String token = appState.getUser.data!.accessToken ?? ""; + GenericRespModel adsGenericModel = await apiClient.postJsonForObject( + (json) => GenericRespModel.fromJson(json), + ApiConsts.adsReserveCreate, + postParams, + token: token, + ); + + return Future.value(adsGenericModel); + } + @override Future createAppointmentForAdSpecialService({ required int adId, diff --git a/lib/services/common_services.dart b/lib/services/common_services.dart index 482037a..14a52cd 100644 --- a/lib/services/common_services.dart +++ b/lib/services/common_services.dart @@ -1,15 +1,19 @@ +import 'dart:developer'; import 'dart:io'; import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:mc_common_app/utils/app_permission_handler.dart'; import 'package:mc_common_app/utils/utils.dart'; +import 'package:permission_handler/permission_handler.dart'; abstract class CommonAppServices { Future> pickMultipleImages(); Future pickImageFromPhone(int sourceFlag); - Future pickFile({FileType fileType = FileType.custom, List? allowedExtensions}); + Future?> pickMultipleFiles(BuildContext context); } class CommonServicesImp implements CommonAppServices { @@ -24,23 +28,23 @@ class CommonServicesImp implements CommonAppServices { } @override - Future pickFile({FileType fileType = FileType.custom, List? allowedExtensions}) async { - FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['png', 'pdf', 'jpeg']); + Future?> pickMultipleFiles(BuildContext context) async { + FilePickerResult? result; + final status = await AppPermissions.checkStoragePermissions(context); + if (status) { + result = await FilePicker.platform.pickFiles(allowMultiple: true, type: FileType.custom, allowedExtensions: ['pdf']); + } + + List pickedFiles = []; if (result != null) { - File file = File(result.files.single.path ?? ""); - int sizeInBytes = file.lengthSync(); - // double sizeInMb = sizeInBytes / (1024 * 1024); - if (sizeInBytes > 1000) { - Utils.showToast("File is larger then 1KB"); - } else { - return file; + for (var element in result.files) { + if (element.path != null) { + pickedFiles.add(File(element.path!)); + } } - } else { - // User canceled the picker - return null; } - return null; + return pickedFiles; } @override diff --git a/lib/utils/AppPermissionHandler.dart b/lib/utils/AppPermissionHandler.dart deleted file mode 100644 index 56d9082..0000000 --- a/lib/utils/AppPermissionHandler.dart +++ /dev/null @@ -1,67 +0,0 @@ - -import 'package:permission_handler/permission_handler.dart'; - -import 'dialogs_and_bottomsheets.dart'; - -enum ConfirmAction { CANCEL, ACCEPT } - -Future requestPermissionGranted( - context, Permission requestPermissions) async { - var result = await requestPermissions.request(); - - switch (result) { - case PermissionStatus.granted: - // Application has been given permission to use the feature. - return true; - case PermissionStatus.denied: - // Application has been denied permission to use the feature. - return false; - case PermissionStatus.permanentlyDenied: - ConfirmAction? res = await showConfirmDialogs( - context, - 'You was denied Permission. You have give manual permission from app setting. ', - 'Open App Setting', - 'Cancel'); - if (res == ConfirmAction.ACCEPT) { - return false; - } else if (res == ConfirmAction.CANCEL) { - return false; - } - return false; - case PermissionStatus.restricted: - // iOS has restricted access to a specific feature. - return false; - default: - return false; - } -} - - -class AppPermissions{ - static void location(Function(bool) completion) { - Permission.location.isGranted.then((isGranted){ - if(!isGranted){ - Permission.location.request().then((granted){ - completion(granted == PermissionStatus.granted); - }); - } - completion(isGranted); - }); - - } - - static void checkAll(Function(bool) completion){ - [ - Permission.location - ].request().then((value){ - - bool allGranted = false; - value.values.forEach((element) { - allGranted = allGranted && element == PermissionStatus.granted; - }); - - completion(allGranted); - - }); - } -} \ No newline at end of file diff --git a/lib/utils/app_permission_handler.dart b/lib/utils/app_permission_handler.dart new file mode 100644 index 0000000..0d8bfd5 --- /dev/null +++ b/lib/utils/app_permission_handler.dart @@ -0,0 +1,97 @@ +import 'dart:developer'; + +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:mc_common_app/widgets/common_widgets/confirm_dialog.dart'; +import 'package:permission_handler/permission_handler.dart'; + +import 'dialogs_and_bottomsheets.dart'; + +enum ConfirmAction { CANCEL, ACCEPT } + +Future requestPermissionGranted(context, Permission requestPermissions) async { + var result = await requestPermissions.request(); + + log("result: $result"); + switch (result) { + case PermissionStatus.granted: + // Application has been given permission to use the feature. + return true; + case PermissionStatus.denied: + // Application has been denied permission to use the feature. + return false; + case PermissionStatus.permanentlyDenied: + ConfirmAction? res = await showConfirmDialogs(context, 'This permission was denied permanently, Please go to settings and allow. ', 'Open App Setting', 'Cancel'); + if (res == ConfirmAction.ACCEPT) { + return false; + } else if (res == ConfirmAction.CANCEL) { + return false; + } + return false; + case PermissionStatus.restricted: + // iOS has restricted access to a specific feature. + return false; + default: + return false; + } +} + +class AppPermissions { + static void location(Function(bool) completion) { + Permission.location.isGranted.then((isGranted) { + if (!isGranted) { + Permission.location.request().then((granted) { + completion(granted == PermissionStatus.granted); + }); + } + completion(isGranted); + }); + } + + static void checkAll(Function(bool) completion) { + [Permission.location].request().then((value) { + bool allGranted = false; + for (var element in value.values) { + allGranted = allGranted && element == PermissionStatus.granted; + } + + completion(allGranted); + }); + } + + static getDialog(BuildContext context) { + return showDialog( + context: context, + builder: (BuildContext cxt) => ConfirmDialog( + message: "You need to give storage permission to select files.", + onTap: () { + Navigator.pop(context); + }, + ), + ); + } + + static Future checkStoragePermissions(BuildContext context) async { + bool permissionStatus; + final deviceInfo = await DeviceInfoPlugin().androidInfo; + + if (deviceInfo.version.sdkInt! > 32) { + permissionStatus = await Permission.photos.request().isGranted; + if (permissionStatus) { + return true; + } else { + getDialog(context); + return false; + } + } else { + permissionStatus = await Permission.storage.request().isGranted; + if (permissionStatus) { + return true; + } else { + getDialog(context); + + return false; + } + } + } +} diff --git a/lib/utils/dialogs_and_bottomsheets.dart b/lib/utils/dialogs_and_bottomsheets.dart index 7d1f06f..4360164 100644 --- a/lib/utils/dialogs_and_bottomsheets.dart +++ b/lib/utils/dialogs_and_bottomsheets.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; -import 'package:mc_common_app/utils/AppPermissionHandler.dart'; +import 'package:mc_common_app/utils/app_permission_handler.dart'; import 'package:mc_common_app/widgets/common_widgets/info_bottom_sheet.dart'; Future showConfirmDialogs(context, msg, positiveText, negativeText) async { diff --git a/lib/utils/enums.dart b/lib/utils/enums.dart index b70f540..79c33c6 100644 --- a/lib/utils/enums.dart +++ b/lib/utils/enums.dart @@ -17,6 +17,9 @@ enum AdReserveStatus { reserved, cancelledByOwner, cancelledByAdmin, + timeOver, + dealDone, + fullPaymentVerified, } enum CreatedByRoleEnum { customer, provider, admin, allAds } diff --git a/lib/utils/location/Location.dart b/lib/utils/location/Location.dart index b8ba710..bdb67b4 100644 --- a/lib/utils/location/Location.dart +++ b/lib/utils/location/Location.dart @@ -7,7 +7,7 @@ import 'package:flutter/rendering.dart'; import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; -import 'package:mc_common_app/utils/AppPermissionHandler.dart'; +import 'package:mc_common_app/utils/app_permission_handler.dart'; import 'package:mc_common_app/utils/utils.dart'; diff --git a/lib/view_models/ad_view_model.dart b/lib/view_models/ad_view_model.dart index 6fbe9fd..eba6428 100644 --- a/lib/view_models/ad_view_model.dart +++ b/lib/view_models/ad_view_model.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; import 'package:flutter/cupertino.dart'; @@ -75,7 +76,7 @@ class AdVM extends BaseVM { List exploreAdsFilteredList = []; List myAdsFilteredList = []; List myAds = []; - List myReservedAdsRespModel = []; + List myReservedAds = []; List myActiveAdsForHome = []; List vehicleDamageCards = []; @@ -161,11 +162,12 @@ class AdVM extends BaseVM { List myAdsFilterOptions = []; populateAdsFilterList() async { + if (myAdsFilterOptions.isNotEmpty && exploreAdsFilterOptions.isNotEmpty) return; if (myAdsEnums.isEmpty) { - myAdsEnums = await commonRepo.getEnumTypeValues(enumTypeID: 18); //TODO: 18 is to get My Ad Filter Enums + myAdsEnums = await commonRepo.getEnumTypeValues(enumTypeID: 18); //18 is to get My Ad Filter Enums } if (exploreAdsEnums.isEmpty) { - exploreAdsEnums = await commonRepo.getEnumTypeValues(enumTypeID: 23); //TODO: 23 is to get Explore Ad Filter Enums + exploreAdsEnums = await commonRepo.getEnumTypeValues(enumTypeID: 23); // 23 is to get Explore Ad Filter Enums } exploreAdsFilterOptions.clear(); @@ -217,10 +219,8 @@ class AdVM extends BaseVM { return; } // this means if the filter is reserved ads - dynamic selectedIds = []; - if (index == 3 && adPostStatusEnum.getIdFromAdPostStatusEnum() == 9) { - selectedIds = myReservedAdsRespModel.map((component) => component.adsID).toList(); - myAdsFilteredList = myAds.where((element) => selectedIds.contains(element.id)).toList(); + if (adPostStatusEnum.getIdFromAdPostStatusEnum() == 9) { + myAdsFilteredList = myReservedAds; for (var ad in myAdsFilteredList) { ad.isReservedByMe = true; } @@ -241,12 +241,25 @@ class AdVM extends BaseVM { setState(ViewState.idle); } - Future getMyReservedAds() async { + Future> getMyReservedAds() async { setState(ViewState.busy); - //TODO: BREAKING - // myReservedAdsRespModel = await adsRepo.getMyReservedAds(); + myReservedAds = await adsRepo.getMyReservedAds(); isFetchingLists = false; setState(ViewState.idle); + return myReservedAds; + } + + Future createReserveAd({required int adId, required BuildContext context}) async { + Utils.showLoading(context); + GenericRespModel genericRespModel = await adsRepo.createReserveAd(adId: adId); + if (genericRespModel.messageStatus == null) { + Utils.hideLoading(context); + + Utils.showToast(genericRespModel.message ?? "Something went wrong!"); + return false; + } + Utils.hideLoading(context); + return true; } Future getExploreAds() async { @@ -316,9 +329,9 @@ class AdVM extends BaseVM { navigateReplaceWithName(context, AppRoutes.dashboard); } - Future cancelMyAdReservation(BuildContext context, {required int adId}) async { + Future cancelMyAdReservation(BuildContext context, {required int adId, required String reason}) async { Utils.showLoading(context); - GenericRespModel respModel = await adsRepo.cancelMyAdReservation(adId: adId); + GenericRespModel respModel = await adsRepo.cancelMyAdReservation(adId: adId, adsReserveStatus: 11, reason: reason); // 11 is to Cancel Reservation if (respModel.messageStatus != 1) { Utils.hideLoading(context); @@ -532,6 +545,22 @@ class AdVM extends BaseVM { vehicleDemandAmount = amount; } + String reservationCancelReason = ""; + String reservationCancelError = ""; + + void updateReservationCancelReason(String value) { + if (value.isNotEmpty) { + reservationCancelError = ""; + } + reservationCancelReason = value; + } + + String completeDealNotesForAdmin = ""; + + void updateCompleteDealNotesForAdmin(String value) { + completeDealNotesForAdmin = value; + } + String vehicleTitle = ""; void updateVehicleTitle(String title) { @@ -854,6 +883,17 @@ class AdVM extends BaseVM { return isValidated; } + bool validateReservationCancelReason() { + bool isValidated = true; + + if (reservationCancelReason.isEmpty) { + reservationCancelError = GlobalConsts.reservationCancelError; + isValidated = false; + notifyListeners(); + } + return isValidated; + } + bool isDamagePartsValidated() { bool isValidated = true; @@ -1034,6 +1074,26 @@ class AdVM extends BaseVM { notifyListeners(); } + List pickedReceiptPdfFiles = []; + + String receiptPdfFileError = ""; + + void removePdfFileFromList(String filePath) { + int index = pickedReceiptPdfFiles.indexWhere((element) => element.path == filePath); + if (index == -1) { + return; + } + pickedReceiptPdfFiles.removeAt(index); + notifyListeners(); + } + + void pickPdfReceiptFile(BuildContext context) async { + List? files = await commonServices.pickMultipleFiles(context); + pickedReceiptPdfFiles.addAll(files!); + if (pickedReceiptPdfFiles.isNotEmpty) receiptPdfFileError = ""; + notifyListeners(); + } + // sourceFlag for Camera = 0 // sourceFlag for Gallery = 1 void pickDamagePartImage(int index) async { diff --git a/lib/view_models/appointments_view_model.dart b/lib/view_models/appointments_view_model.dart index 118ccc6..e7c1c58 100644 --- a/lib/view_models/appointments_view_model.dart +++ b/lib/view_models/appointments_view_model.dart @@ -257,7 +257,7 @@ class AppointmentsVM extends BaseVM { List myAppointmentsEnum = []; populateAppointmentsFilterList() async { - appointmentsFilterOptions.clear(); + if (appointmentsFilterOptions.isNotEmpty) return; myAppointmentsEnum = await commonRepo.getEnumTypeValues(enumTypeID: 13); //TODO: 13 is to get Appointments Filter Enums @@ -502,7 +502,7 @@ class AppointmentsVM extends BaseVM { } populateBranchesFilterList() { - providersFilterOptions.clear(); + providersFilterOptions.clear(); // TODO: THIS SHOULD BE DYNAMIC AND FILTERS SHOULD COME FORM API providersFilterOptions = [ FilterListModel(title: "All Providers", isSelected: true, id: 0), FilterListModel(title: "Maintenance", isSelected: false, id: 1), diff --git a/lib/view_models/chat_view_model.dart b/lib/view_models/chat_view_model.dart index f3d58f3..cdd4859 100644 --- a/lib/view_models/chat_view_model.dart +++ b/lib/view_models/chat_view_model.dart @@ -37,6 +37,13 @@ class ChatVM extends ChangeNotifier { notifyListeners(); } + int latestOfferId = 0; + + updateLatestOfferId(var value) { + latestOfferId = value; + notifyListeners(); + } + List offerRejectModelList = [ OfferRequestCommentModel( index: 0, @@ -93,8 +100,8 @@ class ChatVM extends ChangeNotifier { return isValidated; } - Future onNewMessageReceived({required List messages, required BuildContext context}) async { - log("message I received in onNewMessageReceived ${messages.first.toString()}"); + Future onNewMessageReceived({required List messages, required BuildContext context, bool isMyOwnOffer = false}) async { + // log("message I received in onNewMessageReceived ${messages.first.toString()}"); if (AppState().currentAppType == AppType.customer) { for (var msg in messages) { int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == msg.senderUserID); @@ -104,12 +111,20 @@ class ChatVM extends ChangeNotifier { } } else { for (var msg in messages) { - int providerIndex = context.read().myFilteredRequests.indexWhere( - (element) => element.customerUserID == msg.receiverUserID, - ); // Where we need to call this function for saving a message in chat we will use receiverUserID and in those cases where received ID is null , it means it is a signal R call - log("here is it: $providerIndex"); - log("here is it: ${msg.receiverUserID}"); - log("here is it: ${context.read().myFilteredRequests.first.customerUserID.toString()}"); + // Where we need to call this function for saving a message in chat we will use receiverUserID and in those cases where received ID is null , it means it is a signal R call + int providerIndex = -1; + if (isMyOwnOffer) { + providerIndex = context.read().myFilteredRequests.indexWhere( + (element) => element.customerUserID == msg.receiverUserID, + ); + } else { + providerIndex = context.read().myFilteredRequests.indexWhere( + (element) => element.customerUserID == msg.senderUserID, + ); + } + // log("here is it: $providerIndex"); + // log("here is it: ${msg.senderUserID}"); + // log("here is it: ${context.read().myFilteredRequests.first.customerUserID.toString()}"); if (providerIndex != -1) { context.read().addChatMessagesInRequestsModel(msg: msg, index: providerIndex); } @@ -199,6 +214,14 @@ class ChatVM extends ChangeNotifier { final userId = AppState().getUser.data!.userInfo!.userId.toString(); final name = AppState().getUser.data!.userInfo!.firstName.toString(); + log("I have saved this: ${({ + "ReceiverUserID": receiverId, + "SenderUserID": userId, + "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(), + "ChatText": message, + "RequestID": requestId, + "ReqOfferID": latestOfferId, + }).toString()}"); hubConnection!.invoke( "SendMessageRequestOffer", args: [ @@ -208,7 +231,7 @@ class ChatVM extends ChangeNotifier { "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(), "ChatText": message, "RequestID": requestId, - "ReqOfferID": 0, + "ReqOfferID": latestOfferId, } ], ).catchError((e) { @@ -272,6 +295,14 @@ class ChatVM extends ChangeNotifier { Utils.showLoading(context); List chatMessages = await chatRepo.getUsersChatMessages(providerId: providerId, customerId: customerId, requestOfferId: requestOfferId, requestId: requestId); serviceProviderOffersList[providerOfferIndex].chatMessages = chatMessages; + if (serviceProviderOffersList[providerOfferIndex].chatMessages != null) { + for (var message in serviceProviderOffersList[providerOfferIndex].chatMessages!) { + if (message.chatMessageTypeEnum == ChatMessageTypeEnum.offer) { + updateLatestOfferId(message.reqOfferID ?? 0); + log("latestOfferId: $latestOfferId"); + } + } + } Utils.hideLoading(context); notifyListeners(); } catch (e) { diff --git a/lib/view_models/payment_view_model.dart b/lib/view_models/payment_view_model.dart index c3f9f9d..7e1ea9d 100644 --- a/lib/view_models/payment_view_model.dart +++ b/lib/view_models/payment_view_model.dart @@ -194,7 +194,7 @@ class PaymentVM extends ChangeNotifier { // TODO: Handle this case. break; case PaymentTypes.subscription: - // TODO: Handle this case. + await placeThePayment(context: context, paymentTypeEnum: paymentType); break; case PaymentTypes.ads: case PaymentTypes.adReserve: diff --git a/lib/view_models/requests_view_model.dart b/lib/view_models/requests_view_model.dart index 1774784..56a164b 100644 --- a/lib/view_models/requests_view_model.dart +++ b/lib/view_models/requests_view_model.dart @@ -40,7 +40,8 @@ class RequestsVM extends BaseVM { List myRequestsTypeEnum = []; populateRequestsFilterList() async { - requestsTypeFilterOptions.clear(); + if (requestsTypeFilterOptions.isNotEmpty) return; + if (myRequestsTypeEnum.isEmpty) { myRequestsTypeEnum = await commonRepo.getEnumTypeValues(enumTypeID: 16); //TODO: 16 is to get Requests Filter Enums } @@ -445,7 +446,7 @@ class RequestsVM extends BaseVM { comment: message, offerStatusText: "", )); - context.read().onNewMessageReceived(messages: [chatMessageModel], context: context); + context.read().onNewMessageReceived(messages: [chatMessageModel], context: context, isMyOwnOffer: true); if (!isFromChatScreen) { ChatViewArguments chatViewArguments = ChatViewArguments( chatTypeEnum: ChatTypeEnum.requestOffer, diff --git a/lib/views/advertisement/ad_creation_steps/ad_duration_container.dart b/lib/views/advertisement/ad_creation_steps/ad_duration_container.dart index 139ba00..ba0ef7b 100644 --- a/lib/views/advertisement/ad_creation_steps/ad_duration_container.dart +++ b/lib/views/advertisement/ad_creation_steps/ad_duration_container.dart @@ -139,11 +139,7 @@ class AdDuration extends StatelessWidget { children: [ "Contact Details".toText(fontSize: 18, isBold: true), 8.height, - Row( - children: [ - "Do you want to show your number to the buyers?".toText(fontSize: 14), - ], - ), + "Do you want to show your number to the buyers?".toText(fontSize: 14), 6.height, Container( width: 38, @@ -215,7 +211,7 @@ class AdDuration extends StatelessWidget { ), ), const SizedBox(width: 10), - "Is this number registered on WhatsApp?".toString().toText(fontSize: 14) + "Is this number registered on WhatsApp?".toString().toText() ], ), ], diff --git a/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart b/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart index e256a9e..a939613 100644 --- a/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart +++ b/lib/views/advertisement/ad_creation_steps/ad_review_containers.dart @@ -112,11 +112,11 @@ class VehicleDetailsReview extends StatelessWidget { "Vehicle Pictures:".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), if (adVM.pickedVehicleImages.isNotEmpty) ...[ // 10.height, - PickedImagesContainer( - pickedImages: adVM.pickedVehicleImages, + PickedFilesContainer( + pickedFiles: adVM.pickedVehicleImages, onCrossPressedPrimary: adVM.removeImageFromList, isReview: true, - onAddImagePressed: () {}, + onAddFilePressed: () {}, ), ], ], @@ -141,11 +141,11 @@ class DamagePartsReview extends StatelessWidget { ), if (adVM.vehicleDamageCards[index].partImages != null && adVM.vehicleDamageCards[index].partImages!.isNotEmpty) ...[ "Damage Part Pictures:".toText(fontSize: 12, color: MyColors.lightTextColor, isBold: true), - PickedImagesContainer( - pickedImages: adVM.vehicleDamageCards[index].partImages!, + PickedFilesContainer( + pickedFiles: adVM.vehicleDamageCards[index].partImages!, onCrossPressedPrimary: adVM.removeImageFromList, isReview: true, - onAddImagePressed: () {}, + onAddFilePressed: () {}, ), ], if (index != adVM.vehicleDamageCards.length - 1) const Divider(thickness: 2), diff --git a/lib/views/advertisement/ad_creation_steps/damage_parts_container.dart b/lib/views/advertisement/ad_creation_steps/damage_parts_container.dart index 73912f4..a71a318 100644 --- a/lib/views/advertisement/ad_creation_steps/damage_parts_container.dart +++ b/lib/views/advertisement/ad_creation_steps/damage_parts_container.dart @@ -101,13 +101,13 @@ class DamageParts extends StatelessWidget { // }), // 8.height, // vehicleDamageCard.partImages != null ? - PickedImagesContainer( - pickedImages: vehicleDamageCard.partImages ?? [], + PickedFilesContainer( + pickedFiles: vehicleDamageCard.partImages ?? [], onCrossPressedSecondary: (imageIndex, filePath) { adVM.removeDamageImageFromCard(imageIndex, filePath, index); }, index: index, - onAddImagePressed: () { + onAddFilePressed: () { context.read().pickDamagePartImage(index); }, ), diff --git a/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart b/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart index 8b0b247..e8c4a33 100644 --- a/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart +++ b/lib/views/advertisement/ad_creation_steps/vehicle_details_container.dart @@ -278,10 +278,10 @@ class VehicleDetails extends StatelessWidget { ], if (adVM.pickedVehicleImages.isNotEmpty) ...[ 16.height, - PickedImagesContainer( - pickedImages: adVM.pickedVehicleImages, + PickedFilesContainer( + pickedFiles: adVM.pickedVehicleImages, onCrossPressedPrimary: adVM.removeImageFromList, - onAddImagePressed: () { + onAddFilePressed: () { context.read().pickMultipleImages(); }, ), diff --git a/lib/views/advertisement/ads_detail_view.dart b/lib/views/advertisement/ads_detail_view.dart index 959eb15..0698ffa 100644 --- a/lib/views/advertisement/ads_detail_view.dart +++ b/lib/views/advertisement/ads_detail_view.dart @@ -7,6 +7,7 @@ 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/advertisment_models/ads_bank_details_model.dart'; import 'package:mc_common_app/models/advertisment_models/special_service_model.dart'; import 'package:mc_common_app/models/general_models/widgets_models.dart'; import 'package:mc_common_app/theme/colors.dart'; @@ -18,6 +19,8 @@ import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:mc_common_app/view_models/payment_view_model.dart'; import 'package:mc_common_app/views/advertisement/ad_duration_selection_sheet_content.dart'; import 'package:mc_common_app/views/advertisement/ads_images_slider.dart'; +import 'package:mc_common_app/views/advertisement/custom_add_button.dart'; +import 'package:mc_common_app/views/advertisement/picked_images_container.dart'; import 'package:mc_common_app/views/appointments/widgets/custom_calender_widget.dart'; import 'package:mc_common_app/widgets/bottom_sheet.dart'; import 'package:mc_common_app/widgets/button/show_fill_button.dart'; @@ -41,7 +44,7 @@ class AdsDetailView extends StatefulWidget { class _AdsDetailViewState extends State { @override void initState() { - log("ad: ${widget.adDetails.id}"); + log("ad: ${widget.adDetails.adReserveStatus}"); scheduleMicrotask(() { onAdDetailsLoaded(); }); @@ -298,7 +301,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Reservation Amounts".toText(fontSize: 16, isBold: true), + "Reservation Amount".toText(fontSize: 16, isBold: true), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -360,7 +363,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { ], ), 12.height, - "Special service charges will be added based on desired insurance and delivery Location".toText(fontSize: 12), + "Special service charges will be added based on desired insurance and delivery Location".toText(fontSize: 12, maxLines: 2), 30.height, Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -383,7 +386,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { "Estimated".toText(fontSize: 10, isBold: true), ], ), - 44.height, + 30.height, Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -393,7 +396,7 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { size: 19, ).paddingOnly(bottom: 2), 3.width, - "Some services are mandatory while reserving the Ad.".toText( + "Some services are mandatory while reserving Ad.".toText( color: MyColors.adPendingStatusColor, fontSize: 12, isItalic: true, @@ -407,9 +410,12 @@ class BuildAdDetailsActionButtonForExploreAds extends StatelessWidget { child: ShowFillButton( maxHeight: 55, title: "Complete Reservation", - onPressed: () { - Navigator.pop(context); - navigateWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.adReserve); + onPressed: () async { + // Navigator.pop(context); + bool status = await context.read().createReserveAd(adId: adDetailsModel.id!, context: context); + if (status) { + navigateReplaceWithName(context, AppRoutes.paymentMethodsView, arguments: PaymentTypes.adReserve); + } }, ), ), @@ -902,63 +908,69 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { title: "Cancel Reservation".toText(fontSize: 28, isBold: true, letterSpacing: -1.44), description: Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( + child: Consumer( + builder: (BuildContext context, AdVM adVM, Widget? child) { + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 12.height, - TxtField( - maxLines: 5, - value: "", - errorValue: "", - keyboardType: TextInputType.text, - hint: "Reason for cancellation", - onChanged: (v) => () {}, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 12.height, + TxtField( + maxLines: 5, + value: adVM.reservationCancelReason, + errorValue: adVM.reservationCancelError, + keyboardType: TextInputType.text, + hint: "Reason for cancellation", + onChanged: (v) => adVM.updateReservationCancelReason(v), + ), + ], ), + 25.height, + ShowFillButton( + title: "Submit", + onPressed: () { + bool status = adVM.validateReservationCancelReason(); + if (status) { + Navigator.pop(context); + return actionConfirmationBottomSheet( + context: context, + title: "Do you want to cancel the reservation?".toText(fontSize: 28, isBold: true, letterSpacing: -1.44), + subtitle: "Your ad reservation will be cancelled and this ad will be again visible to everyone to buy.", + actionButtonYes: Expanded( + child: ShowFillButton( + maxHeight: 55, + title: "Yes", + fontSize: 15, + onPressed: () { + Navigator.pop(context); + adVM.cancelMyAdReservation(context, adId: adDetails.id!, reason: ""); + }, + ), + ), + actionButtonNo: Expanded( + child: ShowFillButton( + maxHeight: 55, + isFilled: false, + borderColor: MyColors.darkPrimaryColor, + title: "No", + txtColor: MyColors.darkPrimaryColor, + fontSize: 15, + onPressed: () { + Navigator.pop(context); + }, + ), + ), + ); + } + }, + maxWidth: double.infinity, + ), + 19.height, ], - ), - 25.height, - ShowFillButton( - title: "Submit", - onPressed: () { - Navigator.pop(context); - AdVM adVM = context.read(); - return actionConfirmationBottomSheet( - context: context, - title: "Do you want to cancel the reservation?".toText(fontSize: 28, isBold: true, letterSpacing: -1.44), - subtitle: "Your ad reservation will be cancelled and this ad will be again visible to everyone to buy.", - actionButtonYes: Expanded( - child: ShowFillButton( - maxHeight: 55, - title: "Yes", - fontSize: 15, - onPressed: () { - Navigator.pop(context); - adVM.cancelMyAdReservation(context, adId: adDetails.id!); - }, - ), - ), - actionButtonNo: Expanded( - child: ShowFillButton( - maxHeight: 55, - isFilled: false, - borderColor: MyColors.darkPrimaryColor, - title: "No", - txtColor: MyColors.darkPrimaryColor, - fontSize: 15, - onPressed: () { - Navigator.pop(context); - }, - ), - ), - ); - }, - maxWidth: double.infinity, - ), - 19.height, - ], + ); + }, ), )); }, @@ -984,6 +996,104 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { ); } + Widget completeDealAction(BuildContext context, {required AdDetailsModel adDetails}) { + return Row( + children: [ + Expanded( + child: ShowFillButton( + maxHeight: 55, + backgroundColor: MyColors.darkPrimaryColor, + txtColor: MyColors.white, + isBold: false, + title: "Complete Deal", + onPressed: () { + buildCompleteDealBottomSheet(context, adDetails: adDetails); + }), + ), + ], + ); + } + + Future buildCompleteDealBottomSheet(BuildContext context, {required AdDetailsModel adDetails}) { + return showModalBottomSheet( + context: context, + isScrollControlled: true, + enableDrag: true, + builder: (BuildContext context) { + return InfoBottomSheet( + title: "Upload Bank Receipt".toText(fontSize: 26, isBold: true, letterSpacing: -1.44), + description: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Consumer( + builder: (BuildContext context, AdVM adVM, Widget? child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 12.height, + TxtField( + maxLines: 4, + value: adVM.completeDealNotesForAdmin, + errorValue: "", + keyboardType: TextInputType.text, + hint: "Notes For Admin", + onChanged: (v) => adVM.updateCompleteDealNotesForAdmin(v), + ), + ], + ), + 15.height, + "Attach File".toText(fontSize: 20, isBold: true, letterSpacing: -0.5), + if (adVM.pickedReceiptPdfFiles.isNotEmpty) ...[ + 16.height, + PickedFilesContainer( + pickedFiles: adVM.pickedReceiptPdfFiles, + onCrossPressedPrimary: adVM.removePdfFileFromList, + onAddFilePressed: () { + context.read().pickPdfReceiptFile(context); + }, + isPdf: true, + ), + ] else ...[ + Row( + children: [ + Container( + height: 90, + width: 90, + decoration: BoxDecoration(color: MyColors.greyButtonColor, border: Border.all(width: 2, color: MyColors.greyAddBorderColor)), + margin: const EdgeInsets.all(8), + alignment: Alignment.center, + child: Container( + height: 24, + width: 24, + decoration: const BoxDecoration(shape: BoxShape.circle, color: MyColors.darkTextColor), + child: const Icon(Icons.add, color: MyColors.white), + ), + ).onPress(() { + context.read().pickPdfReceiptFile(context); + }), + ], + ), + ], + 15.height, + ShowFillButton( + title: "Submit", + onPressed: () { + //Upload Attachment + }, + maxWidth: double.infinity, + ), + 19.height, + ], + ); + }, + ), + )); + }, + ); + } + Widget expiredAdAction(BuildContext context) { return Row( children: [ @@ -1037,6 +1147,37 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { ); } + Widget reservedAdActions(context, {required AdDetailsModel adDetailsModel, required AdVM adVM}) { + switch (adDetailsModel.adReserveStatus) { + case AdReserveStatus.defaultStatus: + return pendingForReviewAction(pendingText: "Waiting for Admins Response"); + + case AdReserveStatus.reserved: + return cancelReservationAction(context, adDetails: adDetailsModel); + + case AdReserveStatus.cancelledByOwner: + return pendingForReviewAction(pendingText: "Cancelled by Owner"); + + case AdReserveStatus.cancelledByAdmin: + return pendingForReviewAction(pendingText: "Cancelled by Admin"); + + case AdReserveStatus.timeOver: + return pendingForReviewAction(pendingText: "Reservation Time Over"); + + case AdReserveStatus.dealDone: + if (adVM.adsBankDetailsModel != null) { + return completeDealAction(context, adDetails: adDetailsModel); + } + return pendingForReviewAction(pendingText: "Waiting for Admins Response"); + + case AdReserveStatus.fullPaymentVerified: + return pendingForReviewAction(pendingText: "Payment Verified"); + + default: + return pendingForReviewAction(pendingText: "Waiting for Admins Response"); + } + } + @override Widget build(BuildContext context) { switch (adDetailsModel.adPostStatus!) { @@ -1045,7 +1186,13 @@ class BuildAdDetailsActionButtonForMyAds extends StatelessWidget { case AdPostStatus.active: return markAsSoldAction(context); case AdPostStatus.reserved: - return cancelReservationAction(context, adDetails: adDetailsModel); + AdVM adVM = context.watch(); + if (adVM.state == ViewState.busy) { + return const CircularProgressIndicator(); + } else { + reservedAdActions(context, adDetailsModel: adDetailsModel, adVM: adVM); + } + break; case AdPostStatus.buyingService: case AdPostStatus.reserveCancel: case AdPostStatus.rejected: diff --git a/lib/views/advertisement/picked_images_container.dart b/lib/views/advertisement/picked_images_container.dart index 257bec8..3971938 100644 --- a/lib/views/advertisement/picked_images_container.dart +++ b/lib/views/advertisement/picked_images_container.dart @@ -5,22 +5,24 @@ import 'package:mc_common_app/classes/consts.dart'; import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; -class PickedImagesContainer extends StatelessWidget { - final List pickedImages; +class PickedFilesContainer extends StatelessWidget { + final List pickedFiles; final Function(String filePath)? onCrossPressedPrimary; final Function(int index, String filePath)? onCrossPressedSecondary; final int? index; final bool isReview; - final Function() onAddImagePressed; + final bool isPdf; + final Function() onAddFilePressed; - const PickedImagesContainer({ + const PickedFilesContainer({ Key? key, - required this.pickedImages, + required this.pickedFiles, this.onCrossPressedPrimary, this.onCrossPressedSecondary, this.index, - required this.onAddImagePressed, + required this.onAddFilePressed, this.isReview = false, + this.isPdf = false, }) : super(key: key); @override @@ -32,11 +34,11 @@ class PickedImagesContainer extends StatelessWidget { crossAxisSpacing: 4.0, mainAxisSpacing: 8.0, children: List.generate( - isReview ? pickedImages.length : pickedImages.length + 1, + isReview ? pickedFiles.length : pickedFiles.length + 1, (index) { - if (index == pickedImages.length && !isReview) { + if (index == pickedFiles.length && !isReview) { return Container( - decoration: BoxDecoration( color: MyColors.greyButtonColor, border: Border.all(width: 2, color: MyColors.greyAddBorderColor)), + decoration: BoxDecoration(color: MyColors.greyButtonColor, border: Border.all(width: 2, color: MyColors.greyAddBorderColor)), margin: const EdgeInsets.all(8), alignment: Alignment.center, child: Container( @@ -45,17 +47,17 @@ class PickedImagesContainer extends StatelessWidget { decoration: const BoxDecoration(shape: BoxShape.circle, color: MyColors.darkTextColor), child: const Icon(Icons.add, color: MyColors.white), ), - ).onPress(onAddImagePressed); + ).onPress(onAddFilePressed); } return Center( - child: BuildImageContainer( - file: pickedImages[index], - onCrossPressedPrimary: onCrossPressedPrimary, - onCrossPressedSecondary: onCrossPressedSecondary, - index: index, - isReview: isReview, - ), - ); + child: BuildFilesContainer( + file: pickedFiles[index], + onCrossPressedPrimary: onCrossPressedPrimary, + onCrossPressedSecondary: onCrossPressedSecondary, + index: index, + isReview: isReview, + isPdf: isPdf, + )); }, ), ); @@ -73,37 +75,51 @@ class PickedImagesContainer extends StatelessWidget { } } -class BuildImageContainer extends StatelessWidget { +class BuildFilesContainer extends StatelessWidget { final File file; final Function(String filePath)? onCrossPressedPrimary; final Function(int index, String filePath)? onCrossPressedSecondary; final int? index; final bool isReview; + final bool isPdf; - const BuildImageContainer({ + const BuildFilesContainer({ Key? key, required this.file, this.onCrossPressedPrimary, this.onCrossPressedSecondary, this.index, this.isReview = false, + this.isPdf = false, }) : super(key: key); @override Widget build(BuildContext context) { return Stack( children: [ - Container( + SizedBox( height: 90, width: 90, child: Stack( children: [ - Image.file( - file, - fit: BoxFit.fill, - height: 72, - width: 70, - ).paddingAll(8), + isPdf + ? Container( + height: 72, + width: 70, + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + border: Border.all( + width: 2, + color: MyColors.darkPrimaryColor, + )), + child: const Icon(Icons.picture_as_pdf).paddingAll(8), + ) + : Image.file( + file, + fit: BoxFit.fill, + height: 72, + width: 70, + ).paddingAll(8), !isReview ? Align( alignment: Alignment.topRight, diff --git a/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart b/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart index b086514..e1c5e43 100644 --- a/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart +++ b/lib/views/appointments/widgets/appointment_service_pick_bottom_sheet.dart @@ -136,7 +136,7 @@ class AppointmentServicePickBottomSheet extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Icon( + const Icon( Icons.warning, color: MyColors.adPendingStatusColor, size: 19, @@ -163,7 +163,7 @@ class AppointmentServicePickBottomSheet extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - // TODO: This Price will be decided according to the service selected + // TODO: This Price will be decided according to the service selected, We will calculate the KMs and multiple it with price per KMs 150.toString().toText(fontSize: 30, isBold: true), "SAR".toText(fontSize: 15, isBold: true, color: MyColors.lightTextColor).paddingOnly(bottom: 5), ], diff --git a/lib/views/chat/chat_view.dart b/lib/views/chat/chat_view.dart index ce0a20e..45e9de4 100644 --- a/lib/views/chat/chat_view.dart +++ b/lib/views/chat/chat_view.dart @@ -361,12 +361,8 @@ class _ChatMessageCustomWidgetState extends State { @override Widget build(BuildContext context) { - log("here message type : ${widget.chatMessageModel.chatMessageTypeEnum}"); - log("here message type : ${widget.chatMessageModel.chatText}"); - return Directionality( textDirection: (widget.chatMessageModel.isMyMessage ?? false) ? TextDirection.rtl : TextDirection.ltr, - // textDirection: (widget.chatMessageModel.isMyMessage ?? false) ? TextDirection.rtl : TextDirection.ltr, child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/views/profile/profile_view.dart b/lib/views/profile/profile_view.dart new file mode 100644 index 0000000..5cc7ef1 --- /dev/null +++ b/lib/views/profile/profile_view.dart @@ -0,0 +1,270 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:image_picker/image_picker.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/theme/colors.dart'; +import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/utils/utils.dart'; +import 'package:mc_common_app/views/setting_options/widgets/custom_setting_options_tile.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; + +class ProfileScreen extends StatelessWidget { + const ProfileScreen({Key? key}) : super(key: key); + + // final ImagePicker _picker = ImagePicker(); + + Widget showItem({required String icon, required String title, required VoidCallback onTap, String? subTitle}) { + return InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 16, + height: 16, + child: SvgPicture.asset( + icon, + color: subTitle == null ? MyColors.black : MyColors.primaryColor, + ), + ), + 12.width, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + title.toText(isBold: true, fontSize: 14), + if (subTitle != null) subTitle.toText(fontSize: 14, color: MyColors.primaryColor), + ], + ), + ), + const Icon( + Icons.arrow_forward, + size: 16, + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + extendBody: true, + backgroundColor: const Color(0xffefefef), + body: Stack( + children: [ + Column( + children: [ + Expanded( + flex: 3, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(MyAssets.icLogoWhitePng), + fit: BoxFit.cover, + ), + ), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 7.0, sigmaY: 7.0), + child: Container( + // height:, + color: Colors.white.withOpacity(0.0), + ), + ), + ), + ), + Expanded( + flex: 8, + child: Container( + width: double.infinity, + color: Colors.white, + child: ListView( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + height: 90, + alignment: Alignment.centerLeft, + child: ClipOval( + child: Image.asset( + MyAssets.carBanner, + width: 90, + height: 90, + fit: BoxFit.fill, + ), + )), + Container( + height: 35, + width: 35, + decoration: BoxDecoration(color: MyColors.white, shape: BoxShape.circle, border: Border.all(color: MyColors.darkTextColor, width: 0.1)), + child: const Icon(Icons.edit_note, color: MyColors.darkIconColor, size: 27).paddingOnly(left: 5), + ).onPress(() {}) + ], + ).horPaddingMain(), + 10.height, + "Muhammad Ahmad".toText(fontSize: 20).paddingOnly(left: 25), + Column( + children: [ + CustomProfileOptionsTile( + titleText: "Country", + subtitleText: "Saudi Arabia", + needBorderBelow: true, + onTap: () {}, + ), + CustomProfileOptionsTile( + titleText: "Email", + subtitleText: "mail@gmail.com", + needBorderBelow: true, + onTap: () {}, + ), + CustomProfileOptionsTile( + titleText: "Phone Number", + subtitleText: "0504278212", + needBorderBelow: true, + onTap: () {}, + ), + CustomProfileOptionsTile( + titleText: "Password", + subtitleText: "************", + onTap: () {}, + ), + ], + ).toContainer(width: double.infinity, isShadowEnabled: true, paddingAll: 10, margin: const EdgeInsets.fromLTRB(24, 24, 24, 0), borderRadius: 0), + ], + ), + ), + ), + ], + ), + + CircleAvatar( + radius: 20, + backgroundColor: Colors.white, + child: const Icon(Icons.arrow_back_ios_rounded, color: MyColors.darkIconColor, size: 18).paddingOnly(right: 4), + ).onPress(() { + Navigator.pop(context); + }).paddingOnly(left: 21, right: 21, top: 50), + + // SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.start, + // children: [ + // CircleAvatar( + // radius: 20, + // backgroundColor: Colors.white, + // child: const Icon(Icons.arrow_back_ios_rounded, color: MyColors.darkIconColor, size: 18).paddingOnly(right: 4), + // ).onPress(() { + // Navigator.pop(context); + // }), + // ], + // ).paddingOnly(left: 21, right: 21, top: 50), + // Stack( + // children: [ + // Column( + // children: [ + // showItem( + // icon: MyAssets.icEmail, + // title: "Country", + // onTap: () { + // navigateWithName(context, AppRoutes.changeEmailPage); + // }, + // ), + // const Divider( + // height: 1, + // ), + // showItem( + // icon: MyAssets.icPassword, + // title: "Country", + // onTap: () { + // navigateWithName(context, AppRoutes.changePassword); + // }, + // ), + // const Divider( + // height: 1, + // ), + // showItem( + // icon: MyAssets.icPhoneNumber, + // title: "Country", + // onTap: () { + // navigateWithName(context, AppRoutes.changeMobilePage); + // }, + // ), + // const Divider( + // height: 1, + // ), + // ], + // ).toWhiteContainer(width: double.infinity, pading: const EdgeInsets.symmetric(horizontal: 21)), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Container( + // height: 68, + // alignment: Alignment.centerLeft, + // child: ClipOval( + // child: Image.asset( + // MyAssets.carBanner, + // width: 68, + // height: 68, + // fit: BoxFit.fill, + // ), + // )), + // InkWell( + // onTap: () {}, + // child: Container( + // padding: const EdgeInsets.only(left: 17, right: 17, top: 8, bottom: 8), + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(30), + // color: Colors.black.withOpacity(.21), + // ), + // child: Row( + // children: [ + // const Icon(Icons.photo, color: Colors.white, size: 16), + // 4.width, + // "edit".toText(fontSize: 12, color: Colors.white), + // ], + // ), + // ), + // ), + // ], + // ), + // ], + // ).horPaddingMain(), + // ], + // ), + // ), + // Container( + // height: MediaQuery.of(context).size.height, + // width: MediaQuery.of(context).size.width, + // color: Colors.white, + // child: Column( + // children: [ + // CustomSettingOptionsTile( + // leadingWidget: const Icon(Icons.person, size: 20), titleText: "Invite Friends", needBorderBelow: true, onTap: () => navigateWithName(context, AppRoutes.myRequestsPage)), + // CustomSettingOptionsTile( + // leadingWidget: const Icon(Icons.help, size: 20), titleText: "Help", needBorderBelow: true, onTap: () => navigateWithName(context, AppRoutes.settingOptionsFaqs)), + // CustomSettingOptionsTile(leadingWidget: const Icon(Icons.person, size: 20), titleText: "Account", onTap: () => navigateWithName(context, AppRoutes.profileView)), + // ], + // ).toContainer(width: double.infinity, isShadowEnabled: true, paddingAll: 10, borderRadius: 0), + // ), + ], + ), + ); + } +} + +// + +// diff --git a/lib/views/requests/create_request_page.dart b/lib/views/requests/create_request_page.dart index 43bc84c..54acc45 100644 --- a/lib/views/requests/create_request_page.dart +++ b/lib/views/requests/create_request_page.dart @@ -192,10 +192,10 @@ class CreateRequestPage extends StatelessWidget { ], if (requestsVM.pickedVehicleImages.isNotEmpty) ...[ 16.height, - PickedImagesContainer( - pickedImages: requestsVM.pickedVehicleImages, + PickedFilesContainer( + pickedFiles: requestsVM.pickedVehicleImages, onCrossPressedPrimary: requestsVM.removeImageFromList, - onAddImagePressed: () { + onAddFilePressed: () { context.read().pickMultipleImages(); }, ), diff --git a/lib/views/requests/offer_list_page.dart b/lib/views/requests/offer_list_page.dart index 6b4652b..40c1a16 100644 --- a/lib/views/requests/offer_list_page.dart +++ b/lib/views/requests/offer_list_page.dart @@ -6,7 +6,6 @@ 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_models/provider_offers_model.dart'; -import 'package:mc_common_app/models/requests_models/request_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'; @@ -26,7 +25,7 @@ class OfferListPage extends StatelessWidget { return Scaffold( appBar: const CustomAppBar(title: "Offers"), body: serviceProviderOffers.isEmpty - ? Center(child: "No Requests to show.".toText(fontSize: 16, color: MyColors.lightTextColor)) + ? Center(child: "No Offers to show.".toText(fontSize: 16, color: MyColors.lightTextColor)) : ListView.separated( itemCount: serviceProviderOffers.length, padding: const EdgeInsets.all(16), diff --git a/lib/views/requests/widget/request_item.dart b/lib/views/requests/widget/request_item.dart index dc2fb1f..e5cf4c1 100644 --- a/lib/views/requests/widget/request_item.dart +++ b/lib/views/requests/widget/request_item.dart @@ -81,7 +81,7 @@ class RequestItem extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - request.price.toString().toText( + request.price.toInt().toString().toText( fontSize: 20, color: Colors.black, isBold: true, diff --git a/lib/views/setting_options/setting_options_invite_friends.dart b/lib/views/setting_options/setting_options_invite_friends.dart index 91dfea3..cb9512f 100644 --- a/lib/views/setting_options/setting_options_invite_friends.dart +++ b/lib/views/setting_options/setting_options_invite_friends.dart @@ -2,9 +2,7 @@ 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/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/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; @@ -28,10 +26,22 @@ class SettingOptionsInviteFriends extends StatelessWidget { Column( children: [ CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.person, size: 20), titleText: "Invite Friends", needBorderBelow: true, onTap: () => navigateWithName(context, AppRoutes.myRequestsPage)), + leadingWidget: const Icon(Icons.person, size: 20), + titleText: "Invite Friends", + needBorderBelow: true, + onTap: () {}, + ), CustomSettingOptionsTile( - leadingWidget: const Icon(Icons.help, size: 20), titleText: "Help", needBorderBelow: true, onTap: () => navigateWithName(context, AppRoutes.settingOptionsFaqs)), - CustomSettingOptionsTile(leadingWidget: const Icon(Icons.person, size: 20), titleText: "Account", onTap: () {}), + leadingWidget: const Icon(Icons.help, size: 20), + titleText: "Help", + needBorderBelow: true, + onTap: () => navigateWithName(context, AppRoutes.settingOptionsFaqs), + ), + CustomSettingOptionsTile( + leadingWidget: const Icon(Icons.person, size: 20), + titleText: "Account", + onTap: () => navigateWithName(context, AppRoutes.profileView), + ), ], ).toContainer(width: double.infinity, isShadowEnabled: true, paddingAll: 10, margin: const EdgeInsets.fromLTRB(24, 24, 24, 0), borderRadius: 0), ], diff --git a/lib/views/setting_options/setting_options_language.dart b/lib/views/setting_options/setting_options_language.dart index ed29f70..cc4ea4f 100644 --- a/lib/views/setting_options/setting_options_language.dart +++ b/lib/views/setting_options/setting_options_language.dart @@ -6,6 +6,7 @@ import 'package:mc_common_app/views/setting_options/widgets/custom_setting_optio 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 SettingOptionsLanguage extends StatelessWidget { const SettingOptionsLanguage({super.key}); @@ -26,7 +27,15 @@ class SettingOptionsLanguage extends StatelessWidget { children: [ Column( children: [ - CustomSettingOptionsTile(leadingWidget: const Icon(Icons.person, size: 20), titleText: "My Requests", needBorderBelow: true, onTap: () {}), + CustomSettingOptionsTile( + leadingWidget: const Icon(Icons.person, size: 20), + titleText: "My Requests", + needBorderBelow: true, + onTap: () { + context.read + Navigator.pop(context); + Navigator.pop(context); + }), CustomSettingOptionsTile(leadingWidget: const Icon(Icons.favorite, size: 20), titleText: "Favorite list", needBorderBelow: true, onTap: () {}), CustomSettingOptionsTile(leadingWidget: const Icon(Icons.settings, size: 20), titleText: "Settings", onTap: () => navigateWithName(context, AppRoutes.settingOptionsInviteFriends)), ], diff --git a/lib/views/setting_options/widgets/custom_setting_options_tile.dart b/lib/views/setting_options/widgets/custom_setting_options_tile.dart index b417c7f..2553d44 100644 --- a/lib/views/setting_options/widgets/custom_setting_options_tile.dart +++ b/lib/views/setting_options/widgets/custom_setting_options_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; class CustomSettingOptionsTile extends StatelessWidget { @@ -37,3 +38,37 @@ class CustomSettingOptionsTile extends StatelessWidget { ); } } + +class CustomProfileOptionsTile extends StatelessWidget { + final String titleText; + final bool needBorderBelow; + final Function() onTap; + final String subtitleText; + + const CustomProfileOptionsTile({super.key, required this.onTap, required this.titleText, required this.subtitleText, this.needBorderBelow = false}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + titleText.toText(fontSize: 16), + subtitleText.toText(fontSize: 12, color: MyColors.darkTextColor.withOpacity(0.7)), + ], + ), + const Icon(Icons.edit_note, size: 25), + ], + ).onPress(onTap), + 5.height, + if (needBorderBelow) ...[ + const Divider(thickness: 1), + ], + ], + ); + } +} diff --git a/lib/views/user/login_with_password_page.dart b/lib/views/user/login_with_password_page.dart index 8b6c525..19e484c 100644 --- a/lib/views/user/login_with_password_page.dart +++ b/lib/views/user/login_with_password_page.dart @@ -33,7 +33,8 @@ class _LoginWithPasswordState extends State { ClassType type = ClassType.EMAIL; //TODO: ONLY FOR DEVELOPMENT PURPOSE - String phoneNum = "966504278213", password = "Fa@1234"; + // String phoneNum = "966504278213", password = "Fa@1234"; + String phoneNum = "966504278214", password = "Fa@1234"; String email = ""; String countryCode = ""; Country? _country; diff --git a/lib/widgets/common_widgets/confirm_dialog.dart b/lib/widgets/common_widgets/confirm_dialog.dart new file mode 100644 index 0000000..1233320 --- /dev/null +++ b/lib/widgets/common_widgets/confirm_dialog.dart @@ -0,0 +1,67 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:mc_common_app/extensions/int_extensions.dart'; +import 'package:mc_common_app/extensions/string_extensions.dart'; +import 'package:mc_common_app/generated/locale_keys.g.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/widgets/button/show_fill_button.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; + +class ConfirmDialog extends StatelessWidget { + final String? title; + final String message; + final String? okTitle; + final VoidCallback? onTap; + final VoidCallback? onCloseTap; + + const ConfirmDialog({Key? key, this.title, required this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Colors.white, + shape: const RoundedRectangleBorder(), + insetPadding: const EdgeInsets.only(left: 21, right: 21), + child: Padding( + padding: const EdgeInsets.only(left: 20, right: 20, top: 18, bottom: 28), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + title ?? LocaleKeys.confirm.tr(), + style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: MyColors.darkTextColor, height: 35 / 24, letterSpacing: -0.96), + ).paddingOnly(top: 16), + ), + IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.close), + color: MyColors.darkTextColor, + constraints: const BoxConstraints(), + onPressed: () => onCloseTap ?? Navigator.pop(context), + // onPressed: () => Navigator.pop(context), + ) + ], + ), + message.toText(fontSize: 18, color: MyColors.lightTextColor), + 28.height, + Row( + children: [ + Expanded( + child: ShowFillButton( + title: okTitle ?? "OK", + onPressed: onTap ?? () => Navigator.pop(context), + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index f042568..0e3be1a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,12 +17,13 @@ dependencies: provider: ^6.0.0 easy_localization: ^3.0.3 http: ^0.13.3 - permission_handler: any + permission_handler: ^10.2.0 + device_info_plus: any flutter_svg: ^1.0.3 sizer: ^2.0.15 fluttertoast: ^8.0.8 shared_preferences: ^2.0.6 - file_picker: ^4.4.0 + file_picker: ^6.1.1 image_picker: ^0.8.4+4 equatable: ^2.0.3 logger: ^1.1.0 From e154d6e187b8e36547ccceed06302035a569df34 Mon Sep 17 00:00:00 2001 From: Faiz Hashmi Date: Sun, 25 Feb 2024 14:28:15 +0300 Subject: [PATCH 6/6] Added Account Screens as per new Design Syntax Fixed --- .../setting_options_language.dart | 4 +- pubspec.lock | 1143 ----------------- 2 files changed, 2 insertions(+), 1145 deletions(-) delete mode 100644 pubspec.lock diff --git a/lib/views/setting_options/setting_options_language.dart b/lib/views/setting_options/setting_options_language.dart index cc4ea4f..f79546c 100644 --- a/lib/views/setting_options/setting_options_language.dart +++ b/lib/views/setting_options/setting_options_language.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/view_models/dashboard_view_model_customer.dart'; import 'package:mc_common_app/views/setting_options/widgets/custom_setting_options_tile.dart'; import 'package:mc_common_app/widgets/button/show_fill_button.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; @@ -32,8 +33,7 @@ class SettingOptionsLanguage extends StatelessWidget { titleText: "My Requests", needBorderBelow: true, onTap: () { - context.read - Navigator.pop(context); + context.read().onNavbarTapped(4); Navigator.pop(context); }), CustomSettingOptionsTile(leadingWidget: const Icon(Icons.favorite, size: 20), titleText: "Favorite list", needBorderBelow: true, onTap: () {}), diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index e8ace14..0000000 --- a/pubspec.lock +++ /dev/null @@ -1,1143 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - args: - dependency: transitive - description: - name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 - url: "https://pub.dev" - source: hosted - version: "2.4.2" - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - auto_size_text: - dependency: "direct main" - description: - name: auto_size_text - sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - badges: - dependency: "direct main" - description: - name: badges - sha256: a7b6bbd60dce418df0db3058b53f9d083c22cdb5132a052145dc267494df0b84 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - cached_network_image: - dependency: "direct main" - description: - name: cached_network_image - sha256: fd3d0dc1d451f9a252b32d95d3f0c3c487bc41a75eba2e6097cb0b9c71491b15 - url: "https://pub.dev" - source: hosted - version: "3.2.3" - cached_network_image_platform_interface: - dependency: transitive - description: - name: cached_network_image_platform_interface - sha256: bb2b8403b4ccdc60ef5f25c70dead1f3d32d24b9d6117cfc087f496b178594a7 - url: "https://pub.dev" - source: hosted - version: "2.0.0" - cached_network_image_web: - dependency: transitive - description: - name: cached_network_image_web - sha256: b8eb814ebfcb4dea049680f8c1ffb2df399e4d03bf7a352c775e26fa06e02fa0 - url: "https://pub.dev" - source: hosted - version: "1.0.2" - carousel_slider: - dependency: "direct main" - description: - name: carousel_slider - sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42" - url: "https://pub.dev" - source: hosted - version: "4.2.1" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" - collection: - dependency: transitive - description: - name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" - url: "https://pub.dev" - source: hosted - version: "1.17.1" - country_code_picker: - dependency: "direct main" - description: - name: country_code_picker - sha256: "92818885f0e47486539f80463b66f649970506a91dd3c0731ca3ba5308324a4d" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: fd832b5384d0d6da4f6df60b854d33accaaeb63aa9e10e736a87381f08dee2cb - url: "https://pub.dev" - source: hosted - version: "0.3.3+5" - crypto: - dependency: transitive - description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab - url: "https://pub.dev" - source: hosted - version: "3.0.3" - csslib: - dependency: transitive - description: - name: csslib - sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d - url: "https://pub.dev" - source: hosted - version: "1.0.6" - dropdown_button2: - dependency: "direct main" - description: - name: dropdown_button2 - sha256: b0fe8d49a030315e9eef6c7ac84ca964250155a6224d491c1365061bc974a9e1 - url: "https://pub.dev" - source: hosted - version: "2.3.9" - easy_localization: - dependency: "direct main" - description: - name: easy_localization - sha256: de63e3b422adfc97f256cbb3f8cf12739b6a4993d390f3cadb3f51837afaefe5 - url: "https://pub.dev" - source: hosted - version: "3.0.3" - easy_logger: - dependency: transitive - description: - name: easy_logger - sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 - url: "https://pub.dev" - source: hosted - version: "0.0.2" - equatable: - dependency: "direct main" - description: - name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 - url: "https://pub.dev" - source: hosted - version: "2.0.5" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - ffi: - dependency: transitive - description: - name: ffi - sha256: "13a6ccf6a459a125b3fcdb6ec73bd5ff90822e071207c663bfd1f70062d51d18" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - file: - dependency: transitive - description: - name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" - url: "https://pub.dev" - source: hosted - version: "6.1.4" - file_picker: - dependency: "direct main" - description: - name: file_picker - sha256: "704259669b5e9cb24e15c11cfcf02caf5f20d30901b3916d60b6d1c2d647035f" - url: "https://pub.dev" - source: hosted - version: "4.6.1" - file_selector_linux: - dependency: transitive - description: - name: file_selector_linux - sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" - url: "https://pub.dev" - source: hosted - version: "0.9.2+1" - file_selector_macos: - dependency: transitive - description: - name: file_selector_macos - sha256: "182c3f8350cee659f7b115e956047ee3dc672a96665883a545e81581b9a82c72" - url: "https://pub.dev" - source: hosted - version: "0.9.3+2" - file_selector_platform_interface: - dependency: transitive - description: - name: file_selector_platform_interface - sha256: "0aa47a725c346825a2bd396343ce63ac00bda6eff2fbc43eabe99737dede8262" - url: "https://pub.dev" - source: hosted - version: "2.6.1" - file_selector_windows: - dependency: transitive - description: - name: file_selector_windows - sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 - url: "https://pub.dev" - source: hosted - version: "0.9.3+1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_blurhash: - dependency: transitive - description: - name: flutter_blurhash - sha256: "05001537bd3fac7644fa6558b09ec8c0a3f2eba78c0765f88912882b1331a5c6" - url: "https://pub.dev" - source: hosted - version: "0.7.0" - flutter_cache_manager: - dependency: transitive - description: - name: flutter_cache_manager - sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" - url: "https://pub.dev" - source: hosted - version: "3.3.1" - flutter_inappwebview: - dependency: "direct main" - description: - name: flutter_inappwebview - sha256: f73505c792cf083d5566e1a94002311be497d984b5607f25be36d685cf6361cf - url: "https://pub.dev" - source: hosted - version: "5.7.2+3" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 - url: "https://pub.dev" - source: hosted - version: "2.0.3" - flutter_localizations: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: f185ac890306b5779ecbd611f52502d8d4d63d27703ef73161ca0407e815f02c - url: "https://pub.dev" - source: hosted - version: "2.0.16" - flutter_svg: - dependency: "direct main" - description: - name: flutter_svg - sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2" - url: "https://pub.dev" - source: hosted - version: "1.1.6" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - fluttertoast: - dependency: "direct main" - description: - name: fluttertoast - sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c" - url: "https://pub.dev" - source: hosted - version: "8.2.2" - geolocator: - dependency: "direct main" - description: - name: geolocator - sha256: "9d6eff112971b9f195271834b390fc0e1899a9a6c96225ead72efd5d4aaa80c7" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - geolocator_android: - dependency: transitive - description: - name: geolocator_android - sha256: "6cd3c622df085a79fd61f5c14fa024c3ba593aa6b1df2ee809ac59f45e6a9861" - url: "https://pub.dev" - source: hosted - version: "4.1.8" - geolocator_apple: - dependency: transitive - description: - name: geolocator_apple - sha256: "36527c555f4c425f7d8fa8c7c07d67b78e3ff7590d40448051959e1860c1cfb4" - url: "https://pub.dev" - source: hosted - version: "2.2.7" - geolocator_platform_interface: - dependency: transitive - description: - name: geolocator_platform_interface - sha256: af4d69231452f9620718588f41acc4cb58312368716bfff2e92e770b46ce6386 - url: "https://pub.dev" - source: hosted - version: "4.0.7" - geolocator_web: - dependency: transitive - description: - name: geolocator_web - sha256: f68a122da48fcfff68bbc9846bb0b74ef651afe84a1b1f6ec20939de4d6860e1 - url: "https://pub.dev" - source: hosted - version: "2.1.6" - geolocator_windows: - dependency: transitive - description: - name: geolocator_windows - sha256: "463045515b08bd83f73e014359c4ad063b902eb3899952cfb784497ae6c6583b" - url: "https://pub.dev" - source: hosted - version: "0.2.0" - google_maps: - dependency: transitive - description: - name: google_maps - sha256: "555d5d736339b0478e821167ac521c810d7b51c3b2734e6802a9f046b64ea37a" - url: "https://pub.dev" - source: hosted - version: "6.3.0" - google_maps_flutter: - dependency: "direct main" - description: - name: google_maps_flutter - sha256: d4914cb38b3dcb62c39c085d968d434de0f8050f00f4d9f5ba4a7c7e004934cb - url: "https://pub.dev" - source: hosted - version: "2.5.0" - google_maps_flutter_android: - dependency: transitive - description: - name: google_maps_flutter_android - sha256: e6cb018169e49332f88d23b1d2119b09e8ab4e7d3a1b889a1b7b3fd113e034ba - url: "https://pub.dev" - source: hosted - version: "2.5.1" - google_maps_flutter_ios: - dependency: transitive - description: - name: google_maps_flutter_ios - sha256: "2a595c9789070786c654e9772ec0d1bb759ae37d2dd776291af5398531274e06" - url: "https://pub.dev" - source: hosted - version: "2.3.1" - google_maps_flutter_platform_interface: - dependency: transitive - description: - name: google_maps_flutter_platform_interface - sha256: a3e9e6896501e566d902c6c69f010834d410ef4b7b5c18b90c77e871c86b7907 - url: "https://pub.dev" - source: hosted - version: "2.4.1" - google_maps_flutter_web: - dependency: transitive - description: - name: google_maps_flutter_web - sha256: "05067c5aa762ebee44b7ef4902a311ed8cf891ef655e2798bae063aa3050c8d9" - url: "https://pub.dev" - source: hosted - version: "0.5.4+1" - hexcolor: - dependency: "direct main" - description: - name: hexcolor - sha256: c07f4bbb9095df87eeca87e7c69e8c3d60f70c66102d7b8d61c4af0453add3f6 - url: "https://pub.dev" - source: hosted - version: "3.0.1" - html: - dependency: transitive - description: - name: html - sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" - url: "https://pub.dev" - source: hosted - version: "0.15.4" - http: - dependency: "direct main" - description: - name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" - url: "https://pub.dev" - source: hosted - version: "0.13.6" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - image_picker: - dependency: "direct main" - description: - name: image_picker - sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c - url: "https://pub.dev" - source: hosted - version: "0.8.9" - image_picker_android: - dependency: transitive - description: - name: image_picker_android - sha256: d32a997bcc4ee135aebca8e272b7c517927aa65a74b9c60a81a2764ef1a0462d - url: "https://pub.dev" - source: hosted - version: "0.8.7+5" - image_picker_for_web: - dependency: transitive - description: - name: image_picker_for_web - sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - image_picker_ios: - dependency: transitive - description: - name: image_picker_ios - sha256: c5538cacefacac733c724be7484377923b476216ad1ead35a0d2eadcdc0fc497 - url: "https://pub.dev" - source: hosted - version: "0.8.8+2" - image_picker_linux: - dependency: transitive - description: - name: image_picker_linux - sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" - url: "https://pub.dev" - source: hosted - version: "0.2.1+1" - image_picker_macos: - dependency: transitive - description: - name: image_picker_macos - sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" - url: "https://pub.dev" - source: hosted - version: "0.2.1+1" - image_picker_platform_interface: - dependency: transitive - description: - name: image_picker_platform_interface - sha256: ed9b00e63977c93b0d2d2b343685bed9c324534ba5abafbb3dfbd6a780b1b514 - url: "https://pub.dev" - source: hosted - version: "2.9.1" - image_picker_windows: - dependency: transitive - description: - name: image_picker_windows - sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" - url: "https://pub.dev" - source: hosted - version: "0.2.1+1" - injector: - dependency: "direct main" - description: - name: injector - sha256: "2a683124c716e93b45521794f55bfe770e069cb3d871fc4fbc65b5acef78e832" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - intl: - dependency: "direct main" - description: - name: intl - sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 - url: "https://pub.dev" - source: hosted - version: "0.18.0" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" - js_wrapping: - dependency: transitive - description: - name: js_wrapping - sha256: e385980f7c76a8c1c9a560dfb623b890975841542471eade630b2871d243851c - url: "https://pub.dev" - source: hosted - version: "0.7.4" - lints: - dependency: transitive - description: - name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - local_auth: - dependency: "direct main" - description: - name: local_auth - sha256: "7e6c63082e399b61e4af71266b012e767a5d4525dd6e9ba41e174fd42d76e115" - url: "https://pub.dev" - source: hosted - version: "2.1.7" - local_auth_android: - dependency: transitive - description: - name: local_auth_android - sha256: "9ad0b1ffa6f04f4d91e38c2d4c5046583e23f4cae8345776a994e8670df57fb1" - url: "https://pub.dev" - source: hosted - version: "1.0.34" - local_auth_ios: - dependency: transitive - description: - name: local_auth_ios - sha256: "26a8d1ad0b4ef6f861d29921be8383000fda952e323a5b6752cf82ca9cf9a7a9" - url: "https://pub.dev" - source: hosted - version: "1.1.4" - local_auth_platform_interface: - dependency: transitive - description: - name: local_auth_platform_interface - sha256: fc5bd537970a324260fda506cfb61b33ad7426f37a8ea5c461cf612161ebba54 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - local_auth_windows: - dependency: transitive - description: - name: local_auth_windows - sha256: "505ba3367ca781efb1c50d3132e44a2446bccc4163427bc203b9b4d8994d97ea" - url: "https://pub.dev" - source: hosted - version: "1.0.10" - logger: - dependency: "direct main" - description: - name: logger - sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f" - url: "https://pub.dev" - source: hosted - version: "1.4.0" - logging: - dependency: "direct main" - description: - name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" - url: "https://pub.dev" - source: hosted - version: "0.12.15" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 - url: "https://pub.dev" - source: hosted - version: "0.2.0" - meta: - dependency: transitive - description: - name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - mime: - dependency: transitive - description: - name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e - url: "https://pub.dev" - source: hosted - version: "1.0.4" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - octo_image: - dependency: transitive - description: - name: octo_image - sha256: "107f3ed1330006a3bea63615e81cf637433f5135a52466c7caa0e7152bca9143" - url: "https://pub.dev" - source: hosted - version: "1.0.2" - path: - dependency: transitive - description: - name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" - url: "https://pub.dev" - source: hosted - version: "1.8.3" - path_drawing: - dependency: transitive - description: - name: path_drawing - sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 - url: "https://pub.dev" - source: hosted - version: "1.0.1" - path_parsing: - dependency: transitive - description: - name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf - url: "https://pub.dev" - source: hosted - version: "1.0.1" - path_provider: - dependency: "direct main" - description: - name: path_provider - sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" - url: "https://pub.dev" - source: hosted - version: "2.0.15" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" - url: "https://pub.dev" - source: hosted - version: "2.3.1" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: a34ecd7fb548f8e57321fd8e50d865d266941b54e6c3b7758cf8f37c24116905 - url: "https://pub.dev" - source: hosted - version: "2.0.7" - percent_indicator: - dependency: "direct main" - description: - name: percent_indicator - sha256: c37099ad833a883c9d71782321cb65c3a848c21b6939b6185f0ff6640d05814c - url: "https://pub.dev" - source: hosted - version: "4.2.3" - permission_handler: - dependency: "direct main" - description: - name: permission_handler - sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" - url: "https://pub.dev" - source: hosted - version: "10.4.3" - permission_handler_android: - dependency: transitive - description: - name: permission_handler_android - sha256: d74e77a5ecd38649905db0a7d05ef16bed42ff263b9efb73ed794317c5764ec3 - url: "https://pub.dev" - source: hosted - version: "10.3.4" - permission_handler_apple: - dependency: transitive - description: - name: permission_handler_apple - sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" - url: "https://pub.dev" - source: hosted - version: "9.1.4" - permission_handler_platform_interface: - dependency: transitive - description: - name: permission_handler_platform_interface - sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" - url: "https://pub.dev" - source: hosted - version: "3.11.3" - permission_handler_windows: - dependency: transitive - description: - name: permission_handler_windows - sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 - url: "https://pub.dev" - source: hosted - version: "0.1.3" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 - url: "https://pub.dev" - source: hosted - version: "5.4.0" - platform: - dependency: transitive - description: - name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d - url: "https://pub.dev" - source: hosted - version: "2.1.6" - provider: - dependency: "direct main" - description: - name: provider - sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f - url: "https://pub.dev" - source: hosted - version: "6.0.5" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" - url: "https://pub.dev" - source: hosted - version: "0.27.7" - sanitize_html: - dependency: transitive - description: - name: sanitize_html - sha256: "0a445f19bbaa196f5a4f93461aa066b94e6e025622eb1e9bc77872a5e25233a5" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac - url: "https://pub.dev" - source: hosted - version: "2.2.1" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" - url: "https://pub.dev" - source: hosted - version: "2.2.1" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" - url: "https://pub.dev" - source: hosted - version: "2.3.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a - url: "https://pub.dev" - source: hosted - version: "2.3.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a - url: "https://pub.dev" - source: hosted - version: "2.3.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf - url: "https://pub.dev" - source: hosted - version: "2.2.1" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f - url: "https://pub.dev" - source: hosted - version: "2.3.1" - shimmer: - dependency: "direct main" - description: - name: shimmer - sha256: "1f1009b5845a1f88f1c5630212279540486f97409e9fc3f63883e71070d107bf" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - signalr_core: - dependency: "direct main" - description: - name: signalr_core - sha256: dca676372a00c051511591ed0e24521ff7aa4e9320a7fa778a1007f7f522c8c0 - url: "https://pub.dev" - source: hosted - version: "1.1.1" - simple_gesture_detector: - dependency: transitive - description: - name: simple_gesture_detector - sha256: "86d08f85f1f58583b7b4b941d989f48ea6ce08c1724a1d10954a277c2ec36592" - url: "https://pub.dev" - source: hosted - version: "0.2.0" - sizer: - dependency: "direct main" - description: - name: sizer - sha256: d2b3cb6cbc4a637f508dacd786bae55df31e5fc088044248a43e4fd1e050c117 - url: "https://pub.dev" - source: hosted - version: "2.0.15" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 - url: "https://pub.dev" - source: hosted - version: "1.9.1" - sqflite: - dependency: transitive - description: - name: sqflite - sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" - url: "https://pub.dev" - source: hosted - version: "2.3.0" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a" - url: "https://pub.dev" - source: hosted - version: "2.5.0" - sse_client: - dependency: transitive - description: - name: sse_client - sha256: "71bd826430b41ab20a69d85bf2dfe9f11cfe222938e681ada1aea71fc8adf348" - url: "https://pub.dev" - source: hosted - version: "0.1.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 - url: "https://pub.dev" - source: hosted - version: "1.11.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - synchronized: - dependency: transitive - description: - name: synchronized - sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - table_calendar: - dependency: "direct main" - description: - name: table_calendar - sha256: "1e3521a3e6d3fc7f645a58b135ab663d458ab12504f1ea7f9b4b81d47086c478" - url: "https://pub.dev" - source: hosted - version: "3.0.9" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb - url: "https://pub.dev" - source: hosted - version: "0.5.1" - tuple: - dependency: transitive - description: - name: tuple - sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 - url: "https://pub.dev" - source: hosted - version: "2.0.2" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - universal_io: - dependency: transitive - description: - name: universal_io - sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" - url: "https://pub.dev" - source: hosted - version: "2.2.2" - url_launcher: - dependency: "direct main" - description: - name: url_launcher - sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27" - url: "https://pub.dev" - source: hosted - version: "6.1.14" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330 - url: "https://pub.dev" - source: hosted - version: "6.1.0" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: "7c65021d5dee51813d652357bc65b8dd4a6177082a9966bc8ba6ee477baa795f" - url: "https://pub.dev" - source: hosted - version: "6.1.5" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: b651aad005e0cb06a01dbd84b428a301916dc75f0e7ea6165f80057fee2d8e8e - url: "https://pub.dev" - source: hosted - version: "3.0.6" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: b55486791f666e62e0e8ff825e58a023fd6b1f71c49926483f1128d3bbd8fe88 - url: "https://pub.dev" - source: hosted - version: "3.0.7" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: ba140138558fcc3eead51a1c42e92a9fb074a1b1149ed3c73e66035b2ccd94f2 - url: "https://pub.dev" - source: hosted - version: "2.0.19" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: "95fef3129dc7cfaba2bc3d5ba2e16063bb561fc6d78e63eee16162bc70029069" - url: "https://pub.dev" - source: hosted - version: "3.0.8" - uuid: - dependency: transitive - description: - name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" - url: "https://pub.dev" - source: hosted - version: "3.0.7" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b - url: "https://pub.dev" - source: hosted - version: "2.4.0" - win32: - dependency: transitive - description: - name: win32 - sha256: c0e3a4f7be7dae51d8f152230b86627e3397c1ba8c3fa58e63d44a9f3edc9cef - url: "https://pub.dev" - source: hosted - version: "2.6.1" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" - url: "https://pub.dev" - source: hosted - version: "1.0.3" - xml: - dependency: transitive - description: - name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" - url: "https://pub.dev" - source: hosted - version: "6.3.0" -sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.10.0"