From 4949a141b9461464316628427ae9301ca5cab648 Mon Sep 17 00:00:00 2001 From: Faiz Hashmi Date: Thu, 10 Oct 2024 14:30:12 +0300 Subject: [PATCH] Added Media Viewer --- assets/images/broken_image.png | Bin 0 -> 3098 bytes assets/langs/ar-SA.json | 5 +- assets/langs/en-US.json | 6 +- lib/classes/consts.dart | 3 + lib/config/routes.dart | 10 +- lib/extensions/string_extensions.dart | 6 +- lib/generated/codegen_loader.g.dart | 8 +- lib/generated/locale_keys.g.dart | 2 + .../chat_models/chat_message_model.dart | 5 + .../provider_offers_model.dart | 7 +- lib/repositories/payments_repo.dart | 16 ++- lib/services/payments_service.dart | 29 +++-- lib/utils/enums.dart | 10 +- lib/utils/utils.dart | 36 ++++++ lib/view_models/chat_view_model.dart | 48 +++++--- lib/view_models/payment_view_model.dart | 4 +- .../advertisement/ads_buyer_chats_view.dart | 2 +- .../ads_detail_view/ads_detail_view.dart | 35 ++++++ .../picked_images_container_widget.dart | 26 ++-- lib/views/chat/chat_view.dart | 31 +++-- .../chat/widgets/chat_message_widget.dart | 38 +++--- lib/views/requests/offer_list_page.dart | 5 +- lib/views/requests/request_detail_page.dart | 2 +- lib/widgets/extensions/extensions_widget.dart | 2 +- .../image_viewer/image_viewer_screen.dart | 114 ++++++++++++++++++ 25 files changed, 360 insertions(+), 90 deletions(-) create mode 100644 assets/images/broken_image.png create mode 100644 lib/widgets/image_viewer/image_viewer_screen.dart diff --git a/assets/images/broken_image.png b/assets/images/broken_image.png new file mode 100644 index 0000000000000000000000000000000000000000..dbf6a48e3a45be5f3b89d3eed28849d3c1b70eed GIT binary patch literal 3098 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 literal 0 HcmV?d00001 diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 1f4c471..0658fe9 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -654,5 +654,8 @@ "date": "التاريخ", "selectAll": "اختر الكل", "unselectAll": "إلغاء اختيار الكل", - "copySelectedServices": "نسخ الخدمات المحددة" + "copySelectedServices": "نسخ الخدمات المحددة", + "pictures": "الصور", + "noChatMessage": "لا توجد رسائل بعد. أرسل رسالة لبدء المحادثة!" + } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 4ceed5a..cf67266 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -655,5 +655,9 @@ "date": "Date", "selectAll": "Select All", "unselectAll": "Unselect All", - "copySelectedServices": "Copy Selected Services" + "copySelectedServices": "Copy Selected Services", + "pictures": "Pictures", + "noChatMessage": "No messages yet. Send a message to start the conversation!" + + } \ No newline at end of file diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 79e5d87..7d19e56 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -292,6 +292,9 @@ class MyAssets { static String whatsAppIcon = "${assetPath}icons/whatsapp_icon.svg"; static const String arrowRight = "${assetPath}icons/ic_arrow_right.svg"; + static const String brokenImage = "${assetPath}images/broken_image.png"; + + } RegExp numReg = RegExp(r".*[0-9].*"); diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 4c59feb..49df5ae 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -1,5 +1,6 @@ import 'package:mc_common_app/models/advertisment_models/ad_details_model.dart'; import 'package:mc_common_app/models/chat_models/buyers_chat_for_ads_model.dart'; +import 'package:mc_common_app/models/chat_models/chat_message_model.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/models/user_models/register_user.dart'; @@ -40,6 +41,7 @@ import 'package:mc_common_app/views/user/register_selection_page.dart'; import 'package:mc_common_app/views/splash/splash_page.dart'; import 'package:mc_common_app/views/user/vertify_password_page.dart'; import 'package:flutter/material.dart'; +import 'package:mc_common_app/widgets/image_viewer/image_viewer_screen.dart'; class AppRoutes { //User @@ -110,6 +112,9 @@ class AppRoutes { static const String settingOptionsInviteFriends = "/settingOptionsInviteFriends"; static const String settingOptionsHelp = "/settingOptionsHelp"; + //MediaViewer + static const String mediaViewerScreen = "/mediaViewer"; + //Profile Screen static const String profileView = "/profileView"; @@ -151,7 +156,7 @@ class AppRoutes { AppRoutes.adsDetailView: (context) => AdsDetailView(adDetails: ModalRoute.of(context)!.settings.arguments as AdDetailsModel), AppRoutes.createAdView: (context) => const CreateAdView(), AppRoutes.adsFilterView: (context) => const AdsFilterView(), - AppRoutes.selectAdTypeView: (context) => SelectAdTypeView(arguments: ModalRoute.of(context)!.settings.arguments as List), + AppRoutes.selectAdTypeView: (context) => SelectAdTypeView(arguments: ModalRoute.of(context)!.settings.arguments as List), AppRoutes.chatView: (context) => ChatView(chatViewArguments: ModalRoute.of(context)!.settings.arguments as ChatViewArguments), AppRoutes.adsBuyerChatsListView: (context) => AdsBuyerChatsView(buyersListViewArguments: ModalRoute.of(context)!.settings.arguments as List), AppRoutes.settingOptionsFaqs: (context) => const SettingOptionsFAQs(), @@ -163,6 +168,9 @@ class AppRoutes { AppRoutes.offersListPage: (context) => OfferListPage(offerListPageArguments: ModalRoute.of(context)!.settings.arguments as OfferListPageArguments), AppRoutes.reviewRequestOffer: (context) => const ReviewRequestOffer(), AppRoutes.requestsFilterView: (context) => const RequestsFilterView(), + + //MediaViewer + AppRoutes.mediaViewerScreen: (context) => MediaViewerScreen(images: ModalRoute.of(context)!.settings.arguments as List), }; } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 58208c7..e71607c 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -455,16 +455,12 @@ extension PaymentTypesToInt on PaymentTypes { switch (this) { case PaymentTypes.subscription: return 1; - case PaymentTypes.appointment: return 2; - case PaymentTypes.adReserve: return 4; - case PaymentTypes.ads: return 3; - case PaymentTypes.request: return 5; case PaymentTypes.extendAds: @@ -577,7 +573,7 @@ extension DateTimeConversions on DateTime { final date2 = DateTime.now(); final difference = date2.difference(this); if ((difference.inDays / 7).floor() >= 2) { - return DateHelper.formatAsDayMonthYear(date2); + return DateHelper.formatAsDayMonthYear(this); } else if ((difference.inDays / 7).floor() >= 1) { return (numericDates) ? '1 week ago' : 'Last week'; } else if (difference.inDays >= 2) { diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 2f0664a..1f339eb 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -670,7 +670,9 @@ class CodegenLoader extends AssetLoader{ "date": "التاريخ", "selectAll": "اختر الكل", "unselectAll": "إلغاء اختيار الكل", - "copySelectedServices": "نسخ الخدمات المحددة" + "copySelectedServices": "نسخ الخدمات المحددة", + "pictures": "الصور", + "noChatMessage": "لا توجد رسائل بعد. أرسل رسالة لبدء المحادثة!" }; static const Map en_US = { "firstTimeLogIn": "First Time Log In", @@ -1329,7 +1331,9 @@ static const Map en_US = { "date": "Date", "selectAll": "Select All", "unselectAll": "Unselect All", - "copySelectedServices": "Copy Selected Services" + "copySelectedServices": "Copy Selected Services", + "pictures": "Pictures", + "noChatMessage": "No messages yet. Send a message to start the conversation!" }; static const Map> mapLocales = {"ar_SA": ar_SA, "en_US": en_US}; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 37af609..e430e49 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -634,5 +634,7 @@ abstract class LocaleKeys { static const selectAll = 'selectAll'; static const unselectAll = 'unselectAll'; static const copySelectedServices = 'copySelectedServices'; + static const pictures = 'pictures'; + static const noChatMessage = 'noChatMessage'; } diff --git a/lib/models/chat_models/chat_message_model.dart b/lib/models/chat_models/chat_message_model.dart index 8a5c728..4ad318d 100644 --- a/lib/models/chat_models/chat_message_model.dart +++ b/lib/models/chat_models/chat_message_model.dart @@ -163,4 +163,9 @@ class MessageImageModel { 'reqOfferID': reqOfferID, // We don't include 'isFromNetwork' since it's set manually when parsing JSON }; } + + @override + String toString() { + return 'MessageImageModel{id: $id, imageUrl: $imageUrl, imageStr: $imageStr, imagePath: $imagePath, isFromNetwork: $isFromNetwork, reqOfferID: $reqOfferID}'; + } } diff --git a/lib/models/requests_models/provider_offers_model.dart b/lib/models/requests_models/provider_offers_model.dart index a101485..c829a8c 100644 --- a/lib/models/requests_models/provider_offers_model.dart +++ b/lib/models/requests_models/provider_offers_model.dart @@ -1,7 +1,9 @@ import 'dart:developer'; +import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/main.dart'; import 'package:mc_common_app/models/chat_models/chat_message_model.dart'; +import 'package:mc_common_app/utils/enums.dart'; class ProviderOffersModel { int? id; @@ -61,6 +63,7 @@ class ServiceProvidersOffers { String? email; String? companyName; String? createdOn; + RequestOfferStatusEnum? requestOfferStatusEnum; int? offerCount; List? chatMessages; @@ -74,6 +77,7 @@ class ServiceProvidersOffers { this.chatMessages, this.providerUserId, this.createdOn, + this.requestOfferStatusEnum, }); ServiceProvidersOffers.fromJson(Map json) { @@ -85,11 +89,12 @@ class ServiceProvidersOffers { companyName = json['companyName']; offerCount = json['offerCount']; createdOn = json['createdOn']; + requestOfferStatusEnum = ((json['offerStatusLast']) as int).toRequestOfferStatusEnum(); chatMessages = []; } @override String toString() { - return 'ServiceProvidersOffers{providerUserId: $providerUserId, providerId: $providerId, name: $name, mobileNo: $mobileNo, email: $email, companyName: $companyName,offeredItemCreatedOn: $createdOn, offerCount: $offerCount, chatMessages: $chatMessages}'; + return 'ServiceProvidersOffers{providerUserId: $providerUserId, providerId: $providerId, name: $name, mobileNo: $mobileNo, email: $email, companyName: $companyName,offeredItemCreatedOn: $createdOn, requestOfferStatusEnum: $requestOfferStatusEnum, offerCount: $offerCount, chatMessages: $chatMessages}'; } } diff --git a/lib/repositories/payments_repo.dart b/lib/repositories/payments_repo.dart index 4ffa1fe..579f999 100644 --- a/lib/repositories/payments_repo.dart +++ b/lib/repositories/payments_repo.dart @@ -6,7 +6,7 @@ import 'package:mc_common_app/models/general_models/generic_resp_model.dart'; import 'package:mc_common_app/models/payment_models/pay_order_detail_resp_model.dart'; abstract class PaymentsRepo { - Future getPayOrderDetails({required int paymentId, required int adId}); + Future getPayOrderDetails({required int paymentTypeId, required int adId}); } class PaymentsRepoImp implements PaymentsRepo { @@ -14,8 +14,18 @@ class PaymentsRepoImp implements PaymentsRepo { AppState appState = injector.get(); @override - Future getPayOrderDetails({required int paymentId, required int adId}) async { - GenericRespModel adsGenericModel = await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.payForOrderDetailGet); + Future getPayOrderDetails({required int paymentTypeId, required int adId}) async { + var queryParameters = { + "AdsID": adId.toString(), + "PaymentType": paymentTypeId.toString(), + }; + + GenericRespModel adsGenericModel = await apiClient.getJsonForObject( + token: appState.getUser.data!.accessToken, + queryParameters: queryParameters, + (json) => GenericRespModel.fromJson(json), + ApiConsts.payForOrderDetailGet, + ); PayOrderDetailRespModel payOrderDetailRespModel = PayOrderDetailRespModel.fromJson(adsGenericModel.data); return payOrderDetailRespModel; } diff --git a/lib/services/payments_service.dart b/lib/services/payments_service.dart index aed50b8..36f5c91 100644 --- a/lib/services/payments_service.dart +++ b/lib/services/payments_service.dart @@ -27,23 +27,15 @@ abstract class PaymentService { class PaymentServiceImp implements PaymentService { MyInAppBrowser? myInAppBrowser; var inAppBrowserOptions = InAppBrowserClassOptions( - inAppWebViewGroupOptions: - InAppWebViewGroupOptions(crossPlatform: InAppWebViewOptions(useShouldOverrideUrlLoading: true, transparentBackground: false), ios: IOSInAppWebViewOptions(applePayAPIEnabled: true)), + inAppWebViewGroupOptions: 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)); + ios: IOSInAppBrowserOptions(hideToolbarBottom: true, toolbarBottomBackgroundColor: Colors.white, closeButtonColor: Colors.white, presentationStyle: IOSUIModalPresentationStyle.OVER_FULL_SCREEN)); - @override - Future placePayment({ - required int id, - List? appointmentIds, - required PaymentTypes paymentType, - required Function() onSuccess, - required Function() onFailure, - }) async { + String getUrlRequestByPaymentId({required int id, List? appointmentIds, required PaymentTypes paymentType}) { String urlRequest = ""; int customerId = AppState().getUser.data!.userInfo!.customerId ?? 0; + switch (paymentType) { case PaymentTypes.subscription: urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&OrderProviderSubscriptionID=$id"; @@ -74,6 +66,19 @@ class PaymentServiceImp implements PaymentService { urlRequest = "${ApiConsts.paymentWebViewUrl}?PaymentType=${paymentType.getIdFromPaymentTypesEnum()}&CustomerID=$customerId&RequestID=$id"; break; } + + return urlRequest; + } + + @override + Future placePayment({ + required int id, + List? appointmentIds, + required PaymentTypes paymentType, + required Function() onSuccess, + required Function() onFailure, + }) async { + String urlRequest = getUrlRequestByPaymentId(id: id, paymentType: paymentType, appointmentIds: appointmentIds); log("PaymentUrl: $urlRequest"); myInAppBrowser = MyInAppBrowser(onExitCallback: () { log("Browser Exited"); diff --git a/lib/utils/enums.dart b/lib/utils/enums.dart index b214b10..f948197 100644 --- a/lib/utils/enums.dart +++ b/lib/utils/enums.dart @@ -78,7 +78,15 @@ enum PaymentMethods { tamara, } -enum PaymentTypes { subscription, appointment, adReserve, ads, request, extendAds, partialAppointment } +enum PaymentTypes { + subscription, + appointment, + adReserve, + ads, + request, + extendAds, + partialAppointment, +} enum AdCreationSteps { vehicleDetails, diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 0d7464b..6470fd9 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -249,6 +249,42 @@ class Utils { } } + static Color getChipColorByRequestOfferStatusEnum(RequestOfferStatusEnum requestOfferStatusEnum) { + switch (requestOfferStatusEnum) { + case RequestOfferStatusEnum.offer: + return MyColors.pendingColor; + + case RequestOfferStatusEnum.negotiate: + return MyColors.submittedColor; + + case RequestOfferStatusEnum.accepted: + return MyColors.completedColor; + case RequestOfferStatusEnum.rejected: + return MyColors.cancelledColor; + + case RequestOfferStatusEnum.cancel: + return MyColors.redColor; + } + } + + static String getNameByRequestOfferStatusEnum(RequestOfferStatusEnum requestOfferStatusEnum) { + switch (requestOfferStatusEnum) { + case RequestOfferStatusEnum.offer: + return "Pending"; + + case RequestOfferStatusEnum.negotiate: + return "Negotiate"; + + case RequestOfferStatusEnum.accepted: + return "Accepted"; + case RequestOfferStatusEnum.rejected: + return "Rejected"; + + case RequestOfferStatusEnum.cancel: + return "Cancelled"; + } + } + static Color getChipColorByBranchStatus(BranchStatusEnum branchStatusEnum) { switch (branchStatusEnum) { case BranchStatusEnum.pending: diff --git a/lib/view_models/chat_view_model.dart b/lib/view_models/chat_view_model.dart index 34b308b..5de3d93 100644 --- a/lib/view_models/chat_view_model.dart +++ b/lib/view_models/chat_view_model.dart @@ -303,11 +303,7 @@ class ChatVM extends ChangeNotifier { MessageImageModel convertFileToMessageImageModel({required File file}) { List imageBytes = file.readAsBytesSync(); String image = base64Encode(imageBytes); - MessageImageModel vehiclePostingImages = MessageImageModel(imageStr: image, - id: 0, - isFromNetwork: false, - reqOfferID: latestOfferId, - imagePath: file.path); + MessageImageModel vehiclePostingImages = MessageImageModel(imageStr: image, id: 0, isFromNetwork: false, reqOfferID: latestOfferId, imagePath: file.path); return vehiclePostingImages; } @@ -376,7 +372,7 @@ class ChatVM extends ChangeNotifier { "ReceiverUserID": receiverId, "SenderUserID": userId, "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(), - "ChatText": message, + "ChatText": chatMessageType == ChatMessageTypeEnum.image ? "${messageImages.length} Image(s)" : message, "RequestID": requestId, "ReqOfferID": latestOfferId, }; @@ -413,10 +409,7 @@ class ChatVM extends ChangeNotifier { serviceProviderOffersList[providerIndex].chatMessages!.add(chatMessageModel); } } else { - int providerIndex = context - .read() - .myFilteredRequests - .indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId)); + int providerIndex = context.read().myFilteredRequests.indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId)); log("providerIndex2:$providerIndex"); if (providerIndex != -1) { context.read().addChatMessagesInRequestsModel(msg: chatMessageModel, index: providerIndex); @@ -444,7 +437,7 @@ class ChatVM extends ChangeNotifier { } } - Future getUsersChatMessagesForCustomer({ + Future getRequestsChatMessagesForCustomer({ required BuildContext context, required int providerId, required int requestOfferId, @@ -468,6 +461,17 @@ class ChatVM extends ChangeNotifier { } } } + List unreadMessageIds = []; + + for (var msg in chatMessages) { + if (!msg.isRead! && !msg.isMyMessage!) { + unreadMessageIds.add(msg.id!); + } + } + + if (unreadMessageIds.isNotEmpty) { + await markMessagesAsRead(context, unreadMessageIds, ChatTypeEnum.requestOffer); + } Utils.hideLoading(context); notifyListeners(); } catch (e) { @@ -477,7 +481,7 @@ class ChatVM extends ChangeNotifier { } } - Future getUsersChatMessagesForProvider({ + Future getRequestsChatMessagesForProvider({ required BuildContext context, required int customerId, required int requestOfferId, @@ -489,6 +493,15 @@ class ChatVM extends ChangeNotifier { Utils.showLoading(context); List chatMessages = await chatRepo.getUsersChatMessagesForRequests(providerId: providerId, customerId: customerId, requestOfferId: requestOfferId, requestId: requestId); context.read().overwriteChatMessagesInRequestsModel(messages: chatMessages, index: customerRequestIndex, context: context); + List unreadMessageIds = []; + for (var msg in chatMessages) { + if (!msg.isRead! && !msg.isMyMessage!) { + unreadMessageIds.add(msg.id!); + } + } + if (unreadMessageIds.isNotEmpty) { + await markMessagesAsRead(context, unreadMessageIds, ChatTypeEnum.requestOffer); + } Utils.hideLoading(context); } catch (e) { logger.i(e.toString()); @@ -578,7 +591,7 @@ class ChatVM extends ChangeNotifier { for (var msg in currentMessagesForAds) { msg.senderName = senderName ?? ""; - if (!msg.isRead!) { + if (!msg.isRead! && !msg.isMyMessage!) { unreadMessageIds.add(msg.id!); } } @@ -598,13 +611,13 @@ class ChatVM extends ChangeNotifier { Future markMessagesAsRead(BuildContext context, List messageIds, ChatTypeEnum chatTypeEnum) async { bool status = false; try { - Utils.showLoading(context); + // Utils.showLoading(context); status = await chatRepo.markMessageAsRead(messageIds: messageIds, chatTypeEnum: chatTypeEnum); - Utils.hideLoading(context); + // Utils.hideLoading(context); } catch (e) { logger.i(e.toString()); Utils.showToast(e.toString()); - Utils.hideLoading(context); + // Utils.hideLoading(context); status = false; } return status; @@ -659,7 +672,8 @@ class ChatVM extends ChangeNotifier { notifyListeners(); - if (AppState().currentAppType == AppType.customer) {} else {} + if (AppState().currentAppType == AppType.customer) { + } else {} return true; } return false; diff --git a/lib/view_models/payment_view_model.dart b/lib/view_models/payment_view_model.dart index 89fba48..e60d2f3 100644 --- a/lib/view_models/payment_view_model.dart +++ b/lib/view_models/payment_view_model.dart @@ -78,7 +78,7 @@ class PaymentVM extends ChangeNotifier { Future onAdsPaymentSuccess({required BuildContext context, required int currentAdId, required int paymentTypeId}) async { Utils.showLoading(context); - PayOrderDetailRespModel payOrderDetailRespModel = await paymentRepo.getPayOrderDetails(paymentId: paymentTypeId, adId: currentAdId); + PayOrderDetailRespModel payOrderDetailRespModel = await paymentRepo.getPayOrderDetails(paymentTypeId: paymentTypeId, adId: currentAdId); await Future.delayed(const Duration(seconds: 2)); Utils.hideLoading(context); @@ -98,7 +98,7 @@ class PaymentVM extends ChangeNotifier { Future onAppointmentPaymentSuccess({required BuildContext context, required int currentAdId, required int paymentTypeId}) async { Utils.showLoading(context); //TODO: CONFIRM FROM ZAHOOR THAT WILL THIS METHOD WORK FOR APPOINTMENT - PayOrderDetailRespModel payOrderDetailRespModel = await paymentRepo.getPayOrderDetails(paymentId: paymentTypeId, adId: currentAdId); + PayOrderDetailRespModel payOrderDetailRespModel = await paymentRepo.getPayOrderDetails(paymentTypeId: paymentTypeId, adId: currentAdId); await Future.delayed(const Duration(seconds: 2)); Utils.hideLoading(context); diff --git a/lib/views/advertisement/ads_buyer_chats_view.dart b/lib/views/advertisement/ads_buyer_chats_view.dart index 520896c..3645d77 100644 --- a/lib/views/advertisement/ads_buyer_chats_view.dart +++ b/lib/views/advertisement/ads_buyer_chats_view.dart @@ -57,7 +57,7 @@ class AdsBuyerChatsView extends StatelessWidget { (chatForAdsModel.buyerName ?? "").toText(fontSize: 16, isBold: true), if (chatForAdsModel.unReadMessagesCount != null && chatForAdsModel.unReadMessagesCount! > 0) ...[ Center( - child: "${chatForAdsModel.unReadMessagesCount ?? "1"}".toText( + child: "${chatForAdsModel.unReadMessagesCount}".toText( color: Colors.white, isBold: true, fontSize: 10, diff --git a/lib/views/advertisement/ads_detail_view/ads_detail_view.dart b/lib/views/advertisement/ads_detail_view/ads_detail_view.dart index 28516fd..e9a0e90 100644 --- a/lib/views/advertisement/ads_detail_view/ads_detail_view.dart +++ b/lib/views/advertisement/ads_detail_view/ads_detail_view.dart @@ -7,6 +7,7 @@ import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/models/advertisment_models/ad_details_model.dart'; import 'package:mc_common_app/models/general_models/widgets_models.dart'; import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/utils/date_helper.dart'; import 'package:mc_common_app/utils/dialogs_and_bottomsheets.dart'; import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/utils.dart'; @@ -271,6 +272,36 @@ class _AdsDetailViewState extends State { }).toWhiteContainer(width: double.infinity, allPading: 12); } + Widget buildAdStartEndDates() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + ("${LocaleKeys.startDate.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), + widget.adDetails.startdate != null + ? DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.startdate!)).toText( + fontSize: 10, + ) + : const SizedBox(), + ], + ), + Row( + children: [ + ("${LocaleKeys.endDate.tr()}: ").toText(fontSize: 12, color: MyColors.lightTextColor), + widget.adDetails.enddate != null + ? DateHelper.formatAsDayMonthYear(DateTime.parse(widget.adDetails.enddate!)).toText( + fontSize: 10, + ) + : const SizedBox(), + ], + ), + ], + ).paddingOnly(top: 5, bottom: 5).onPress(() { + showMyBottomSheet(context, child: AdDamagePartPicturesSheet(adDamageReportList: widget.adDetails.vehicle!.damagereport ?? [])); + }).toWhiteContainer(width: double.infinity, allPading: 12); + } + @override Widget build(BuildContext context) { Widget actionWidget = const SizedBox(); @@ -318,6 +349,10 @@ class _AdsDetailViewState extends State { if (widget.adDetails.vehicle!.damagereport != null && widget.adDetails.vehicle!.damagereport!.isNotEmpty) ...[ buildDamagePartDetails(), ], + if (widget.adDetails.isMyAd ?? false) ...[ + 12.height, + buildAdStartEndDates(), + ], 12.height, if (widget.adDetails.adPostStatus == AdPostStatus.rejected) ...[ buildAdRejectionDetails(), diff --git a/lib/views/advertisement/components/picked_images_container_widget.dart b/lib/views/advertisement/components/picked_images_container_widget.dart index 6089489..6809fa6 100644 --- a/lib/views/advertisement/components/picked_images_container_widget.dart +++ b/lib/views/advertisement/components/picked_images_container_widget.dart @@ -107,18 +107,20 @@ class BuildFilesContainer extends StatelessWidget { child: const Icon(Icons.picture_as_pdf).paddingAll(8), ) : image.isFromNetwork! - ? Image.network( - image.filePath!, - fit: BoxFit.fill, - height: 75, - width: 73, - ).paddingAll(8) - : Image.file( - File(image.filePath!), - fit: BoxFit.fill, - height: 72, - width: 70, - ).paddingAll(8), + ? image.filePath! + .buildNetworkImage( + fit: BoxFit.fill, + height: 75, + width: 73, + ) + .paddingAll(8) + : image.filePath! + .buildFileImage( + fit: BoxFit.fill, + height: 72, + width: 70, + ) + .paddingAll(8), !isReview ? Align( alignment: Alignment.topRight, diff --git a/lib/views/chat/chat_view.dart b/lib/views/chat/chat_view.dart index f95b1aa..3284ca0 100644 --- a/lib/views/chat/chat_view.dart +++ b/lib/views/chat/chat_view.dart @@ -110,14 +110,18 @@ class _ChatViewState extends State { children: [ Expanded( child: chatMessages.isEmpty - ? Center(child: LocaleKeys.noRequestsShow.tr().toText(fontSize: 16, color: MyColors.lightTextColor)) + ? Center(child: LocaleKeys.noChatMessage.tr().toText(fontSize: 16, color: MyColors.lightTextColor)) : ListView.separated( controller: chatVM.scrollController, itemCount: chatMessages.length, separatorBuilder: (BuildContext context, int index) => 20.height, itemBuilder: (BuildContext context, int index) { ChatMessageModel chatMessageModel = chatMessages[index]; - return ChatMessageCustomWidget(chatMessageModel: chatMessageModel, requestStatusEnum: requestVM.currentSelectedRequest!.requestStatus); + return ChatMessageCustomWidget( + chatMessageModel: chatMessageModel, + requestStatusEnum: requestVM.currentSelectedRequest?.requestStatus, + chatTypeEnum: chatTypeEnum, + ); }, ).horPaddingMain(), ), @@ -190,7 +194,7 @@ class _ChatViewState extends State { onAddFilePressed: () => chatVM.pickMultipleImages(), ), ), - ] else ...[ + ] else if (chatTypeEnum == ChatTypeEnum.requestOffer) ...[ Expanded( flex: 1, child: const Icon( @@ -199,21 +203,22 @@ class _ChatViewState extends State { size: 30, ).onPress(() => chatVM.pickMultipleImages()), ), - Expanded( - flex: 8, - child: TxtField( - value: chatVM.chatMessageText, - hint: LocaleKeys.typeMessageHere.tr(), - keyboardType: TextInputType.text, - isNeedBorder: false, - onChanged: (v) => chatVM.updateChatMessageText(v), - ), - ), ], + Expanded( + flex: 8, + child: TxtField( + value: chatVM.chatMessageText, + hint: LocaleKeys.typeMessageHere.tr(), + keyboardType: TextInputType.text, + isNeedBorder: false, + onChanged: (v) => chatVM.updateChatMessageText(v), + ), + ), Expanded( flex: 1, child: const Icon(Icons.send_rounded, color: MyColors.darkPrimaryColor, size: 30).onPress( () async { + debugPrint("AppState().getUser.data.userInfo.userId: ${AppState().getUser.data!.userInfo!.userId}"); ChatMessageTypeEnum chatMessageTypeEnum = ChatMessageTypeEnum.freeText; if (chatVM.pickedImagesForMessage.isNotEmpty) { diff --git a/lib/views/chat/widgets/chat_message_widget.dart b/lib/views/chat/widgets/chat_message_widget.dart index 39ac551..cdadefb 100644 --- a/lib/views/chat/widgets/chat_message_widget.dart +++ b/lib/views/chat/widgets/chat_message_widget.dart @@ -1,9 +1,8 @@ -import 'dart:developer'; - import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:mc_common_app/classes/app_state.dart'; import 'package:mc_common_app/classes/consts.dart'; +import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/int_extensions.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; @@ -11,6 +10,7 @@ import 'package:mc_common_app/models/chat_models/chat_message_model.dart'; import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/utils/dialogs_and_bottomsheets.dart'; import 'package:mc_common_app/utils/enums.dart'; +import 'package:mc_common_app/utils/navigator.dart'; import 'package:mc_common_app/utils/utils.dart'; import 'package:mc_common_app/view_models/chat_view_model.dart'; import 'package:mc_common_app/view_models/requests_view_model.dart'; @@ -24,9 +24,10 @@ import 'package:easy_localization/easy_localization.dart' as lcl; class ChatMessageCustomWidget extends StatefulWidget { final ChatMessageModel chatMessageModel; - final RequestStatusEnum requestStatusEnum; + final RequestStatusEnum? requestStatusEnum; + final ChatTypeEnum chatTypeEnum; - const ChatMessageCustomWidget({super.key, required this.chatMessageModel, required this.requestStatusEnum}); + const ChatMessageCustomWidget({super.key, required this.chatMessageModel, required this.requestStatusEnum, required this.chatTypeEnum}); @override State createState() => _ChatMessageCustomWidgetState(); @@ -424,12 +425,13 @@ class _ChatMessageCustomWidgetState extends State { ); } - Widget buildImageGridWidget( - {required List messagesImages, - double gridItemSize = 50.0, // Default fixed size for each grid item - double spacing = 8.0, - double borderWidth = 1, - Color borderColor = MyColors.textColor}) { + Widget buildImageGridWidget({ + required List messagesImages, + double gridItemSize = 50.0, + double spacing = 8.0, + double borderWidth = 1, + Color borderColor = MyColors.textColor, + }) { int imageCount = messagesImages.length; if (imageCount == 1) { return Container( @@ -447,7 +449,9 @@ class _ChatMessageCustomWidgetState extends State { ) : messagesImages[0].imagePath.buildFileImage(fit: BoxFit.cover), ), - ); + ).onPress(() { + navigateWithName(context, AppRoutes.mediaViewerScreen, arguments: messagesImages); + }); } return SizedBox( // height: (gridItemSize * 2) + (spacing * 2), // Fixed height for 2 rows including spacing @@ -468,7 +472,7 @@ class _ChatMessageCustomWidgetState extends State { return Stack( fit: StackFit.expand, children: [ - (messagesImages[0].isFromNetwork ?? false) + (messagesImages[index].isFromNetwork ?? false) ? messagesImages[index].imageUrl.buildNetworkImage( fit: BoxFit.cover, width: gridItemSize, @@ -490,7 +494,9 @@ class _ChatMessageCustomWidgetState extends State { ), ), ], - ); + ).onPress(() { + navigateWithName(context, AppRoutes.mediaViewerScreen, arguments: messagesImages); + }); } else { return Container( width: gridItemSize, @@ -507,7 +513,9 @@ class _ChatMessageCustomWidgetState extends State { height: gridItemSize, ), ), - ); + ).onPress(() { + navigateWithName(context, AppRoutes.mediaViewerScreen, arguments: messagesImages); + }); } }, ), @@ -534,7 +542,7 @@ class _ChatMessageCustomWidgetState extends State { children: [ buildFreeTextDetailsInMessage(), 10.height, - buildOfferDetailsInChatMessage(requestStatusEnum: widget.requestStatusEnum, chatMessageModel: widget.chatMessageModel, context: context), + buildOfferDetailsInChatMessage(requestStatusEnum: widget.requestStatusEnum!, chatMessageModel: widget.chatMessageModel, context: context), ], ); break; diff --git a/lib/views/requests/offer_list_page.dart b/lib/views/requests/offer_list_page.dart index 031de54..ea1dac7 100644 --- a/lib/views/requests/offer_list_page.dart +++ b/lib/views/requests/offer_list_page.dart @@ -10,6 +10,7 @@ import 'package:mc_common_app/models/requests_models/provider_offers_model.dart' import 'package:mc_common_app/theme/colors.dart'; import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/navigator.dart'; +import 'package:mc_common_app/utils/utils.dart'; import 'package:mc_common_app/view_models/chat_view_model.dart'; import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; @@ -34,7 +35,9 @@ class OfferListPage extends StatelessWidget { itemBuilder: (context, index) { ServiceProvidersOffers offersModel = serviceProviderOffers[index]; return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Utils.statusContainerChip(text: Utils.getNameByRequestOfferStatusEnum(offersModel.requestOfferStatusEnum!), chipColor: Utils.getChipColorByRequestOfferStatusEnum(offersModel.requestOfferStatusEnum!)), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, @@ -91,7 +94,7 @@ class OfferListPage extends StatelessWidget { final chatVM = context.read(); await chatVM - .getUsersChatMessagesForCustomer( + .getRequestsChatMessagesForCustomer( context: context, providerId: offersModel.providerId ?? 0, requestOfferId: 0, diff --git a/lib/views/requests/request_detail_page.dart b/lib/views/requests/request_detail_page.dart index d85b491..5b3e1c0 100644 --- a/lib/views/requests/request_detail_page.dart +++ b/lib/views/requests/request_detail_page.dart @@ -115,7 +115,7 @@ class RequestDetailPage extends StatelessWidget { ); final chatVM = context.read(); await chatVM - .getUsersChatMessagesForProvider( + .getRequestsChatMessagesForProvider( customerId: requestDetailPageArguments.requestModel.customerId, context: context, requestOfferId: 0, diff --git a/lib/widgets/extensions/extensions_widget.dart b/lib/widgets/extensions/extensions_widget.dart index b8c95a0..a29c170 100644 --- a/lib/widgets/extensions/extensions_widget.dart +++ b/lib/widgets/extensions/extensions_widget.dart @@ -369,7 +369,7 @@ extension BuildSVG on String? { return SizedBox( height: height, width: width, - child: SvgPicture.asset(MyAssets.providersIcon, color: MyColors.darkPrimaryColor).paddingAll(15), + child: Image.asset(MyAssets.brokenImage).paddingAll(10), ); } diff --git a/lib/widgets/image_viewer/image_viewer_screen.dart b/lib/widgets/image_viewer/image_viewer_screen.dart new file mode 100644 index 0000000..e4c84d7 --- /dev/null +++ b/lib/widgets/image_viewer/image_viewer_screen.dart @@ -0,0 +1,114 @@ +import 'dart:developer'; + +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/generated/locale_keys.g.dart'; +import 'package:mc_common_app/models/chat_models/chat_message_model.dart'; +import 'package:mc_common_app/theme/colors.dart'; +import 'package:mc_common_app/widgets/common_widgets/app_bar.dart'; +import 'package:mc_common_app/widgets/extensions/extensions_widget.dart'; + +class MediaViewerScreen extends StatefulWidget { + final List images; // List of image URLs or file paths + + const MediaViewerScreen({Key? key, required this.images}) : super(key: key); + + @override + MediaViewerScreenState createState() => MediaViewerScreenState(); +} + +class MediaViewerScreenState extends State { + int selectedIndex = 0; // Track selected image index + + @override + Widget build(BuildContext context) { + debugPrint("loaded"); + return Scaffold( + appBar: CustomAppBar(title: LocaleKeys.pictures.tr()), + body: Column( + children: [ + // Main Image Display (selected image) + Expanded( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: InteractiveViewer( + + panEnabled: true, + // Allow panning + scaleEnabled: true, + // Allow zooming + minScale: 1.0, + // Minimum zoom level + maxScale: 4.0, + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: (widget.images[selectedIndex].isFromNetwork ?? false) + ? widget.images[selectedIndex].imageUrl.buildNetworkImage( + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + ) + : widget.images[selectedIndex].imagePath.buildFileImage( + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + ), + ), + ), + ), + ), + ), + // Thumbnail List at the bottom + Container( + height: 100, // Set height for thumbnail list + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: widget.images.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + setState(() { + selectedIndex = index; // Change the selected image + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Container( + width: 80, // Thumbnail size + height: 80, + decoration: BoxDecoration( + border: Border.all( + color: selectedIndex == index ? MyColors.darkPrimaryColor : MyColors.greyShadowColor, // Highlight selected image + width: selectedIndex == index ? 2 : 1, + ), + borderRadius: BorderRadius.circular(10), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: (widget.images[index].isFromNetwork ?? false) + ? widget.images[index].imageUrl.buildNetworkImage( + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + ) + : widget.images[index].imagePath.buildFileImage( + fit: BoxFit.cover, + width: double.infinity, + height: double.infinity, + ), + ), + ), + ), + ); + }, + ), + ), + 10.height, + ], + ), + ); + } +}