|
|
|
@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'package:http/http.dart';
|
|
|
|
import 'package:http/http.dart';
|
|
|
|
import 'package:just_audio/just_audio.dart' as JustAudio;
|
|
|
|
import 'package:just_audio/just_audio.dart' as JustAudio;
|
|
|
|
|
|
|
|
import 'package:just_audio/just_audio.dart';
|
|
|
|
import 'package:mohem_flutter_app/api/chat/chat_api_client.dart';
|
|
|
|
import 'package:mohem_flutter_app/api/chat/chat_api_client.dart';
|
|
|
|
import 'package:mohem_flutter_app/app_state/app_state.dart';
|
|
|
|
import 'package:mohem_flutter_app/app_state/app_state.dart';
|
|
|
|
import 'package:mohem_flutter_app/classes/consts.dart';
|
|
|
|
import 'package:mohem_flutter_app/classes/consts.dart';
|
|
|
|
@ -35,22 +36,21 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
|
|
|
|
|
|
|
|
TextEditingController message = TextEditingController();
|
|
|
|
TextEditingController message = TextEditingController();
|
|
|
|
TextEditingController search = TextEditingController();
|
|
|
|
TextEditingController search = TextEditingController();
|
|
|
|
List<SingleUserChatModel> userChatHistory = [];
|
|
|
|
List<SingleUserChatModel> userChatHistory = [], repliedMsg = [];
|
|
|
|
List<ChatUser>? pChatHistory, searchedChats;
|
|
|
|
List<ChatUser>? pChatHistory, searchedChats;
|
|
|
|
String chatCID = '';
|
|
|
|
String chatCID = '';
|
|
|
|
bool isLoading = true;
|
|
|
|
bool isLoading = true;
|
|
|
|
bool isChatScreenActive = false;
|
|
|
|
bool isChatScreenActive = false;
|
|
|
|
int receiverID = 0;
|
|
|
|
int receiverID = 0;
|
|
|
|
late File selectedFile;
|
|
|
|
late File selectedFile;
|
|
|
|
bool isFileSelected = false;
|
|
|
|
|
|
|
|
String sFileType = "";
|
|
|
|
String sFileType = "";
|
|
|
|
bool isMsgReply = false;
|
|
|
|
|
|
|
|
List<SingleUserChatModel> repliedMsg = [];
|
|
|
|
|
|
|
|
List<ChatUser> favUsersList = [];
|
|
|
|
List<ChatUser> favUsersList = [];
|
|
|
|
int paginationVal = 0;
|
|
|
|
int paginationVal = 0;
|
|
|
|
bool currentUserTyping = false;
|
|
|
|
|
|
|
|
int? cTypingUserId = 0;
|
|
|
|
int? cTypingUserId = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool isTextMsg = false, isReplyMsg = false, isAttachmentMsg = false, isVoiceMsg = false;
|
|
|
|
|
|
|
|
|
|
|
|
//Chat Home Page Counter
|
|
|
|
//Chat Home Page Counter
|
|
|
|
int chatUConvCounter = 0;
|
|
|
|
int chatUConvCounter = 0;
|
|
|
|
|
|
|
|
|
|
|
|
@ -411,6 +411,9 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OnSubmitChatAsync(List<Object?>? parameters) {
|
|
|
|
void OnSubmitChatAsync(List<Object?>? parameters) {
|
|
|
|
|
|
|
|
print(isChatScreenActive);
|
|
|
|
|
|
|
|
print(receiverID);
|
|
|
|
|
|
|
|
print(isChatScreenActive);
|
|
|
|
logger.i(parameters);
|
|
|
|
logger.i(parameters);
|
|
|
|
List<SingleUserChatModel> data = [], temp = [];
|
|
|
|
List<SingleUserChatModel> data = [], temp = [];
|
|
|
|
for (dynamic msg in parameters!) {
|
|
|
|
for (dynamic msg in parameters!) {
|
|
|
|
@ -537,45 +540,56 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
required bool isAttachment,
|
|
|
|
required bool isAttachment,
|
|
|
|
required bool isReply,
|
|
|
|
required bool isReply,
|
|
|
|
Uint8List? image,
|
|
|
|
Uint8List? image,
|
|
|
|
Uint8List? voice,
|
|
|
|
required bool isImageLoaded,
|
|
|
|
required bool isImageLoaded}) async {
|
|
|
|
String? userEmail,
|
|
|
|
|
|
|
|
int? userStatus,
|
|
|
|
|
|
|
|
File? voiceFile,
|
|
|
|
|
|
|
|
required bool isVoiceAttached}) async {
|
|
|
|
Uuid uuid = const Uuid();
|
|
|
|
Uuid uuid = const Uuid();
|
|
|
|
String contentNo = uuid.v4();
|
|
|
|
String contentNo = uuid.v4();
|
|
|
|
String msg = message.text;
|
|
|
|
String msg;
|
|
|
|
|
|
|
|
if (isVoiceAttached) {
|
|
|
|
|
|
|
|
msg = voiceFile!.path.split("/").last;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
msg = message.text;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.w(jsonEncode(repliedMsg));
|
|
|
|
SingleUserChatModel data = SingleUserChatModel(
|
|
|
|
SingleUserChatModel data = SingleUserChatModel(
|
|
|
|
userChatHistoryId: 0,
|
|
|
|
userChatHistoryId: 0,
|
|
|
|
chatEventId: chatEventId,
|
|
|
|
chatEventId: chatEventId,
|
|
|
|
chatSource: 1,
|
|
|
|
chatSource: 1,
|
|
|
|
contant: msg,
|
|
|
|
contant: msg,
|
|
|
|
contantNo: contentNo,
|
|
|
|
contantNo: contentNo,
|
|
|
|
conversationId: chatCID,
|
|
|
|
conversationId: chatCID,
|
|
|
|
createdDate: DateTime.now(),
|
|
|
|
createdDate: DateTime.now(),
|
|
|
|
currentUserId: AppState().chatDetails!.response!.id,
|
|
|
|
currentUserId: AppState().chatDetails!.response!.id,
|
|
|
|
currentUserName: AppState().chatDetails!.response!.userName,
|
|
|
|
currentUserName: AppState().chatDetails!.response!.userName,
|
|
|
|
targetUserId: targetUserId,
|
|
|
|
targetUserId: targetUserId,
|
|
|
|
targetUserName: targetUserName,
|
|
|
|
targetUserName: targetUserName,
|
|
|
|
isReplied: false,
|
|
|
|
isReplied: false,
|
|
|
|
fileTypeId: fileTypeId,
|
|
|
|
fileTypeId: fileTypeId,
|
|
|
|
userChatReplyResponse: isReply ? UserChatReplyResponse.fromJson(repliedMsg.first.toJson()) : null,
|
|
|
|
userChatReplyResponse: isReply ? UserChatReplyResponse.fromJson(repliedMsg.first.toJson()) : null,
|
|
|
|
fileTypeResponse: isAttachment
|
|
|
|
fileTypeResponse: isAttachment
|
|
|
|
? FileTypeResponse(
|
|
|
|
? FileTypeResponse(
|
|
|
|
fileTypeId: fileTypeId,
|
|
|
|
fileTypeId: fileTypeId,
|
|
|
|
fileTypeName: getFileExtension(selectedFile.path).toString(),
|
|
|
|
fileTypeName: isVoiceMsg ? getFileExtension(voiceFile!.path).toString() : getFileExtension(selectedFile.path).toString(),
|
|
|
|
fileKind: "file",
|
|
|
|
fileKind: "file",
|
|
|
|
fileName: selectedFile.path.split("/").last,
|
|
|
|
fileName: isVoiceMsg ? msg : selectedFile.path.split("/").last,
|
|
|
|
fileTypeDescription: getFileTypeDescription(getFileExtension(selectedFile.path).toString()),
|
|
|
|
fileTypeDescription: isVoiceMsg ? getFileTypeDescription(getFileExtension(voiceFile!.path).toString()) : getFileTypeDescription(getFileExtension(selectedFile.path).toString()),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
: null,
|
|
|
|
: null,
|
|
|
|
image: image,
|
|
|
|
image: image,
|
|
|
|
isImageLoaded: isImageLoaded,
|
|
|
|
isImageLoaded: isImageLoaded,
|
|
|
|
voice: voice,
|
|
|
|
voice: isVoiceMsg ? voiceFile! : null,
|
|
|
|
);
|
|
|
|
voiceController: isVoiceMsg ? AudioPlayer() : null);
|
|
|
|
if (kDebugMode) {
|
|
|
|
if (kDebugMode) {
|
|
|
|
logger.i("model data: " + jsonEncode(data));
|
|
|
|
logger.i("model data: " + jsonEncode(data));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
userChatHistory.insert(0, data);
|
|
|
|
userChatHistory.insert(0, data);
|
|
|
|
isFileSelected = false;
|
|
|
|
isTextMsg = false;
|
|
|
|
isMsgReply = false;
|
|
|
|
isReplyMsg = false;
|
|
|
|
|
|
|
|
isAttachmentMsg = false;
|
|
|
|
|
|
|
|
isVoiceMsg = false;
|
|
|
|
sFileType = "";
|
|
|
|
sFileType = "";
|
|
|
|
message.clear();
|
|
|
|
message.clear();
|
|
|
|
notifyListeners();
|
|
|
|
notifyListeners();
|
|
|
|
@ -586,20 +600,55 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void sendChatMessage(BuildContext context, {required int targetUserId, required int userStatus, required String userEmail, required String targetUserName}) async {
|
|
|
|
void sendChatMessage(BuildContext context, {required int targetUserId, required int userStatus, required String userEmail, required String targetUserName}) async {
|
|
|
|
if (!isFileSelected && !isMsgReply) {
|
|
|
|
if (kDebugMode) {
|
|
|
|
if (kDebugMode) {
|
|
|
|
print("====================== Values ============================");
|
|
|
|
print("Normal Text Msg");
|
|
|
|
print("Is Text " + isTextMsg.toString());
|
|
|
|
|
|
|
|
print("isReply " + isReplyMsg.toString());
|
|
|
|
|
|
|
|
print("isAttachment " + isAttachmentMsg.toString());
|
|
|
|
|
|
|
|
print("isVoice " + isVoiceMsg.toString());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//Text
|
|
|
|
|
|
|
|
if (isTextMsg && !isAttachmentMsg && !isVoiceMsg && !isReplyMsg) {
|
|
|
|
|
|
|
|
logger.d("// Normal Text Message");
|
|
|
|
|
|
|
|
if (message.text == null || message.text.isEmpty) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sendChatToServer(
|
|
|
|
|
|
|
|
chatEventId: 1,
|
|
|
|
|
|
|
|
fileTypeId: null,
|
|
|
|
|
|
|
|
targetUserId: targetUserId,
|
|
|
|
|
|
|
|
targetUserName: targetUserName,
|
|
|
|
|
|
|
|
isAttachment: false,
|
|
|
|
|
|
|
|
chatReplyId: null,
|
|
|
|
|
|
|
|
isReply: false,
|
|
|
|
|
|
|
|
isImageLoaded: false,
|
|
|
|
|
|
|
|
image: null,
|
|
|
|
|
|
|
|
isVoiceAttached: false,
|
|
|
|
|
|
|
|
userEmail: userEmail,
|
|
|
|
|
|
|
|
userStatus: userStatus);
|
|
|
|
|
|
|
|
} else if (isTextMsg && !isAttachmentMsg && !isVoiceMsg && isReplyMsg) {
|
|
|
|
|
|
|
|
logger.d("// Text Message as Reply");
|
|
|
|
if (message.text == null || message.text.isEmpty) {
|
|
|
|
if (message.text == null || message.text.isEmpty) {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sendChatToServer(
|
|
|
|
sendChatToServer(
|
|
|
|
chatEventId: 1, fileTypeId: null, targetUserId: targetUserId, targetUserName: targetUserName, isAttachment: false, chatReplyId: null, isReply: false, isImageLoaded: false, image: null);
|
|
|
|
chatEventId: 1,
|
|
|
|
|
|
|
|
fileTypeId: null,
|
|
|
|
|
|
|
|
targetUserId: targetUserId,
|
|
|
|
|
|
|
|
targetUserName: targetUserName,
|
|
|
|
|
|
|
|
chatReplyId: repliedMsg.first.userChatHistoryId,
|
|
|
|
|
|
|
|
isAttachment: false,
|
|
|
|
|
|
|
|
isReply: true,
|
|
|
|
|
|
|
|
isImageLoaded: repliedMsg.first.isImageLoaded!,
|
|
|
|
|
|
|
|
image: repliedMsg.first.image,
|
|
|
|
|
|
|
|
isVoiceAttached: false,
|
|
|
|
|
|
|
|
voiceFile: null,
|
|
|
|
|
|
|
|
userEmail: userEmail,
|
|
|
|
|
|
|
|
userStatus: userStatus);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isFileSelected && !isMsgReply) {
|
|
|
|
// Attachment
|
|
|
|
if (kDebugMode) {
|
|
|
|
else if (!isTextMsg && isAttachmentMsg && !isVoiceMsg && !isReplyMsg) {
|
|
|
|
logger.i("Normal Attachment Msg");
|
|
|
|
logger.d("// Normal Image Message");
|
|
|
|
}
|
|
|
|
|
|
|
|
Utils.showLoading(context);
|
|
|
|
Utils.showLoading(context);
|
|
|
|
dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile);
|
|
|
|
dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile);
|
|
|
|
String? ext = getFileExtension(selectedFile.path);
|
|
|
|
String? ext = getFileExtension(selectedFile.path);
|
|
|
|
@ -613,46 +662,100 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
chatReplyId: null,
|
|
|
|
chatReplyId: null,
|
|
|
|
isReply: false,
|
|
|
|
isReply: false,
|
|
|
|
isImageLoaded: true,
|
|
|
|
isImageLoaded: true,
|
|
|
|
image: selectedFile.readAsBytesSync());
|
|
|
|
image: selectedFile.readAsBytesSync(),
|
|
|
|
|
|
|
|
isVoiceAttached: false,
|
|
|
|
|
|
|
|
userEmail: userEmail,
|
|
|
|
|
|
|
|
userStatus: userStatus);
|
|
|
|
|
|
|
|
} else if (!isTextMsg && isAttachmentMsg && !isVoiceMsg && isReplyMsg) {
|
|
|
|
|
|
|
|
logger.d("// Image as Reply Msg");
|
|
|
|
|
|
|
|
Utils.showLoading(context);
|
|
|
|
|
|
|
|
dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile);
|
|
|
|
|
|
|
|
String? ext = getFileExtension(selectedFile.path);
|
|
|
|
|
|
|
|
Utils.hideLoading(context);
|
|
|
|
|
|
|
|
sendChatToServer(
|
|
|
|
|
|
|
|
chatEventId: 2,
|
|
|
|
|
|
|
|
fileTypeId: getFileType(ext.toString()),
|
|
|
|
|
|
|
|
targetUserId: targetUserId,
|
|
|
|
|
|
|
|
targetUserName: targetUserName,
|
|
|
|
|
|
|
|
isAttachment: true,
|
|
|
|
|
|
|
|
chatReplyId: repliedMsg.first.userChatHistoryId,
|
|
|
|
|
|
|
|
isReply: true,
|
|
|
|
|
|
|
|
isImageLoaded: true,
|
|
|
|
|
|
|
|
image: selectedFile.readAsBytesSync(),
|
|
|
|
|
|
|
|
isVoiceAttached: false,
|
|
|
|
|
|
|
|
userEmail: userEmail,
|
|
|
|
|
|
|
|
userStatus: userStatus);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!isFileSelected && isMsgReply) {
|
|
|
|
//Voice
|
|
|
|
if (kDebugMode) {
|
|
|
|
|
|
|
|
print("Normal Text To Text Reply");
|
|
|
|
else if (!isTextMsg && !isAttachmentMsg && isVoiceMsg && !isReplyMsg) {
|
|
|
|
|
|
|
|
logger.d("// Normal Voice Message");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!isPause) {
|
|
|
|
|
|
|
|
path = await recorderController.stop(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (message.text == null || message.text.isEmpty) {
|
|
|
|
if (kDebugMode) {
|
|
|
|
return;
|
|
|
|
logger.i("path:" + path!);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
File voiceFile = File(path!);
|
|
|
|
|
|
|
|
voiceFile.readAsBytesSync();
|
|
|
|
|
|
|
|
_timer?.cancel();
|
|
|
|
|
|
|
|
isPause = false;
|
|
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
|
|
isRecoding = false;
|
|
|
|
|
|
|
|
Utils.showLoading(context);
|
|
|
|
|
|
|
|
dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile);
|
|
|
|
|
|
|
|
String? ext = getFileExtension(voiceFile.path);
|
|
|
|
|
|
|
|
Utils.hideLoading(context);
|
|
|
|
sendChatToServer(
|
|
|
|
sendChatToServer(
|
|
|
|
chatEventId: 1,
|
|
|
|
chatEventId: 2,
|
|
|
|
fileTypeId: null,
|
|
|
|
fileTypeId: getFileType(ext.toString()),
|
|
|
|
targetUserId: targetUserId,
|
|
|
|
targetUserId: targetUserId,
|
|
|
|
targetUserName: targetUserName,
|
|
|
|
targetUserName: targetUserName,
|
|
|
|
chatReplyId: repliedMsg.first.userChatHistoryId,
|
|
|
|
chatReplyId: null,
|
|
|
|
isAttachment: false,
|
|
|
|
isAttachment: true,
|
|
|
|
isReply: true,
|
|
|
|
isReply: isReplyMsg,
|
|
|
|
isImageLoaded: repliedMsg.first.isImageLoaded!,
|
|
|
|
isImageLoaded: false,
|
|
|
|
image: repliedMsg.first.image);
|
|
|
|
voiceFile: voiceFile,
|
|
|
|
} // reply msg over image && normal
|
|
|
|
isVoiceAttached: true,
|
|
|
|
if (isFileSelected && isMsgReply) {
|
|
|
|
userEmail: userEmail,
|
|
|
|
|
|
|
|
userStatus: userStatus);
|
|
|
|
|
|
|
|
notifyListeners();
|
|
|
|
|
|
|
|
} else if (!isTextMsg && !isAttachmentMsg && isVoiceMsg && isReplyMsg) {
|
|
|
|
|
|
|
|
logger.d("// Voice as Reply Msg");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!isPause) {
|
|
|
|
|
|
|
|
path = await recorderController.stop(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
if (kDebugMode) {
|
|
|
|
if (kDebugMode) {
|
|
|
|
print("Reply With File");
|
|
|
|
logger.i("path:" + path!);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
File voiceFile = File(path!);
|
|
|
|
|
|
|
|
voiceFile.readAsBytesSync();
|
|
|
|
|
|
|
|
_timer?.cancel();
|
|
|
|
|
|
|
|
isPause = false;
|
|
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
|
|
isRecoding = false;
|
|
|
|
|
|
|
|
|
|
|
|
Utils.showLoading(context);
|
|
|
|
Utils.showLoading(context);
|
|
|
|
dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile);
|
|
|
|
dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile);
|
|
|
|
String? ext = getFileExtension(selectedFile.path);
|
|
|
|
String? ext = getFileExtension(voiceFile.path);
|
|
|
|
Utils.hideLoading(context);
|
|
|
|
Utils.hideLoading(context);
|
|
|
|
sendChatToServer(
|
|
|
|
sendChatToServer(
|
|
|
|
chatEventId: 2,
|
|
|
|
chatEventId: 2,
|
|
|
|
fileTypeId: getFileType(ext.toString()),
|
|
|
|
fileTypeId: getFileType(ext.toString()),
|
|
|
|
targetUserId: targetUserId,
|
|
|
|
targetUserId: targetUserId,
|
|
|
|
targetUserName: targetUserName,
|
|
|
|
targetUserName: targetUserName,
|
|
|
|
|
|
|
|
chatReplyId: null,
|
|
|
|
isAttachment: true,
|
|
|
|
isAttachment: true,
|
|
|
|
chatReplyId: repliedMsg.first.userChatHistoryId,
|
|
|
|
isReply: isReplyMsg,
|
|
|
|
isReply: true,
|
|
|
|
isImageLoaded: false,
|
|
|
|
isImageLoaded: true,
|
|
|
|
voiceFile: voiceFile,
|
|
|
|
image: selectedFile.readAsBytesSync());
|
|
|
|
isVoiceAttached: true,
|
|
|
|
|
|
|
|
userEmail: userEmail,
|
|
|
|
|
|
|
|
userStatus: userStatus);
|
|
|
|
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (searchedChats != null) {
|
|
|
|
if (searchedChats != null) {
|
|
|
|
dynamic contain = searchedChats!.where((ChatUser element) => element.id == targetUserId);
|
|
|
|
dynamic contain = searchedChats!.where((ChatUser element) => element.id == targetUserId);
|
|
|
|
if (contain.isEmpty) {
|
|
|
|
if (contain.isEmpty) {
|
|
|
|
@ -676,34 +779,36 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
);
|
|
|
|
);
|
|
|
|
notifyListeners();
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
|
|
List<String> emails = [];
|
|
|
|
|
|
|
|
emails.add(await EmailImageEncryption().encrypt(val: userEmail));
|
|
|
|
|
|
|
|
List<ChatUserImageModel> chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails);
|
|
|
|
|
|
|
|
searchedChats!.add(
|
|
|
|
|
|
|
|
ChatUser(
|
|
|
|
|
|
|
|
id: targetUserId,
|
|
|
|
|
|
|
|
userName: targetUserName,
|
|
|
|
|
|
|
|
unreadMessageCount: 0,
|
|
|
|
|
|
|
|
email: userEmail,
|
|
|
|
|
|
|
|
isImageLoading: false,
|
|
|
|
|
|
|
|
image: chatImages.first.profilePicture ?? "",
|
|
|
|
|
|
|
|
isImageLoaded: true,
|
|
|
|
|
|
|
|
isTyping: false,
|
|
|
|
|
|
|
|
isFav: false,
|
|
|
|
|
|
|
|
userStatus: userStatus,
|
|
|
|
|
|
|
|
userLocalDownlaodedImage: await downloadImageLocal(chatImages.first.profilePicture, targetUserId.toString()),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
notifyListeners();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// else {
|
|
|
|
|
|
|
|
// List<String> emails = [];
|
|
|
|
|
|
|
|
// emails.add(await EmailImageEncryption().encrypt(val: userEmail));
|
|
|
|
|
|
|
|
// List<ChatUserImageModel> chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails);
|
|
|
|
|
|
|
|
// searchedChats!.add(
|
|
|
|
|
|
|
|
// ChatUser(
|
|
|
|
|
|
|
|
// id: targetUserId,
|
|
|
|
|
|
|
|
// userName: targetUserName,
|
|
|
|
|
|
|
|
// unreadMessageCount: 0,
|
|
|
|
|
|
|
|
// email: userEmail,
|
|
|
|
|
|
|
|
// isImageLoading: false,
|
|
|
|
|
|
|
|
// image: chatImages.first.profilePicture ?? "",
|
|
|
|
|
|
|
|
// isImageLoaded: true,
|
|
|
|
|
|
|
|
// isTyping: false,
|
|
|
|
|
|
|
|
// isFav: false,
|
|
|
|
|
|
|
|
// userStatus: userStatus,
|
|
|
|
|
|
|
|
// userLocalDownlaodedImage: await downloadImageLocal(chatImages.first.profilePicture, targetUserId.toString()),
|
|
|
|
|
|
|
|
// ),
|
|
|
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
// notifyListeners();
|
|
|
|
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void selectImageToUpload(BuildContext context) {
|
|
|
|
void selectImageToUpload(BuildContext context) {
|
|
|
|
ImageOptions.showImageOptionsNew(context, true, (String image, File file) async {
|
|
|
|
ImageOptions.showImageOptionsNew(context, true, (String image, File file) async {
|
|
|
|
if (checkFileSize(file.path)) {
|
|
|
|
if (checkFileSize(file.path)) {
|
|
|
|
selectedFile = file;
|
|
|
|
selectedFile = file;
|
|
|
|
isFileSelected = true;
|
|
|
|
isAttachmentMsg = true;
|
|
|
|
|
|
|
|
isTextMsg = false;
|
|
|
|
sFileType = getFileExtension(file.path)!;
|
|
|
|
sFileType = getFileExtension(file.path)!;
|
|
|
|
message.text = file.path.split("/").last;
|
|
|
|
message.text = file.path.split("/").last;
|
|
|
|
Navigator.of(context).pop();
|
|
|
|
Navigator.of(context).pop();
|
|
|
|
@ -715,7 +820,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void removeAttachment() {
|
|
|
|
void removeAttachment() {
|
|
|
|
isFileSelected = false;
|
|
|
|
isAttachmentMsg = false;
|
|
|
|
sFileType = "";
|
|
|
|
sFileType = "";
|
|
|
|
message.text = '';
|
|
|
|
message.text = '';
|
|
|
|
notifyListeners();
|
|
|
|
notifyListeners();
|
|
|
|
@ -784,14 +889,14 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
void chatReply(SingleUserChatModel data) {
|
|
|
|
void chatReply(SingleUserChatModel data) {
|
|
|
|
repliedMsg = [];
|
|
|
|
repliedMsg = [];
|
|
|
|
data.isReplied = true;
|
|
|
|
data.isReplied = true;
|
|
|
|
isMsgReply = true;
|
|
|
|
isReplyMsg = true;
|
|
|
|
repliedMsg.add(data);
|
|
|
|
repliedMsg.add(data);
|
|
|
|
notifyListeners();
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void closeMe() {
|
|
|
|
void closeMe() {
|
|
|
|
repliedMsg = [];
|
|
|
|
repliedMsg = [];
|
|
|
|
isMsgReply = false;
|
|
|
|
isReplyMsg = false;
|
|
|
|
notifyListeners();
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -841,10 +946,12 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
receiverID = 0;
|
|
|
|
receiverID = 0;
|
|
|
|
paginationVal = 0;
|
|
|
|
paginationVal = 0;
|
|
|
|
message.text = '';
|
|
|
|
message.text = '';
|
|
|
|
isFileSelected = false;
|
|
|
|
isAttachmentMsg = false;
|
|
|
|
repliedMsg = [];
|
|
|
|
repliedMsg = [];
|
|
|
|
sFileType = "";
|
|
|
|
sFileType = "";
|
|
|
|
isMsgReply = false;
|
|
|
|
isReplyMsg = false;
|
|
|
|
|
|
|
|
isTextMsg = false;
|
|
|
|
|
|
|
|
isVoiceMsg = false;
|
|
|
|
notifyListeners();
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -855,7 +962,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
receiverID = 0;
|
|
|
|
receiverID = 0;
|
|
|
|
paginationVal = 0;
|
|
|
|
paginationVal = 0;
|
|
|
|
message.text = '';
|
|
|
|
message.text = '';
|
|
|
|
isFileSelected = false;
|
|
|
|
isTextMsg = false;
|
|
|
|
|
|
|
|
isAttachmentMsg = false;
|
|
|
|
|
|
|
|
isVoiceMsg = false;
|
|
|
|
|
|
|
|
isReplyMsg = false;
|
|
|
|
repliedMsg = [];
|
|
|
|
repliedMsg = [];
|
|
|
|
sFileType = "";
|
|
|
|
sFileType = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -866,7 +976,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
receiverID = 0;
|
|
|
|
receiverID = 0;
|
|
|
|
paginationVal = 0;
|
|
|
|
paginationVal = 0;
|
|
|
|
message.text = '';
|
|
|
|
message.text = '';
|
|
|
|
isFileSelected = false;
|
|
|
|
isTextMsg = false;
|
|
|
|
|
|
|
|
isAttachmentMsg = false;
|
|
|
|
|
|
|
|
isVoiceMsg = false;
|
|
|
|
|
|
|
|
isReplyMsg = false;
|
|
|
|
repliedMsg = [];
|
|
|
|
repliedMsg = [];
|
|
|
|
sFileType = "";
|
|
|
|
sFileType = "";
|
|
|
|
deleteData();
|
|
|
|
deleteData();
|
|
|
|
@ -1052,6 +1165,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
isRecoding = false;
|
|
|
|
isRecoding = false;
|
|
|
|
isPlaying = false;
|
|
|
|
isPlaying = false;
|
|
|
|
isPause = false;
|
|
|
|
isPause = false;
|
|
|
|
|
|
|
|
isVoiceMsg = false;
|
|
|
|
recorderController.dispose();
|
|
|
|
recorderController.dispose();
|
|
|
|
playerController.dispose();
|
|
|
|
playerController.dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -1061,6 +1175,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
if (status.isDenied == true) {
|
|
|
|
if (status.isDenied == true) {
|
|
|
|
startRecoding();
|
|
|
|
startRecoding();
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
|
|
|
|
isVoiceMsg = true;
|
|
|
|
recorderController.reset();
|
|
|
|
recorderController.reset();
|
|
|
|
await recorderController.record(path);
|
|
|
|
await recorderController.record(path);
|
|
|
|
_recodeDuration = 0;
|
|
|
|
_recodeDuration = 0;
|
|
|
|
@ -1123,6 +1238,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
isPause = false;
|
|
|
|
isPause = false;
|
|
|
|
isRecoding = false;
|
|
|
|
isRecoding = false;
|
|
|
|
isPlaying = false;
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
|
|
isVoiceMsg = false;
|
|
|
|
notifyListeners();
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -1141,169 +1257,49 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
|
|
|
|
return numberStr;
|
|
|
|
return numberStr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void playRecoding() async {
|
|
|
|
// void playRecoding() async {
|
|
|
|
isPlaying = true;
|
|
|
|
// isPlaying = true;
|
|
|
|
await playerController.startPlayer(finishMode: FinishMode.pause);
|
|
|
|
// await playerController.startPlayer(finishMode: FinishMode.pause);
|
|
|
|
}
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
void playOrPause() async {
|
|
|
|
|
|
|
|
playerController.playerState == PlayerState.playing ? await playerController.pausePlayer() : playRecoding();
|
|
|
|
|
|
|
|
notifyListeners();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void sendVoiceMessage(BuildContext context, {required int targetUserId, required int userStatus, required String userEmail, required String targetUserName}) async {
|
|
|
|
|
|
|
|
if (!isPause) {
|
|
|
|
|
|
|
|
path = await recorderController.stop(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (kDebugMode) {
|
|
|
|
|
|
|
|
logger.i("path:" + path!);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
File voiceFile = File(path!);
|
|
|
|
|
|
|
|
voiceFile.readAsBytesSync();
|
|
|
|
|
|
|
|
_timer?.cancel();
|
|
|
|
|
|
|
|
isPause = false;
|
|
|
|
|
|
|
|
isPlaying = false;
|
|
|
|
|
|
|
|
isRecoding = false;
|
|
|
|
|
|
|
|
Utils.showLoading(context);
|
|
|
|
|
|
|
|
dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile);
|
|
|
|
|
|
|
|
String? ext = getFileExtension(voiceFile.path);
|
|
|
|
|
|
|
|
Utils.hideLoading(context);
|
|
|
|
|
|
|
|
sendVoiceMessageToServer(
|
|
|
|
|
|
|
|
msgText: voiceFile.path!.split("/").last,
|
|
|
|
|
|
|
|
chatEventId: 2,
|
|
|
|
|
|
|
|
fileTypeId: getFileType(ext.toString()),
|
|
|
|
|
|
|
|
targetUserId: targetUserId,
|
|
|
|
|
|
|
|
targetUserName: targetUserName,
|
|
|
|
|
|
|
|
isVoiceAttached: true,
|
|
|
|
|
|
|
|
voice: voiceFile.readAsBytesSync(),
|
|
|
|
|
|
|
|
userEmail: userEmail,
|
|
|
|
|
|
|
|
userStatus: userStatus,
|
|
|
|
|
|
|
|
chatReplyId: null,
|
|
|
|
|
|
|
|
isAttachment: true,
|
|
|
|
|
|
|
|
isReply: isMsgReply,
|
|
|
|
|
|
|
|
voiceFile: voiceFile,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
notifyListeners();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> sendVoiceMessageToServer(
|
|
|
|
|
|
|
|
{String? msgText,
|
|
|
|
|
|
|
|
int? chatEventId,
|
|
|
|
|
|
|
|
int? fileTypeId,
|
|
|
|
|
|
|
|
int? targetUserId,
|
|
|
|
|
|
|
|
String? targetUserName,
|
|
|
|
|
|
|
|
bool? isVoiceAttached,
|
|
|
|
|
|
|
|
Uint8List? voice,
|
|
|
|
|
|
|
|
String? userEmail,
|
|
|
|
|
|
|
|
int? userStatus,
|
|
|
|
|
|
|
|
bool? isReply,
|
|
|
|
|
|
|
|
bool? isAttachment,
|
|
|
|
|
|
|
|
int? chatReplyId,
|
|
|
|
|
|
|
|
File? voiceFile}) async {
|
|
|
|
|
|
|
|
Uuid uuid = const Uuid();
|
|
|
|
|
|
|
|
String contentNo = uuid.v4();
|
|
|
|
|
|
|
|
String msg = msgText!;
|
|
|
|
|
|
|
|
SingleUserChatModel data = SingleUserChatModel(
|
|
|
|
|
|
|
|
chatEventId: chatEventId,
|
|
|
|
|
|
|
|
chatSource: 1,
|
|
|
|
|
|
|
|
contant: msg,
|
|
|
|
|
|
|
|
contantNo: contentNo,
|
|
|
|
|
|
|
|
conversationId: chatCID,
|
|
|
|
|
|
|
|
createdDate: DateTime.now(),
|
|
|
|
|
|
|
|
currentUserId: AppState().chatDetails!.response!.id,
|
|
|
|
|
|
|
|
currentUserName: AppState().chatDetails!.response!.userName,
|
|
|
|
|
|
|
|
targetUserId: targetUserId,
|
|
|
|
|
|
|
|
targetUserName: targetUserName,
|
|
|
|
|
|
|
|
isReplied: false,
|
|
|
|
|
|
|
|
fileTypeId: fileTypeId,
|
|
|
|
|
|
|
|
userChatReplyResponse: isReply! ? UserChatReplyResponse.fromJson(repliedMsg.first.toJson()) : null,
|
|
|
|
|
|
|
|
fileTypeResponse: isAttachment!
|
|
|
|
|
|
|
|
? FileTypeResponse(
|
|
|
|
|
|
|
|
fileTypeId: fileTypeId,
|
|
|
|
|
|
|
|
fileTypeName: getFileExtension(voiceFile!.path).toString(),
|
|
|
|
|
|
|
|
fileKind: "file",
|
|
|
|
|
|
|
|
fileName: msgText,
|
|
|
|
|
|
|
|
fileTypeDescription: getFileTypeDescription(getFileExtension(voiceFile!.path).toString()),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
: null,
|
|
|
|
|
|
|
|
image: null,
|
|
|
|
|
|
|
|
isImageLoaded: false,
|
|
|
|
|
|
|
|
voice: voice,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
userChatHistory.insert(0, data);
|
|
|
|
|
|
|
|
notifyListeners();
|
|
|
|
|
|
|
|
String chatData =
|
|
|
|
|
|
|
|
'{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId": $fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"chatReplyId":$chatReplyId,"conversationId":"$chatCID"}';
|
|
|
|
|
|
|
|
await chatHubConnection.invoke("AddChatUserAsync", args: <Object>[json.decode(chatData)]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (searchedChats != null) {
|
|
|
|
|
|
|
|
dynamic contain = searchedChats!.where((ChatUser element) => element.id == targetUserId);
|
|
|
|
|
|
|
|
if (contain.isEmpty) {
|
|
|
|
|
|
|
|
List<String> emails = [];
|
|
|
|
|
|
|
|
emails.add(await EmailImageEncryption().encrypt(val: userEmail!));
|
|
|
|
|
|
|
|
List<ChatUserImageModel> chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails);
|
|
|
|
|
|
|
|
searchedChats!.add(
|
|
|
|
|
|
|
|
ChatUser(
|
|
|
|
|
|
|
|
id: targetUserId,
|
|
|
|
|
|
|
|
userName: targetUserName,
|
|
|
|
|
|
|
|
unreadMessageCount: 0,
|
|
|
|
|
|
|
|
email: userEmail,
|
|
|
|
|
|
|
|
isImageLoading: false,
|
|
|
|
|
|
|
|
image: chatImages.first.profilePicture ?? "",
|
|
|
|
|
|
|
|
isImageLoaded: true,
|
|
|
|
|
|
|
|
isTyping: false,
|
|
|
|
|
|
|
|
isFav: false,
|
|
|
|
|
|
|
|
userStatus: userStatus,
|
|
|
|
|
|
|
|
userLocalDownlaodedImage: await downloadImageLocal(chatImages.first.profilePicture, targetUserId.toString()),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
notifyListeners();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
List<String> emails = [];
|
|
|
|
|
|
|
|
emails.add(await EmailImageEncryption().encrypt(val: userEmail!));
|
|
|
|
|
|
|
|
List<ChatUserImageModel> chatImages = await ChatApiClient().getUsersImages(encryptedEmails: emails);
|
|
|
|
|
|
|
|
searchedChats!.add(
|
|
|
|
|
|
|
|
ChatUser(
|
|
|
|
|
|
|
|
id: targetUserId,
|
|
|
|
|
|
|
|
userName: targetUserName,
|
|
|
|
|
|
|
|
unreadMessageCount: 0,
|
|
|
|
|
|
|
|
email: userEmail,
|
|
|
|
|
|
|
|
isImageLoading: false,
|
|
|
|
|
|
|
|
image: chatImages.first.profilePicture ?? "",
|
|
|
|
|
|
|
|
isImageLoaded: true,
|
|
|
|
|
|
|
|
isTyping: false,
|
|
|
|
|
|
|
|
isFav: false,
|
|
|
|
|
|
|
|
userStatus: userStatus,
|
|
|
|
|
|
|
|
userLocalDownlaodedImage: await downloadImageLocal(chatImages.first.profilePicture, targetUserId.toString()),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
notifyListeners();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void playVoice(
|
|
|
|
void playVoice(
|
|
|
|
BuildContext context, {
|
|
|
|
BuildContext context, {
|
|
|
|
required SingleUserChatModel data,
|
|
|
|
required SingleUserChatModel data,
|
|
|
|
}) async {
|
|
|
|
}) async {
|
|
|
|
Utils.showLoading(context);
|
|
|
|
if (data.voice != null && data.voice!.existsSync()) {
|
|
|
|
Uint8List encodedString = await ChatApiClient().downloadURL(fileName: data.contant!, fileTypeDescription: getFileTypeDescription(data.fileTypeResponse!.fileTypeName ?? ""));
|
|
|
|
print("Heree");
|
|
|
|
try {
|
|
|
|
await data.voiceController!.setFilePath(data!.voice!.path);
|
|
|
|
String path = await downChatVoice(encodedString, data.fileTypeResponse!.fileTypeName ?? "", data);
|
|
|
|
await data.voiceController!.setLoopMode(LoopMode.off);
|
|
|
|
File file = File(path!);
|
|
|
|
Duration? duration = await data.voiceController!.load();
|
|
|
|
file.readAsBytesSync();
|
|
|
|
await data.voiceController!.seek(duration);
|
|
|
|
Utils.hideLoading(context);
|
|
|
|
await data.voiceController!.play();
|
|
|
|
await data.voiceController!.preparePlayer(file.path, 1.0);
|
|
|
|
} else {
|
|
|
|
data.voiceController!.startPlayer(finishMode: FinishMode.stop);
|
|
|
|
Utils.showLoading(context);
|
|
|
|
notifyListeners();
|
|
|
|
Uint8List encodedString = await ChatApiClient().downloadURL(fileName: data.contant!, fileTypeDescription: getFileTypeDescription(data.fileTypeResponse!.fileTypeName ?? ""));
|
|
|
|
} catch (e) {
|
|
|
|
try {
|
|
|
|
Utils.showToast("Cannot open file.");
|
|
|
|
String path = await downChatVoice(encodedString, data.fileTypeResponse!.fileTypeName ?? "", data);
|
|
|
|
|
|
|
|
File file = File(path!);
|
|
|
|
|
|
|
|
await file.readAsBytes();
|
|
|
|
|
|
|
|
data.voice = file;
|
|
|
|
|
|
|
|
Duration? duration = await data.voiceController!.setFilePath(file.path);
|
|
|
|
|
|
|
|
await data.voiceController!.setLoopMode(LoopMode.off);
|
|
|
|
|
|
|
|
await data.voiceController!.seek(duration);
|
|
|
|
|
|
|
|
await data.voiceController!.setVolume(1.0);
|
|
|
|
|
|
|
|
await data.voiceController!.load();
|
|
|
|
|
|
|
|
Utils.hideLoading(context);
|
|
|
|
|
|
|
|
await data.voiceController!.play();
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
Utils.showToast("Cannot open file.");
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void stopPlaying(BuildContext context, {required SingleUserChatModel data}) async {
|
|
|
|
void pausePlaying(BuildContext context, {required SingleUserChatModel data}) async {
|
|
|
|
await data.voiceController!.stopPlayer();
|
|
|
|
await data.voiceController!.pause();
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void resumePlaying(BuildContext context, {required SingleUserChatModel data}) async {
|
|
|
|
|
|
|
|
await data.voiceController!.play();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Future<String> downChatVoice(Uint8List bytes, String ext, SingleUserChatModel data) async {
|
|
|
|
Future<String> downChatVoice(Uint8List bytes, String ext, SingleUserChatModel data) async {
|
|
|
|
|