Completed the Enhancement CR

faiz_development_common
Faiz Hashmi 1 day ago
parent 3146a9433b
commit 6f6533a1ca

@ -901,6 +901,6 @@
"downloadApp": "تحميل تطبيق موّاتر:",
"androidApp": "أندرويد",
"iosApp": "آيفون",
"pleaseWait": "يرجى الانتظار..."
"pleaseWait": "يرجى الانتظار...",
"numberOfShipping": "عدد الشحنات"
}

@ -899,5 +899,6 @@
"downloadApp": "Download MoWater App:",
"androidApp": "Android",
"iosApp": "iOS",
"pleaseWait": "Please wait..."
"pleaseWait": "Please wait...",
"numberOfShipping": "Number of Shipping"
}

@ -380,6 +380,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";
// Tones
static const String engineStartSound = "${assetPath}tones/engine_start_tone.mp3";
}
RegExp numReg = RegExp(r".*[0-9].*");
@ -471,4 +474,3 @@ class GuestConsts {
},
);
}

@ -441,6 +441,7 @@ class CodegenLoader extends AssetLoader{
"usedSparePart": "قطع غيار مستعملة",
"hasYourCarHadAccident": "هل تعرضت سيارتك لأي حادث؟",
"odometerReading": "قراءة عداد المسافات (كم)",
"canMileageIncrease": "هل يمكن زيادة المسافة المقطوعة عند الفحص؟",
"currentlyInUse": "قيد الاستخدام حاليًا",
"demand": "الطلب",
"adDurationExpired": "انتهت مدة عرض الإعلان الخاص بك",
@ -916,7 +917,8 @@ class CodegenLoader extends AssetLoader{
"downloadApp": "تحميل تطبيق موّاتر:",
"androidApp": "أندرويد",
"iosApp": "آيفون",
"pleaseWait": "يرجى الانتظار..."
"pleaseWait": "يرجى الانتظار...",
"numberOfShipping": "عدد الشحنات"
};
static const Map<String,dynamic> _en_US = {
"firstTimeLogIn": "First Time Log In",
@ -1346,6 +1348,7 @@ static const Map<String,dynamic> _en_US = {
"usedSparePart": "Used Spare Part",
"hasYourCarHadAccident": "Has your car had any accident?",
"odometerReading": "Odometer Reading (Km)",
"canMileageIncrease": "Can the mileage be increased when checking?",
"currentlyInUse": "Currently in Use",
"demand": "Demand",
"adDurationExpired": "Your Ad Duration time is over",
@ -1818,7 +1821,8 @@ static const Map<String,dynamic> _en_US = {
"downloadApp": "Download MoWater App:",
"androidApp": "Android",
"iosApp": "iOS",
"pleaseWait": "Please wait..."
"pleaseWait": "Please wait...",
"numberOfShipping": "Number of Shipping"
};
static const Map<String, Map<String,dynamic>> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US};
}

@ -406,6 +406,7 @@ abstract class LocaleKeys {
static const usedSparePart = 'usedSparePart';
static const hasYourCarHadAccident = 'hasYourCarHadAccident';
static const odometerReading = 'odometerReading';
static const canMileageIncrease = 'canMileageIncrease';
static const currentlyInUse = 'currentlyInUse';
static const demand = 'demand';
static const adDurationExpired = 'adDurationExpired';
@ -882,5 +883,6 @@ abstract class LocaleKeys {
static const androidApp = 'androidApp';
static const iosApp = 'iosApp';
static const pleaseWait = 'pleaseWait';
static const numberOfShipping = 'numberOfShipping';
}

@ -15,6 +15,7 @@ class ShippingRequestModel {
String? customerName;
int? vehicleTypeID;
String? vehicleType;
String? numberOfShipping;
ShippingRequestModel({
this.id,
@ -28,6 +29,7 @@ class ShippingRequestModel {
this.customerName,
this.vehicleTypeID,
this.vehicleType,
this.numberOfShipping,
});
ShippingRequestModel.fromJsonShipping(Map<String, dynamic> json, bool isForShipping) {
@ -45,6 +47,7 @@ class ShippingRequestModel {
customerName = json['customerName'];
vehicleTypeID = json['vehicleTypeID'];
vehicleType = json['vehicleType'];
numberOfShipping = json['numberOfShipping'];
}
}

@ -18,9 +18,9 @@ abstract class ShippingRepo {
Future<List<ShippingRequestModel>> getShippingRequestListByStatus({ShippingRequestStatusEnum? shippingStatusEnum, int? requestId});
Future<GenericRespModel> updateShippingRequestStatus({required ShippingRequestStatusEnum shippingStatusEnum, required int shippingRequestId, String? comment});
Future<GenericRespModel> updateShippingRequestStatus({required ShippingRequestStatusEnum shippingStatusEnum, required int shippingRequestId, String? comment, String? numberOfShipping});
Future<GenericRespModel> updateSelfPickupRequestStatus({required SelfPickupRequestStatusEnum selfPickupStatusEnum, required int shippingRequestId, String? comment});
Future<GenericRespModel> updateSelfPickupRequestStatus({required SelfPickupRequestStatusEnum selfPickupStatusEnum, required int shippingRequestId, String? comment, String? numberOfShipping});
}
class ShippingRepoImp extends ShippingRepo {
@ -78,12 +78,13 @@ class ShippingRepoImp extends ShippingRepo {
}
@override
Future<GenericRespModel> updateShippingRequestStatus({required ShippingRequestStatusEnum shippingStatusEnum, required int shippingRequestId, String? comment}) async {
Future<GenericRespModel> updateShippingRequestStatus({required ShippingRequestStatusEnum shippingStatusEnum, required int shippingRequestId, String? comment, String? numberOfShipping}) async {
String token = appState.getUser.data!.accessToken ?? "";
Map<String, String> queryParameters = {
"id": "$shippingRequestId",
"shippingStatus": "${shippingStatusEnum.getIdFromShippingStatusEnum()}",
"comment": comment ?? "",
"numberOfShipping": numberOfShipping ?? "",
};
GenericRespModel genericRespModel = await apiClient.postJsonForObject(
@ -101,12 +102,13 @@ class ShippingRepoImp extends ShippingRepo {
}
@override
Future<GenericRespModel> updateSelfPickupRequestStatus({required SelfPickupRequestStatusEnum selfPickupStatusEnum, required int shippingRequestId, String? comment}) async {
Future<GenericRespModel> updateSelfPickupRequestStatus({required SelfPickupRequestStatusEnum selfPickupStatusEnum, required int shippingRequestId, String? comment, String? numberOfShipping}) async {
String token = appState.getUser.data!.accessToken ?? "";
Map<String, String> queryParameters = {
"id": "$shippingRequestId",
"selfPickUpStatus": "${selfPickupStatusEnum.getIdFromSelfPickupStatusEnum()}",
"comment": comment ?? "",
"numberOfShipping": numberOfShipping ?? "",
};
GenericRespModel genericRespModel = await apiClient.postJsonForObject(

@ -237,6 +237,7 @@ class AdVM extends BaseVM {
bool hasMoreDataForExploreAds = true;
bool hasMoreDataForMyAds = true;
bool isLoadingMore = false;
bool isLoadingAdsDuration = false;
Future<void> fetchMoreExploreAds() async {
if (isLoadingMore) return;
@ -457,9 +458,18 @@ class AdVM extends BaseVM {
}
Future<void> getVehicleAdsDuration() async {
final countryId = AppState().getUser.data!.userInfo!.countryId;
vehicleAdsDurations = await adsRepo.getAdsDuration(countryId: countryId);
isLoadingAdsDuration = true;
notifyListeners();
try {
final countryId = AppState().getUser.data!.userInfo!.countryId;
vehicleAdsDurations = await adsRepo.getAdsDuration(countryId: countryId);
isLoadingAdsDuration = false;
notifyListeners();
} catch (e) {
isLoadingAdsDuration = false;
notifyListeners();
rethrow;
}
}
Future<void> getVehicleAdsSpecialServices({required int cityId, required int countryId, required int specialServiceType}) async {

@ -35,6 +35,20 @@ class ChatVM extends BaseVM {
HubConnection? hubConnection;
bool _isSubscribed = false; // Track if listeners are subscribed
// Store ViewModels to avoid context dependency
RequestsVM? _requestsVM;
AdVM? _adVM;
// Track if a message is currently being sent (prevents duplicate sends)
bool isSendingMessage = false;
// Method to inject ViewModels (called once from Dashboard)
void setViewModels({required RequestsVM requestsVM, required AdVM adVM}) {
_requestsVM = requestsVM;
_adVM = adVM;
logger.i("ViewModels injected into ChatVM");
}
String chatMessageText = "";
updateChatMessageText(String value) {
@ -251,7 +265,12 @@ class ChatVM extends BaseVM {
notifyListeners();
}
void _subscribeToReceiveRequestOfferMessages(BuildContext context) {
void _subscribeToReceiveRequestOfferMessages() {
if (_requestsVM == null) {
logger.e("RequestsVM not set! Cannot subscribe to request offer messages.");
return;
}
hubConnection!.on(SignalrConsts.receiveMessageRequestOffer, (List<Object?>? arguments) {
logger.i(arguments);
@ -261,9 +280,10 @@ class ChatVM extends BaseVM {
final chatMessage = ChatMessageModel.fromJson(message as Map<String, dynamic>, isForReqOfferImagesURLs: true);
chat.add(chatMessage);
}
onNewMessageReceivedForRequestOffer(messages: chat, requestsVM: context.read<RequestsVM>());
onNewMessageReceivedForRequestOffer(messages: chat, requestsVM: _requestsVM!);
// Utils.showToast(arguments.toString());
});
logger.i("Subscribed to RequestOffer messages");
}
Future<bool> markMessagesAsRead(BuildContext context, List<int> messageIds, ChatTypeEnum chatTypeEnum) async {
@ -346,13 +366,15 @@ class ChatVM extends BaseVM {
// Clear old listeners before subscribing (prevents duplicates)
_clearAllListeners();
// Subscribe to messages (only if context is available for first-time connection)
// For reconnections, we'll re-subscribe without context
if (context != null) {
_subscribeToReceiveRequestOfferMessages(context);
_subscribeToReceiveAdMessages(context);
_subscribeToReceiveGeneralMessages(context);
// Subscribe to messages (no context needed anymore!)
if (_requestsVM != null && _adVM != null) {
_subscribeToReceiveRequestOfferMessages();
_subscribeToReceiveAdMessages();
_subscribeToReceiveGeneralMessages();
_isSubscribed = true;
logger.i("All listeners subscribed successfully");
} else {
logger.w("ViewModels not injected yet. Listeners will be subscribed after injection.");
}
// Configure handlers
@ -367,11 +389,21 @@ class ChatVM extends BaseVM {
_isSubscribed = false;
});
hubConnection!.onreconnected((connectionId) {
hubConnection!.onreconnected((connectionId) async {
logger.i("onReconnected: ${connectionId.toString()}");
// Re-subscribe after automatic reconnect (SignalR's own reconnect, not ours)
// This is safe because we clear listeners in onreconnecting
_retryCount = 0; // Reset on successful reconnect
// CRITICAL FIX: Re-subscribe listeners after automatic reconnect
if (_requestsVM != null && _adVM != null) {
_clearAllListeners();
_subscribeToReceiveRequestOfferMessages();
_subscribeToReceiveAdMessages();
_subscribeToReceiveGeneralMessages();
_isSubscribed = true;
logger.i("✅ Listeners re-subscribed after automatic reconnection");
} else {
logger.e("❌ Cannot re-subscribe: ViewModels not set!");
}
});
} catch (e) {
logger.e("Error during hub connection setup: ${e.toString()}");
@ -527,83 +559,112 @@ class ChatVM extends BaseVM {
}) async {
if (chatMessageType == ChatMessageTypeEnum.freeText && message.isEmpty) return false;
// Ensure connection is established
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
logger.i("Hub Not Connected!! Attempting to connect...");
await buildHubConnection(context: context);
// Prevent duplicate sends
if (isSendingMessage) {
logger.w("Message already being sent, ignoring duplicate tap");
return false;
}
// Wait a bit for connection to establish
await Future.delayed(const Duration(milliseconds: 500));
isSendingMessage = true;
notifyListeners();
// Check again after attempt
try {
// Ensure connection is established
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
Utils.showToast("Failed to connect. Please try again.");
return false;
}
}
if (hubConnection!.state == HubConnectionState.connected) {
final userId = AppState().getUser.data!.userInfo!.userId.toString();
final name = AppState().getUser.data!.userInfo!.firstName.toString();
List<Map<String, dynamic>> requestImagesMap = [];
List<MessageImageModel> messageImages = getMessagesImageList();
if (messageImages.isNotEmpty && chatMessageType == ChatMessageTypeEnum.image) {
for (var element in messageImages) {
requestImagesMap.add(element.toJson());
logger.i("Hub Not Connected!! Attempting to connect...");
await buildHubConnection(context: context);
// Wait a bit for connection to establish
await Future.delayed(const Duration(milliseconds: 500));
// Check again after attempt
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
Utils.showToast("Failed to connect. Please try again.");
isSendingMessage = false;
notifyListeners();
return false;
}
}
final obj = <String, dynamic>{
"ReceiverUserID": receiverId,
"SenderUserID": userId,
"MessageType": chatMessageType.getIdFromChatMessageTypeEnum(),
"ChatText": chatMessageType == ChatMessageTypeEnum.image ? "${messageImages.length} Image(s)" : message,
"RequestID": requestId,
"ReqOfferID": latestOfferId,
};
if (chatMessageType == ChatMessageTypeEnum.image) {
obj.addAll({"ReqOfferImages": requestImagesMap});
}
logger.i(obj);
try {
await hubConnection!.invoke(
SignalrConsts.sendMessageRequestOffer,
args: <Object>[obj],
);
ChatMessageModel chatMessageModel = ChatMessageModel(
messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
chatText: message,
isMyMessage: true,
requestID: requestId,
senderName: name,
senderUserID: userId,
chatMessageTypeEnum: chatMessageType,
receiverUserID: receiverId,
messageImages: messageImages,
);
if (AppState().currentAppType == AppType.customer) {
int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == receiverId);
if (providerIndex != -1) {
serviceProviderOffersList[providerIndex].chatMessages!.add(chatMessageModel);
if (hubConnection!.state == HubConnectionState.connected) {
final userId = AppState().getUser.data!.userInfo!.userId.toString();
final name = AppState().getUser.data!.userInfo!.firstName.toString();
List<Map<String, dynamic>> requestImagesMap = [];
List<MessageImageModel> messageImages = getMessagesImageList();
if (messageImages.isNotEmpty && chatMessageType == ChatMessageTypeEnum.image) {
for (var element in messageImages) {
requestImagesMap.add(element.toJson());
}
} else {
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);
}
final obj = <String, dynamic>{
"ReceiverUserID": receiverId,
"SenderUserID": userId,
"MessageType": chatMessageType.getIdFromChatMessageTypeEnum(),
"ChatText": chatMessageType == ChatMessageTypeEnum.image ? "${messageImages.length} Image(s)" : message,
"RequestID": requestId,
"ReqOfferID": latestOfferId,
};
if (chatMessageType == ChatMessageTypeEnum.image) {
obj.addAll({"ReqOfferImages": requestImagesMap});
}
logger.i(obj);
try {
await hubConnection!.invoke(
SignalrConsts.sendMessageRequestOffer,
args: <Object>[obj],
);
ChatMessageModel chatMessageModel = ChatMessageModel(
messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
chatText: message,
isMyMessage: true,
requestID: requestId,
senderName: name,
senderUserID: userId,
chatMessageTypeEnum: chatMessageType,
receiverUserID: receiverId,
messageImages: messageImages,
);
if (AppState().currentAppType == AppType.customer) {
int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == receiverId);
if (providerIndex != -1) {
serviceProviderOffersList[providerIndex].chatMessages!.add(chatMessageModel);
}
} else {
// Use stored RequestsVM instead of context.read
if (_requestsVM != null) {
int providerIndex = _requestsVM!.myFilteredRequests.indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId));
log("providerIndex2:$providerIndex");
if (providerIndex != -1) {
_requestsVM!.addChatMessagesInRequestsModel(msg: chatMessageModel, index: providerIndex);
}
}
}
isSendingMessage = false;
notifyListeners();
return true;
} catch (e) {
logger.e("error in invoking SendMessage: ${e.toString()}");
Utils.showToast("Failed to send message");
isSendingMessage = false;
notifyListeners();
return false;
}
return true;
} catch (e) {
logger.e("error in invoking SendMessage: ${e.toString()}");
Utils.showToast("Failed to send message");
return false;
}
isSendingMessage = false;
notifyListeners();
return false;
} catch (e) {
logger.e("Unexpected error in onMessageSendForRequest: ${e.toString()}");
isSendingMessage = false;
notifyListeners();
return false;
}
return false;
}
List<ServiceProvidersOffers> serviceProviderOffersList = [];
@ -806,7 +867,12 @@ class ChatVM extends BaseVM {
}
}
void _subscribeToReceiveAdMessages(BuildContext context) {
void _subscribeToReceiveAdMessages() {
if (_adVM == null) {
logger.e("AdVM not set! Cannot subscribe to ad messages.");
return;
}
hubConnection!.on(SignalrConsts.receiveMessageAds, (List<Object?>? arguments) {
if (arguments == null || arguments.isEmpty) return;
List<ChatMessageModel> chat = [];
@ -818,6 +884,7 @@ class ChatVM extends BaseVM {
logger.i(arguments);
// Utils.showToast(arguments.toString());
});
logger.i("Subscribed to Ads messages");
}
Future<void> onNewMessageReceivedForAds({required List<ChatMessageModel> messages}) async {
@ -837,63 +904,89 @@ class ChatVM extends BaseVM {
}) async {
if (message.isEmpty) return false;
// Ensure connection is established
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
logger.i("Hub Not Connected!! Attempting to connect...");
await buildHubConnection(context: context);
// Prevent duplicate sends
if (isSendingMessage) {
logger.w("Message already being sent, ignoring duplicate tap");
return false;
}
// Wait a bit for connection to establish
await Future.delayed(const Duration(milliseconds: 500));
isSendingMessage = true;
notifyListeners();
// Check again after attempt
try {
// Ensure connection is established
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
Utils.showToast("Failed to connect. Please try again.");
return false;
logger.i("Hub Not Connected!! Attempting to connect...");
await buildHubConnection(context: context);
// Wait a bit for connection to establish
await Future.delayed(const Duration(milliseconds: 500));
// Check again after attempt
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
Utils.showToast("Failed to connect. Please try again.");
isSendingMessage = false;
notifyListeners();
return false;
}
}
}
if (hubConnection!.state == HubConnectionState.connected) {
final userId = AppState().getUser.data!.userInfo!.userId.toString();
final name = AppState().getUser.data!.userInfo!.firstName.toString();
final obj = <String, dynamic>{
"ReceiverUserID": receiverId,
// "SenderUserID": userId,
"MessageType": chatMessageType.getIdFromChatMessageTypeEnum(),
"ChatText": message,
"AdsID": adId,
};
logger.i("$obj");
try {
await hubConnection!.invoke(
SignalrConsts.sendMessageAds,
args: <Object>[obj],
);
ChatMessageModel chatMessageModel = ChatMessageModel(
messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
chatText: message,
isMyMessage: true,
senderName: name,
senderUserID: userId,
chatMessageTypeEnum: ChatMessageTypeEnum.freeText,
receiverUserID: receiverId,
);
currentMessagesForAds.add(chatMessageModel);
notifyListeners();
if (AppState().currentAppType == AppType.customer) {
} else {}
return true;
} catch (e) {
logger.e("error in invoking SendMessage: ${e.toString()}");
Utils.showToast("Failed to send message");
return false;
if (hubConnection!.state == HubConnectionState.connected) {
final userId = AppState().getUser.data!.userInfo!.userId.toString();
final name = AppState().getUser.data!.userInfo!.firstName.toString();
final obj = <String, dynamic>{
"ReceiverUserID": receiverId,
// "SenderUserID": userId,
"MessageType": chatMessageType.getIdFromChatMessageTypeEnum(),
"ChatText": message,
"AdsID": adId,
};
logger.i("$obj");
try {
await hubConnection!.invoke(
SignalrConsts.sendMessageAds,
args: <Object>[obj],
);
ChatMessageModel chatMessageModel = ChatMessageModel(
messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
chatText: message,
isMyMessage: true,
senderName: name,
senderUserID: userId,
chatMessageTypeEnum: ChatMessageTypeEnum.freeText,
receiverUserID: receiverId,
);
currentMessagesForAds.add(chatMessageModel);
notifyListeners();
if (AppState().currentAppType == AppType.customer) {
} else {}
isSendingMessage = false;
notifyListeners();
return true;
} catch (e) {
logger.e("error in invoking SendMessage: ${e.toString()}");
Utils.showToast("Failed to send message");
isSendingMessage = false;
notifyListeners();
return false;
}
}
isSendingMessage = false;
notifyListeners();
return false;
} catch (e) {
logger.e("Unexpected error in onTextMessageSendForAds: ${e.toString()}");
isSendingMessage = false;
notifyListeners();
return false;
}
return false;
}
// ========================General ==================
@ -934,7 +1027,8 @@ class ChatVM extends BaseVM {
notifyListeners();
}
void _subscribeToReceiveGeneralMessages(BuildContext context) {
void _subscribeToReceiveGeneralMessages() {
// General messages don't need specific VM, just storing messages
hubConnection!.on(SignalrConsts.receiveMessageGeneral, (List<Object?>? arguments) {
if (arguments == null || arguments.isEmpty) return;
List<ChatMessageModel> chat = [];
@ -946,6 +1040,7 @@ class ChatVM extends BaseVM {
logger.i(arguments);
// Utils.showToast(arguments.toString());
});
logger.i("Subscribed to General messages");
}
Future<bool> onTextMessageSendForGeneralChat({
@ -957,59 +1052,85 @@ class ChatVM extends BaseVM {
}) async {
if (message.isEmpty) return false;
// Ensure connection is established
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
logger.i("Hub Not Connected!! Attempting to connect...");
await buildHubConnection(context: context);
// Prevent duplicate sends
if (isSendingMessage) {
logger.w("Message already being sent, ignoring duplicate tap");
return false;
}
// Wait a bit for connection to establish
await Future.delayed(const Duration(milliseconds: 500));
isSendingMessage = true;
notifyListeners();
// Check again after attempt
try {
// Ensure connection is established
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
Utils.showToast("Failed to connect. Please try again.");
return false;
logger.i("Hub Not Connected!! Attempting to connect...");
await buildHubConnection(context: context);
// Wait a bit for connection to establish
await Future.delayed(const Duration(milliseconds: 500));
// Check again after attempt
if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
Utils.showToast("Failed to connect. Please try again.");
isSendingMessage = false;
notifyListeners();
return false;
}
}
}
if (hubConnection!.state == HubConnectionState.connected) {
final userId = AppState().getUser.data!.userInfo!.userId.toString();
final name = AppState().getUser.data!.userInfo!.firstName.toString();
final obj = <String, dynamic>{
"ReceiverUserID": receiverId,
"MessageType": chatMessageType.getIdFromChatMessageTypeEnum(),
"ChatText": message,
};
logger.i("$obj");
try {
await hubConnection!.invoke(
SignalrConsts.sendMessageGeneral,
args: <Object>[obj],
);
ChatMessageModel chatMessageModel = ChatMessageModel(
messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
chatText: message,
isMyMessage: true,
senderName: name,
senderUserID: userId,
chatMessageTypeEnum: ChatMessageTypeEnum.freeText,
receiverUserID: receiverId,
);
currentMessagesForGeneralChat.add(chatMessageModel);
notifyListeners();
if (AppState().currentAppType == AppType.customer) {
} else {}
return true;
} catch (e) {
logger.e("error in invoking SendMessage: ${e.toString()}");
Utils.showToast("Failed to send message");
return false;
if (hubConnection!.state == HubConnectionState.connected) {
final userId = AppState().getUser.data!.userInfo!.userId.toString();
final name = AppState().getUser.data!.userInfo!.firstName.toString();
final obj = <String, dynamic>{
"ReceiverUserID": receiverId,
"MessageType": chatMessageType.getIdFromChatMessageTypeEnum(),
"ChatText": message,
};
logger.i("$obj");
try {
await hubConnection!.invoke(
SignalrConsts.sendMessageGeneral,
args: <Object>[obj],
);
ChatMessageModel chatMessageModel = ChatMessageModel(
messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
chatText: message,
isMyMessage: true,
senderName: name,
senderUserID: userId,
chatMessageTypeEnum: ChatMessageTypeEnum.freeText,
receiverUserID: receiverId,
);
currentMessagesForGeneralChat.add(chatMessageModel);
notifyListeners();
if (AppState().currentAppType == AppType.customer) {
} else {}
isSendingMessage = false;
notifyListeners();
return true;
} catch (e) {
logger.e("error in invoking SendMessage: ${e.toString()}");
Utils.showToast("Failed to send message");
isSendingMessage = false;
notifyListeners();
return false;
}
}
isSendingMessage = false;
notifyListeners();
return false;
} catch (e) {
logger.e("Unexpected error in onTextMessageSendForGeneralChat: ${e.toString()}");
isSendingMessage = false;
notifyListeners();
return false;
}
return false;
}
}

@ -107,6 +107,9 @@ class DashboardVmCustomer extends BaseVM with WidgetsBindingObserver {
RequestsVM requestsVM = Provider.of<RequestsVM>(context, listen: false);
ChatVM chatVM = Provider.of<ChatVM>(context, listen: false);
// Inject ViewModels into ChatVM (only needs to be done once, but safe to repeat)
chatVM.setViewModels(requestsVM: requestsVM, adVM: adVM);
await appointmentsVM.populateBranchesFilterList();
await appointmentsVM.populateAppointmentsFilterList();
await adVM.populateAdsFilterList();

@ -122,6 +122,10 @@ class DashboardVMProvider extends BaseVM with WidgetsBindingObserver {
final serviceVM = context.read<ServiceVM>();
final adVM = context.read<AdVM>();
final subscriptionsVM = context.read<SubscriptionsVM>();
// Inject ViewModels into ChatVM (only needs to be done once, but safe to repeat)
chatVM.setViewModels(requestsVM: requestsVM, adVM: adVM);
await requestsVM.populateDataForRequestsFilter();
await requestsVM.populateDeliveryOptionEnums();
await appointmentVM.populateAppointmentsFilterList();

@ -85,6 +85,7 @@ class PaymentVM extends ChangeNotifier {
);
// Utils.hideLoading(context); // Commented out: matching pair with showLoading
log("isPaid: ${isPaid}");
// TODO: VERIFY THIS WITH ZAHOOR
if (!isPaid) {
// Utils.showToast(LocaleKeys.paymentFailed.tr());
@ -95,6 +96,7 @@ class PaymentVM extends ChangeNotifier {
}
onVerified();
} catch (e) {
log("verifyPayments: ${e.toString()}");
Utils.showToast(e.toString());
return;
}
@ -206,6 +208,7 @@ class PaymentVM extends ChangeNotifier {
}
void onSubscriptionPaymentSuccess(BuildContext context) {
log("onSubscriptionPaymentSuccess");
pop(context);
// context.read<SubscriptionsVM>().getSubscriptionBySP(AppState().getUser.data?.userInfo?.providerId.toString() ?? "", true);
navigateReplaceWithNameUntilRoute(context, AppRoutes.dashboard);

@ -391,8 +391,8 @@ class RequestsVM extends BaseVM {
// Initialize spare parts condition filters (All, New, Used)
if (sparePartsConditionFilterOptions.isEmpty) {
sparePartsConditionFilterOptions.add(FilterListModel(title: "All", isSelected: true, id: 3)); // All
sparePartsConditionFilterOptions.add(FilterListModel(title: "New Spare Part", isSelected: false, id: 1)); // New
sparePartsConditionFilterOptions.add(FilterListModel(title: "Used Spare Part", isSelected: false, id: 2)); // Used
sparePartsConditionFilterOptions.add(FilterListModel(title: "New", isSelected: false, id: 1)); // New
sparePartsConditionFilterOptions.add(FilterListModel(title: "Used", isSelected: false, id: 2)); // Used
}
if (vehicleBrands.isEmpty) {

@ -30,11 +30,16 @@ class ShippingManagementVM extends BaseVM {
List<FilterListModel> shippingRequestStatusesList = [];
String requestStatusComments = "";
String numberOfShipping = "";
void updateRequestStatusComments(var value) {
requestStatusComments = value;
}
void updateNumberOfShipping(var value) {
numberOfShipping = value;
}
bool isSelfPickupTapped = false;
void updateIsSelfPickupTapped(var value) {
@ -55,6 +60,7 @@ class ShippingManagementVM extends BaseVM {
}
selfPickupRequestsFilterOptions[0].isSelected = true;
requestStatusComments = "";
numberOfShipping = "";
}
Future<void> populateSelfPickupRequestFilterList() async {
@ -174,10 +180,16 @@ class ShippingManagementVM extends BaseVM {
Future<bool> onUpdateShippingStatusTapped({required BuildContext context, required ShippingRequestStatusEnum shippingStatusEnum, required int shippingRequestId}) async {
Utils.showLoading(context);
try {
GenericRespModel? genericRespModel = await shippingRepo.updateShippingRequestStatus(shippingStatusEnum: shippingStatusEnum, shippingRequestId: shippingRequestId, comment: requestStatusComments);
GenericRespModel? genericRespModel = await shippingRepo.updateShippingRequestStatus(
shippingStatusEnum: shippingStatusEnum,
shippingRequestId: shippingRequestId,
comment: requestStatusComments,
numberOfShipping: numberOfShipping.isEmpty ? null : numberOfShipping,
);
Utils.showToast(genericRespModel.message.toString());
Utils.hideLoading(context);
requestStatusComments = "";
numberOfShipping = "";
return genericRespModel.messageStatus == 1;
} catch (e) {
logger.i(e.toString());
@ -190,11 +202,16 @@ class ShippingManagementVM extends BaseVM {
Future<bool> onUpdateSelfPickupStatusTapped({required BuildContext context, required SelfPickupRequestStatusEnum selfPickupRequestStatusEnum, required int shippingRequestId}) async {
Utils.showLoading(context);
try {
GenericRespModel? genericRespModel =
await shippingRepo.updateSelfPickupRequestStatus(selfPickupStatusEnum: selfPickupRequestStatusEnum, shippingRequestId: shippingRequestId, comment: requestStatusComments);
GenericRespModel? genericRespModel = await shippingRepo.updateSelfPickupRequestStatus(
selfPickupStatusEnum: selfPickupRequestStatusEnum,
shippingRequestId: shippingRequestId,
comment: requestStatusComments,
numberOfShipping: numberOfShipping.isEmpty ? null : numberOfShipping,
);
Utils.showToast(genericRespModel.message.toString());
Utils.hideLoading(context);
requestStatusComments = "";
numberOfShipping = "";
return genericRespModel.messageStatus == 1;
} catch (e) {
logger.i(e.toString());

@ -174,8 +174,8 @@ class _AdCardState extends State<AdCard> {
children: [
ClipRRect(
child: SizedBox(
width: 100,
height: 100,
width: 80,
height: 80,
child: hasMultipleImages
? PageView.builder(
controller: _pageController,
@ -328,8 +328,8 @@ class _AdCardState extends State<AdCard> {
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.start,
children: [
(widget.adDetails.vehicle!.cityName ?? "").toText(color: MyColors.lightTextColor),
widget.adDetails.createdOn != null ? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor) : const SizedBox(),
(widget.adDetails.vehicle!.cityName ?? "").toText(color: MyColors.lightTextColor, fontSize: 8),
widget.adDetails.createdOn != null ? DateTime.parse(widget.adDetails.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor, fontSize: 8) : const SizedBox(),
],
),
],

@ -307,25 +307,34 @@ class _ChatViewState extends State<ChatView> {
],
Expanded(
flex: 1,
child: const Icon(Icons.send_rounded, color: MyColors.darkPrimaryColor, size: 30).onPress(
() async {
ChatMessageTypeEnum chatMessageTypeEnum = ChatMessageTypeEnum.freeText;
child: chatVM.isSendingMessage
? const SizedBox(
width: 30,
height: 30,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(MyColors.darkPrimaryColor),
),
)
: const Icon(Icons.send_rounded, color: MyColors.darkPrimaryColor, size: 30).onPress(
() async {
ChatMessageTypeEnum chatMessageTypeEnum = ChatMessageTypeEnum.freeText;
if (chatVM.pickedImagesForMessage.isNotEmpty) {
chatMessageTypeEnum = ChatMessageTypeEnum.image;
}
final status = await onMessageSend(chatMessageType: chatMessageTypeEnum);
if (chatVM.pickedImagesForMessage.isNotEmpty) {
chatMessageTypeEnum = ChatMessageTypeEnum.image;
}
final status = await onMessageSend(chatMessageType: chatMessageTypeEnum);
if (status) {
chatVM.scrollChatDown();
if (chatMessageTypeEnum == ChatMessageTypeEnum.freeText) {
chatVM.clearChatMessageText();
} else if (chatMessageTypeEnum == ChatMessageTypeEnum.image) {
chatVM.clearPickedImagesForMessage();
}
}
},
),
if (status) {
chatVM.scrollChatDown();
if (chatMessageTypeEnum == ChatMessageTypeEnum.freeText) {
chatVM.clearChatMessageText();
} else if (chatMessageTypeEnum == ChatMessageTypeEnum.image) {
chatVM.clearPickedImagesForMessage();
}
}
},
),
),
],
],

@ -62,124 +62,147 @@ class AdsFragment extends StatelessWidget {
).toViewOnly(context, onTap: () {
navigateWithName(context, AppRoutes.loginWithPassword, arguments: false);
}),
body: SizedBox(
width: double.infinity,
height: double.infinity,
child: Column(
children: [
16.height,
Consumer(
builder: (BuildContext context, AdVM adVM, Widget? child) {
return Column(
children: [
Row(
body: Stack(
children: [
SizedBox(
width: double.infinity,
height: double.infinity,
child: Column(
children: [
16.height,
Consumer(
builder: (BuildContext context, AdVM adVM, Widget? child) {
return Column(
children: [
Expanded(
child: ShowFillButton(
isFilled: adVM.isExploreAdsTapped,
maxHeight: 55,
fontSize: 15,
title: LocaleKeys.exploreAds.tr(),
txtColor: adVM.isExploreAdsTapped ? MyColors.white : MyColors.darkTextColor,
onPressed: () {
log("accessToken: ${AppState().getUser.data!.accessToken}");
log("AppState().getUser.data!.userInfo!.userId;: ${AppState().getUser.data!.userInfo!.userId}");
Row(
children: [
Expanded(
child: ShowFillButton(
isFilled: adVM.isExploreAdsTapped,
maxHeight: 55,
fontSize: 15,
title: LocaleKeys.exploreAds.tr(),
txtColor: adVM.isExploreAdsTapped ? MyColors.white : MyColors.darkTextColor,
onPressed: () {
log("accessToken: ${AppState().getUser.data!.accessToken}");
log("AppState().getUser.data!.userInfo!.userId;: ${AppState().getUser.data!.userInfo!.userId}");
if (adVM.myAds.isEmpty) {
adVM.getMyAds();
if (adVM.myAds.isEmpty) {
adVM.getMyAds();
}
adVM.updateIsExploreAds(true);
},
),
),
12.width,
Expanded(
child: ShowFillButton(
isFilled: !adVM.isExploreAdsTapped,
txtColor: !adVM.isExploreAdsTapped ? MyColors.white : MyColors.darkTextColor,
maxHeight: 55,
fontSize: 15,
title: LocaleKeys.myAds.tr(),
onPressed: () {
adVM.updateIsExploreAds(false);
},
).toViewOnly(context, onTap: () {
navigateWithName(context, AppRoutes.loginWithPassword, arguments: false);
}),
),
],
).horPaddingMain(),
if (adVM.isExploreAdsTapped) ...[
if (adVM.adsFiltersCounter == 0) ...[
16.height,
FiltersList(
showImages: true,
filterList: adVM.exploreAdsFilterOptions,
onFilterTapped: (index, selectedFilterId) {
adVM.applyFilterOnExploreAds(vehicleBrandId: selectedFilterId);
},
needLeftPadding: false,
).paddingOnly(left: 21),
]
] else ...[
16.height,
FiltersList(
filterList: adVM.myAdsFilterOptions,
onFilterTapped: (index, selectedFilterId) {
adVM.applyFilterOnMyAds(adPostStatusEnum: selectedFilterId.toAdPostEnum());
},
needLeftPadding: false,
).paddingOnly(left: 21),
],
],
);
},
),
16.height,
Expanded(
child: RefreshIndicator(
onRefresh: () async {
if (adVM.isExploreAdsTapped) {
int vehicleBrandId = adVM.exploreAdsFilterOptions.firstWhere((element) => element.isSelected).id;
adVM.applyFilterOnExploreAds(vehicleBrandId: vehicleBrandId);
} else {
AdPostStatus adPostStatusEnum = adVM.myAdsFilterOptions.firstWhere((element) => element.isSelected).id.toAdPostEnum();
adVM.applyFilterOnMyAds(adPostStatusEnum: adPostStatusEnum);
}
},
child: adVM.state == ViewState.busy
? const AdsListShimmer(itemCount: 5)
: AdsListWidget(
isAdsFragment: true,
isDraftAds: false,
shouldShowAdStatus: !adVM.isExploreAdsTapped,
adsList: getAdsList(adVM),
hasMoreData: adVM.isExploreAdsTapped ? adVM.hasMoreDataForExploreAds : adVM.hasMoreDataForMyAds,
onFetchMoreAds: () async {
AdPostStatus adPostStatusEnum = adVM.myAdsFilterOptions.firstWhere((element) => element.isSelected).id.toAdPostEnum();
if (adVM.isExploreAdsTapped) {
await adVM.fetchMoreExploreAds();
} else {
await adVM.fetchMoreMyAds(adsStatus: adPostStatusEnum);
}
adVM.updateIsExploreAds(true);
},
),
),
12.width,
Expanded(
child: ShowFillButton(
isFilled: !adVM.isExploreAdsTapped,
txtColor: !adVM.isExploreAdsTapped ? MyColors.white : MyColors.darkTextColor,
maxHeight: 55,
fontSize: 15,
title: LocaleKeys.myAds.tr(),
onPressed: () {
adVM.updateIsExploreAds(false);
},
).toViewOnly(context, onTap: () {
navigateWithName(context, AppRoutes.loginWithPassword, arguments: false);
}),
),
],
).horPaddingMain(),
if (adVM.isExploreAdsTapped) ...[
if (adVM.adsFiltersCounter == 0) ...[
16.height,
FiltersList(
showImages: true,
filterList: adVM.exploreAdsFilterOptions,
onFilterTapped: (index, selectedFilterId) {
adVM.applyFilterOnExploreAds(vehicleBrandId: selectedFilterId);
},
needLeftPadding: false,
).paddingOnly(left: 21),
]
] else ...[
16.height,
FiltersList(
filterList: adVM.myAdsFilterOptions,
onFilterTapped: (index, selectedFilterId) {
adVM.applyFilterOnMyAds(adPostStatusEnum: selectedFilterId.toAdPostEnum());
},
needLeftPadding: false,
).paddingOnly(left: 21),
],
],
);
},
),
),
if (adVM.isLoadingMore) ...[
const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
))
]
],
),
16.height,
Expanded(
child: RefreshIndicator(
onRefresh: () async {
if (adVM.isExploreAdsTapped) {
int vehicleBrandId = adVM.exploreAdsFilterOptions.firstWhere((element) => element.isSelected).id;
adVM.applyFilterOnExploreAds(vehicleBrandId: vehicleBrandId);
} else {
AdPostStatus adPostStatusEnum = adVM.myAdsFilterOptions.firstWhere((element) => element.isSelected).id.toAdPostEnum();
adVM.applyFilterOnMyAds(adPostStatusEnum: adPostStatusEnum);
}
},
child: adVM.state == ViewState.busy
? const AdsListShimmer(itemCount: 5)
: AdsListWidget(
isAdsFragment: true,
isDraftAds: false,
shouldShowAdStatus: !adVM.isExploreAdsTapped,
adsList: getAdsList(adVM),
hasMoreData: adVM.isExploreAdsTapped ? adVM.hasMoreDataForExploreAds : adVM.hasMoreDataForMyAds,
onFetchMoreAds: () async {
AdPostStatus adPostStatusEnum = adVM.myAdsFilterOptions.firstWhere((element) => element.isSelected).id.toAdPostEnum();
if (adVM.isExploreAdsTapped) {
await adVM.fetchMoreExploreAds();
} else {
await adVM.fetchMoreMyAds(adsStatus: adPostStatusEnum);
}
},
),
),
// Loading overlay when FAB is clicked and API is fetching
if (adVM.isLoadingAdsDuration)
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black.withOpacity(0.3),
child: const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(MyColors.darkPrimaryColor),
),
),
),
if (adVM.isLoadingMore) ...[
const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
))
]
],
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
adVM.getVehicleAdsDuration();
// Await API calls with loading state
await adVM.getVehicleAdsDuration();
// Ensure vehicle types are loaded
if (adVM.vehicleTypes.isEmpty) {
await adVM.getVehicleTypes();
}
if (AppState().userType != UserType.providerDealer) {
if (adVM.vehicleAdDurationId.selectedId == -1 && adVM.vehicleAdsDurations.isNotEmpty) {
SelectionModel selection = SelectionModel(

@ -86,9 +86,9 @@ class RequestItem extends StatelessWidget {
),
],
2.height,
request.cityName.toText(color: MyColors.lightTextColor, letterSpacing: -0.4, fontSize: 10),
request.cityName.toText(color: MyColors.lightTextColor, letterSpacing: -0.4, fontSize: 6),
if (request.createdOn != null) ...[
DateTime.parse(request.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor, letterSpacing: -0.4, fontSize: 10),
DateTime.parse(request.createdOn!).getTimeAgo().toText(color: MyColors.lightTextColor, letterSpacing: -0.4, fontSize: 6),
],
],
)

@ -121,6 +121,17 @@ class _ShippingManagementViewState extends State<ShippingManagementView> {
},
),
12.height,
// Show numberOfShipping field only when updating from pending to initiated (first update)
if (shippingRequestStatusEnum == ShippingRequestStatusEnum.pending) ...[
TxtField(
value: shippingManagementVM.numberOfShipping,
errorValue: "",
keyboardType: TextInputType.text,
hint: LocaleKeys.numberOfShipping.tr(),
onChanged: (v) => shippingManagementVM.updateNumberOfShipping(v),
),
12.height,
],
TxtField(
maxLines: 4,
value: shippingManagementVM.requestStatusComments,
@ -202,6 +213,17 @@ class _ShippingManagementViewState extends State<ShippingManagementView> {
},
),
12.height,
// Show numberOfShipping field only when updating from pending to preparingToCollect (first update)
if (selfPickupRequestStatusEnum == SelfPickupRequestStatusEnum.pending) ...[
TxtField(
value: shippingManagementVM.numberOfShipping,
errorValue: "",
keyboardType: TextInputType.text,
hint: LocaleKeys.numberOfShipping.tr(),
onChanged: (v) => shippingManagementVM.updateNumberOfShipping(v),
),
12.height,
],
TxtField(
maxLines: 4,
value: shippingManagementVM.requestStatusComments,
@ -352,6 +374,10 @@ class _ShippingManagementViewState extends State<ShippingManagementView> {
if (shippingRequest.vehicleType != null && shippingRequest.vehicleType!.isNotEmpty) ...[
("${LocaleKeys.itemType.tr()}: " "${shippingRequest.vehicleType}" ?? "").toText(color: MyColors.lightTextColor, fontSize: 12, fontWeight: MyFonts.Medium),
],
// Display numberOfShipping when not null
if (shippingRequest.numberOfShipping != null && shippingRequest.numberOfShipping!.isNotEmpty) ...[
("${LocaleKeys.numberOfShipping.tr()}: ${shippingRequest.numberOfShipping}").toText(color: MyColors.lightTextColor, fontSize: 12, fontWeight: MyFonts.Medium),
],
if (shippingRequest.comment != null && shippingRequest.comment!.isNotEmpty) ...[
("${LocaleKeys.comment.tr()}: ${shippingRequest.comment ?? ""}").toText(color: MyColors.lightTextColor, fontSize: 12, fontWeight: MyFonts.Medium),
],

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mc_common_app/classes/consts.dart';
import 'package:mc_common_app/config/routes.dart';
import 'package:mc_common_app/utils/navigator.dart';
@ -6,6 +7,8 @@ import 'package:mc_common_app/utils/utils.dart';
import 'package:mc_common_app/view_models/ad_view_model.dart';
import 'package:mc_common_app/view_models/chat_view_model.dart';
import 'package:provider/provider.dart';
import 'package:audioplayers/audioplayers.dart';
import 'dart:typed_data';
class SplashPage extends StatefulWidget {
const SplashPage({super.key});
@ -16,14 +19,29 @@ class SplashPage extends StatefulWidget {
class SplashPageState extends State<SplashPage> with WidgetsBindingObserver {
bool showFlashingLights = false;
final AudioPlayer _audioPlayer = AudioPlayer();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_playEngineSound();
startSplashTimer();
}
Future<void> _playEngineSound() async {
try {
// Load audio from package assets using rootBundle (same pattern as Image.asset)
// MyAssets.engineStartSound = "packages/mc_common_app/assets/tones/engine_start_tone.mp3"
final ByteData data = await rootBundle.load(MyAssets.engineStartSound);
final Uint8List bytes = data.buffer.asUint8List();
await _audioPlayer.play(BytesSource(bytes));
} catch (e) {
// Ignore errors if sound fails to play
debugPrint('Error playing engine sound: $e');
}
}
void startSplashTimer() {
// After 3 seconds, show the flashing lights gif
Future.delayed(const Duration(seconds: 3), () {
@ -45,6 +63,8 @@ class SplashPageState extends State<SplashPage> with WidgetsBindingObserver {
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_audioPlayer.stop();
_audioPlayer.dispose();
super.dispose();
}
@ -64,8 +84,12 @@ class SplashPageState extends State<SplashPage> with WidgetsBindingObserver {
// });
// }
navigateToNextDeciderPage() {
navigateReplaceWithName(context, AppRoutes.registerSelection);
navigateToNextDeciderPage() async {
// Stop the audio before navigating
await _audioPlayer.stop();
if (mounted) {
navigateReplaceWithName(context, AppRoutes.registerSelection);
}
}
@override

@ -25,6 +25,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
audioplayers:
dependency: "direct main"
description:
name: audioplayers
sha256: f16640453cc47487b7de72a2b28d37c7df1ac97999849f4a46d92b1d2b0f093d
url: "https://pub.dev"
source: hosted
version: "6.7.1"
audioplayers_android:
dependency: transitive
description:
name: audioplayers_android
sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605"
url: "https://pub.dev"
source: hosted
version: "5.2.1"
audioplayers_darwin:
dependency: transitive
description:
name: audioplayers_darwin
sha256: c994b3bb3a921e4904ac40e013fbc94488e824fd7c1de6326f549943b0b44a91
url: "https://pub.dev"
source: hosted
version: "6.4.0"
audioplayers_linux:
dependency: transitive
description:
name: audioplayers_linux
sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001
url: "https://pub.dev"
source: hosted
version: "4.2.1"
audioplayers_platform_interface:
dependency: transitive
description:
name: audioplayers_platform_interface
sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656"
url: "https://pub.dev"
source: hosted
version: "7.1.1"
audioplayers_web:
dependency: transitive
description:
name: audioplayers_web
sha256: "24a6f258062bd7da8cb2157e83fccb9816a08dd306cbaaa24f9813d071470545"
url: "https://pub.dev"
source: hosted
version: "5.2.1"
audioplayers_windows:
dependency: transitive
description:
name: audioplayers_windows
sha256: "95f875a96c88c3dbbcb608d4f8288e300b0113d256a81d0b3197fcc18f0dc91a"
url: "https://pub.dev"
source: hosted
version: "4.3.1"
auto_size_text:
dependency: "direct main"
description:
@ -1484,4 +1540,4 @@ packages:
version: "6.5.0"
sdks:
dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.24.0"
flutter: ">=3.35.0"

@ -43,6 +43,7 @@ dependencies:
change_app_package_name: ^1.3.0
share_plus: any
toastification: ^2.3.0
audioplayers: ^6.0.0
geolocator_android: 4.5.5
@ -78,6 +79,7 @@ flutter:
assets:
- assets/
- assets/langs/
- assets/tones/
- assets/icons/
- assets/icons/payments/
- assets/images/

Loading…
Cancel
Save