Added Media Viewer

faiz_development_common
Faiz Hashmi 2 weeks ago
parent 3bd72ef382
commit 4949a141b9

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

@ -654,5 +654,8 @@
"date": "التاريخ",
"selectAll": "اختر الكل",
"unselectAll": "إلغاء اختيار الكل",
"copySelectedServices": "نسخ الخدمات المحددة"
"copySelectedServices": "نسخ الخدمات المحددة",
"pictures": "الصور",
"noChatMessage": "لا توجد رسائل بعد. أرسل رسالة لبدء المحادثة!"
}

@ -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!"
}

@ -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].*");

@ -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<bool>),
AppRoutes.selectAdTypeView: (context) => SelectAdTypeView(arguments: ModalRoute.of(context)!.settings.arguments as List<dynamic>),
AppRoutes.chatView: (context) => ChatView(chatViewArguments: ModalRoute.of(context)!.settings.arguments as ChatViewArguments),
AppRoutes.adsBuyerChatsListView: (context) => AdsBuyerChatsView(buyersListViewArguments: ModalRoute.of(context)!.settings.arguments as List<BuyersChatForAdsModel>),
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<MessageImageModel>),
};
}

@ -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) {

@ -670,7 +670,9 @@ class CodegenLoader extends AssetLoader{
"date": "التاريخ",
"selectAll": "اختر الكل",
"unselectAll": "إلغاء اختيار الكل",
"copySelectedServices": "نسخ الخدمات المحددة"
"copySelectedServices": "نسخ الخدمات المحددة",
"pictures": "الصور",
"noChatMessage": "لا توجد رسائل بعد. أرسل رسالة لبدء المحادثة!"
};
static const Map<String,dynamic> en_US = {
"firstTimeLogIn": "First Time Log In",
@ -1329,7 +1331,9 @@ static const Map<String,dynamic> 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<String, Map<String,dynamic>> mapLocales = {"ar_SA": ar_SA, "en_US": en_US};
}

@ -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';
}

@ -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}';
}
}

@ -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<ChatMessageModel>? chatMessages;
@ -74,6 +77,7 @@ class ServiceProvidersOffers {
this.chatMessages,
this.providerUserId,
this.createdOn,
this.requestOfferStatusEnum,
});
ServiceProvidersOffers.fromJson(Map<String, dynamic> 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}';
}
}

@ -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<PayOrderDetailRespModel> getPayOrderDetails({required int paymentId, required int adId});
Future<PayOrderDetailRespModel> getPayOrderDetails({required int paymentTypeId, required int adId});
}
class PaymentsRepoImp implements PaymentsRepo {
@ -14,8 +14,18 @@ class PaymentsRepoImp implements PaymentsRepo {
AppState appState = injector.get<AppState>();
@override
Future<PayOrderDetailRespModel> 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<PayOrderDetailRespModel> 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;
}

@ -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<void> placePayment({
required int id,
List<int>? appointmentIds,
required PaymentTypes paymentType,
required Function() onSuccess,
required Function() onFailure,
}) async {
String getUrlRequestByPaymentId({required int id, List<int>? 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<void> placePayment({
required int id,
List<int>? 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");

@ -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,

@ -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:

@ -303,11 +303,7 @@ class ChatVM extends ChangeNotifier {
MessageImageModel convertFileToMessageImageModel({required File file}) {
List<int> 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<RequestsVM>()
.myFilteredRequests
.indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId));
int providerIndex = context.read<RequestsVM>().myFilteredRequests.indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId));
log("providerIndex2:$providerIndex");
if (providerIndex != -1) {
context.read<RequestsVM>().addChatMessagesInRequestsModel(msg: chatMessageModel, index: providerIndex);
@ -444,7 +437,7 @@ class ChatVM extends ChangeNotifier {
}
}
Future<void> getUsersChatMessagesForCustomer({
Future<void> getRequestsChatMessagesForCustomer({
required BuildContext context,
required int providerId,
required int requestOfferId,
@ -468,6 +461,17 @@ class ChatVM extends ChangeNotifier {
}
}
}
List<int> 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<void> getUsersChatMessagesForProvider({
Future<void> getRequestsChatMessagesForProvider({
required BuildContext context,
required int customerId,
required int requestOfferId,
@ -489,6 +493,15 @@ class ChatVM extends ChangeNotifier {
Utils.showLoading(context);
List<ChatMessageModel> chatMessages = await chatRepo.getUsersChatMessagesForRequests(providerId: providerId, customerId: customerId, requestOfferId: requestOfferId, requestId: requestId);
context.read<RequestsVM>().overwriteChatMessagesInRequestsModel(messages: chatMessages, index: customerRequestIndex, context: context);
List<int> 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<bool> markMessagesAsRead(BuildContext context, List<int> 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;

@ -78,7 +78,7 @@ class PaymentVM extends ChangeNotifier {
Future<void> 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<void> 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);

@ -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,

@ -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<AdsDetailView> {
}).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<AdsDetailView> {
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(),

@ -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,

@ -110,14 +110,18 @@ class _ChatViewState extends State<ChatView> {
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<ChatView> {
onAddFilePressed: () => chatVM.pickMultipleImages(),
),
),
] else ...[
] else if (chatTypeEnum == ChatTypeEnum.requestOffer) ...[
Expanded(
flex: 1,
child: const Icon(
@ -199,21 +203,22 @@ class _ChatViewState extends State<ChatView> {
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) {

@ -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<ChatMessageCustomWidget> createState() => _ChatMessageCustomWidgetState();
@ -424,12 +425,13 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
);
}
Widget buildImageGridWidget(
{required List<MessageImageModel> 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<MessageImageModel> 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<ChatMessageCustomWidget> {
)
: 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<ChatMessageCustomWidget> {
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<ChatMessageCustomWidget> {
),
),
],
);
).onPress(() {
navigateWithName(context, AppRoutes.mediaViewerScreen, arguments: messagesImages);
});
} else {
return Container(
width: gridItemSize,
@ -507,7 +513,9 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
height: gridItemSize,
),
),
);
).onPress(() {
navigateWithName(context, AppRoutes.mediaViewerScreen, arguments: messagesImages);
});
}
},
),
@ -534,7 +542,7 @@ class _ChatMessageCustomWidgetState extends State<ChatMessageCustomWidget> {
children: [
buildFreeTextDetailsInMessage(),
10.height,
buildOfferDetailsInChatMessage(requestStatusEnum: widget.requestStatusEnum, chatMessageModel: widget.chatMessageModel, context: context),
buildOfferDetailsInChatMessage(requestStatusEnum: widget.requestStatusEnum!, chatMessageModel: widget.chatMessageModel, context: context),
],
);
break;

@ -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<ChatVM>();
await chatVM
.getUsersChatMessagesForCustomer(
.getRequestsChatMessagesForCustomer(
context: context,
providerId: offersModel.providerId ?? 0,
requestOfferId: 0,

@ -115,7 +115,7 @@ class RequestDetailPage extends StatelessWidget {
);
final chatVM = context.read<ChatVM>();
await chatVM
.getUsersChatMessagesForProvider(
.getRequestsChatMessagesForProvider(
customerId: requestDetailPageArguments.requestModel.customerId,
context: context,
requestOfferId: 0,

@ -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),
);
}

@ -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<MessageImageModel> 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<MediaViewerScreen> {
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,
],
),
);
}
}
Loading…
Cancel
Save