chat voice message implementation

merge-requests/116/head
Aamir Muhammad 3 years ago
parent f60394cc38
commit abd1fa9375

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#E2E5E7;" d="M128,0c-17.6,0-32,14.4-32,32v448c0,17.6,14.4,32,32,32h320c17.6,0,32-14.4,32-32V128L352,0H128z"/>
<path style="fill:#B0B7BD;" d="M384,128h96L352,0v96C352,113.6,366.4,128,384,128z"/>
<polygon style="fill:#CAD1D8;" points="480,224 384,128 480,128 "/>
<path style="fill:#F15642;" d="M416,416c0,8.8-7.2,16-16,16H48c-8.8,0-16-7.2-16-16V256c0-8.8,7.2-16,16-16h352c8.8,0,16,7.2,16,16
V416z"/>
<g>
<path style="fill:#FFFFFF;" d="M88.368,384c-4.096-2.304-6.656-6.912-4.096-12.288l36.72-71.744c3.456-6.784,12.656-7.04,15.856,0
l36.08,71.744c5.248,9.984-10.24,17.904-14.848,7.936l-5.632-11.248h-47.2l-5.52,11.248C97.712,384,92.992,384.912,88.368,384z
M143.392,351.52l-14.464-31.616l-15.744,31.616H143.392z"/>
<path style="fill:#FFFFFF;" d="M189.184,384c-4.096-2.304-6.656-6.912-4.096-12.288l36.704-71.744
c3.456-6.784,12.672-7.04,15.872,0l36.064,71.744c5.248,9.984-10.24,17.904-14.832,7.936l-5.648-11.248h-47.2l-5.504,11.248
C198.512,384,193.776,384.912,189.184,384z M244.192,351.52l-14.448-31.616l-15.728,31.616H244.192z"/>
<path style="fill:#FFFFFF;" d="M282.416,339.088c0-24.688,15.488-45.904,44.912-45.904c11.136,0,19.952,3.312,29.296,11.376
c3.456,3.184,3.84,8.832,0.384,12.4c-3.456,3.056-8.704,2.688-11.76-0.368c-5.248-5.504-10.624-7.024-17.92-7.024
c-19.696,0-29.168,13.936-29.168,29.536c0,15.872,9.344,30.464,29.168,30.464c7.296,0,14.08-2.96,19.952-8.192
c3.968-3.072,9.472-1.552,11.776,1.536c2.048,2.816,3.056,7.536-1.408,12.016c-8.96,8.336-19.696,9.984-30.336,9.984
C296.368,384.912,282.416,363.792,282.416,339.088z"/>
</g>
<path style="fill:#CAD1D8;" d="M400,432H96v16h304c8.8,0,16-7.2,16-16v-16C416,424.8,408.8,432,400,432z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#E2E5E7;" d="M128,0c-17.6,0-32,14.4-32,32v448c0,17.616,14.4,32,32,32h320c17.6,0,32-14.384,32-32V128L352,0H128z
"/>
<path style="fill:#B0B7BD;" d="M384,128h96L352,0v96C352,113.6,366.4,128,384,128z"/>
<polygon style="fill:#CAD1D8;" points="480,224 384,128 480,128 "/>
<path style="fill:#50BEE8;" d="M416,416c0,8.8-7.2,16-16,16H48c-8.8,0-16-7.2-16-16V256c0-8.8,7.2-16,16-16h352c8.8,0,16,7.2,16,16
V416z"/>
<g>
<path style="fill:#FFFFFF;" d="M117.184,327.84v47.344c0,5.632-4.592,8.832-9.216,8.832c-4.096,0-7.664-3.2-7.664-8.832v-72.032
c0-6.64,5.632-8.832,7.664-8.832c3.712,0,5.888,2.192,8.064,4.608l28.16,38l29.152-39.408c4.24-5.248,14.592-3.2,14.592,5.632
v72.032c0,5.632-3.6,8.832-7.68,8.832c-4.592,0-8.192-3.2-8.192-8.832V327.84l-21.232,26.88c-4.592,5.632-10.352,5.632-14.576,0
L117.184,327.84z"/>
<path style="fill:#FFFFFF;" d="M210.288,303.152c0-4.224,3.328-8.832,8.704-8.832h29.552c16.64,0,31.616,11.136,31.616,32.496
c0,20.224-14.976,31.472-31.616,31.472h-21.36v16.896c0,5.632-3.584,8.832-8.192,8.832c-4.224,0-8.704-3.2-8.704-8.832V303.152z
M227.168,310.448v31.856h21.36c8.576,0,15.36-7.552,15.36-15.488c0-8.96-6.784-16.368-15.36-16.368L227.168,310.448
L227.168,310.448z"/>
<path style="fill:#FFFFFF;" d="M322.064,311.472h-21.872c-10.736,0-10.096-15.984,0-15.984h39.152c7.792,0,11.376,8.96,5.632,14.72
l-21.232,19.824c15.616-1.152,27.888,10.48,27.888,24.816c0,15.728-11.136,29.168-34.544,29.168
c-10.24,0-20.336-4.224-26.224-13.44c-6.144-9.072,7.024-17.776,13.936-8.832c3.328,4.352,8.704,6.528,14.448,6.528
c7.808,0,15.488-3.328,15.488-13.44c0-13.296-16.256-11.248-25.072-10.352c-10.752,2.048-13.936-9.6-7.664-14.448L322.064,311.472z
"/>
</g>
<path style="fill:#CAD1D8;" d="M400,432H96v16h304c8.8,0,16-7.2,16-16v-16C416,424.8,408.8,432,400,432z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -96,6 +96,7 @@ class ChatApiClient {
} }
return response; return response;
} catch (e) { } catch (e) {
getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal);
throw e; throw e;
} }
} }
@ -119,7 +120,6 @@ class ChatApiClient {
if (!kReleaseMode) { if (!kReleaseMode) {
logger.i("res: " + response.body); logger.i("res: " + response.body);
} }
fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body); fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body);
return favoriteChatUser; return favoriteChatUser;
} catch (e) { } catch (e) {
@ -128,29 +128,28 @@ class ChatApiClient {
} }
} }
Future<StreamedResponse> uploadMedia(String userId, File file) async { Future<Object?> uploadMedia(String userId, File file) async {
print("${ApiConsts.chatMediaImageUploadUrl}upload");
print(AppState().chatDetails!.response!.token);
dynamic request = MultipartRequest('POST', Uri.parse('${ApiConsts.chatMediaImageUploadUrl}upload')); dynamic request = MultipartRequest('POST', Uri.parse('${ApiConsts.chatMediaImageUploadUrl}upload'));
request.fields.addAll({'userId': userId, 'fileSource': '1'}); request.fields.addAll({'userId': userId, 'fileSource': '1'});
request.files.add(await MultipartFile.fromPath('files', file.path)); request.files.add(await MultipartFile.fromPath('files', file.path));
request.headers.addAll({'Authorization': 'Bearer ${AppState().chatDetails!.response!.token}'}); request.headers.addAll({'Authorization': 'Bearer ${AppState().chatDetails!.response!.token}'});
StreamedResponse response = await request.send(); StreamedResponse response = await request.send();
if (!kReleaseMode) {} String data = await response.stream.bytesToString();
return response; if (!kReleaseMode) {
logger.i("res: " + data);
}
return jsonDecode(data);
} }
// Download File For Chat // Download File For Chat
Future<Uint8List> downloadURL({required String fileName, required String fileTypeDescription}) async { Future<Uint8List> downloadURL({required String fileName, required String fileTypeDescription}) async {
print(fileName);
print(fileTypeDescription);
print("${ApiConsts.chatMediaImageUploadUrl}download");
print(AppState().chatDetails!.response!.token);
Response response = await ApiClient().postJsonForResponse( Response response = await ApiClient().postJsonForResponse(
"${ApiConsts.chatMediaImageUploadUrl}download", "${ApiConsts.chatMediaImageUploadUrl}download",
{"fileType": fileTypeDescription, "fileName": fileName, "fileSource": 1}, {"fileType": fileTypeDescription, "fileName": fileName, "fileSource": 1},
token: AppState().chatDetails!.response!.token, token: AppState().chatDetails!.response!.token,
); );
Uint8List data = Uint8List.fromList(response.bodyBytes); Uint8List data = Uint8List.fromList(response.bodyBytes);
return data; return data;
} }

@ -32,7 +32,8 @@ class SingleUserChatModel {
this.userChatReplyResponse, this.userChatReplyResponse,
this.isReplied, this.isReplied,
this.isImageLoaded, this.isImageLoaded,
this.image}); this.image,
this.voice});
int? userChatHistoryId; int? userChatHistoryId;
int? userChatHistoryLineId; int? userChatHistoryLineId;
@ -58,6 +59,7 @@ class SingleUserChatModel {
bool? isReplied; bool? isReplied;
bool? isImageLoaded; bool? isImageLoaded;
Uint8List? image; Uint8List? image;
Uint8List? voice;
factory SingleUserChatModel.fromJson(Map<String, dynamic> json) => SingleUserChatModel( factory SingleUserChatModel.fromJson(Map<String, dynamic> json) => SingleUserChatModel(
userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"],
@ -83,7 +85,8 @@ class SingleUserChatModel {
userChatReplyResponse: json["userChatReplyResponse"] == null ? null : UserChatReplyResponse.fromJson(json["userChatReplyResponse"]), userChatReplyResponse: json["userChatReplyResponse"] == null ? null : UserChatReplyResponse.fromJson(json["userChatReplyResponse"]),
isReplied: false, isReplied: false,
isImageLoaded: false, isImageLoaded: false,
image: null); image: null,
voice: null);
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId, "userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId,
@ -143,19 +146,19 @@ class FileTypeResponse {
} }
class UserChatReplyResponse { class UserChatReplyResponse {
UserChatReplyResponse({ UserChatReplyResponse(
this.userChatHistoryId, {this.userChatHistoryId,
this.chatEventId, this.chatEventId,
this.contant, this.contant,
this.contantNo, this.contantNo,
this.fileTypeId, this.fileTypeId,
this.createdDate, this.createdDate,
this.targetUserId, this.targetUserId,
this.targetUserName, this.targetUserName,
this.fileTypeResponse, this.fileTypeResponse,
this.isImageLoaded, this.isImageLoaded,
this.image, this.image,
}); this.voice});
int? userChatHistoryId; int? userChatHistoryId;
int? chatEventId; int? chatEventId;
@ -168,19 +171,22 @@ class UserChatReplyResponse {
FileTypeResponse? fileTypeResponse; FileTypeResponse? fileTypeResponse;
bool? isImageLoaded; bool? isImageLoaded;
Uint8List? image; Uint8List? image;
Uint8List? voice;
factory UserChatReplyResponse.fromJson(Map<String, dynamic> json) => UserChatReplyResponse( factory UserChatReplyResponse.fromJson(Map<String, dynamic> json) => UserChatReplyResponse(
userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"],
chatEventId: json["chatEventId"] == null ? null : json["chatEventId"], chatEventId: json["chatEventId"] == null ? null : json["chatEventId"],
contant: json["contant"] == null ? null : json["contant"], contant: json["contant"] == null ? null : json["contant"],
contantNo: json["contantNo"] == null ? null : json["contantNo"], contantNo: json["contantNo"] == null ? null : json["contantNo"],
fileTypeId: json["fileTypeId"], fileTypeId: json["fileTypeId"],
createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]),
targetUserId: json["targetUserId"] == null ? null : json["targetUserId"], targetUserId: json["targetUserId"] == null ? null : json["targetUserId"],
targetUserName: json["targetUserName"] == null ? null : json["targetUserName"], targetUserName: json["targetUserName"] == null ? null : json["targetUserName"],
fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]),
isImageLoaded: false, isImageLoaded: false,
image: null); image: null,
voice: null,
);
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId, "userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId,

@ -85,12 +85,13 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
void registerEvents() { void registerEvents() {
chatHubConnection.on("OnUpdateUserStatusAsync", changeStatus); chatHubConnection.on("OnUpdateUserStatusAsync", changeStatus);
// chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); // chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived);
// hubConnection.on("OnSeenChatUserAsync", onChatSeen); chatHubConnection.on("OnSubmitChatAsync", OnSubmitChatAsync);
chatHubConnection.on("OnUserTypingAsync", onUserTyping); chatHubConnection.on("OnUserTypingAsync", onUserTyping);
chatHubConnection.on("OnUserCountAsync", userCountAsync); chatHubConnection.on("OnUserCountAsync", userCountAsync);
// hubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow); // chatHubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow);
chatHubConnection.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered); chatHubConnection.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered);
chatHubConnection.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus); chatHubConnection.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus);
print("Alll Registered");
} }
void getUserRecentChats() async { void getUserRecentChats() async {
@ -107,9 +108,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
if (favUList.response != null && recentChat.response != null) { if (favUList.response != null && recentChat.response != null) {
favUsersList = favUList.response!; favUsersList = favUList.response!;
favUsersList.sort( favUsersList.sort(
(ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo( (ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase()),
b.userName!.toLowerCase(),
),
); );
for (dynamic user in recentChat.response!) { for (dynamic user in recentChat.response!) {
for (dynamic favUser in favUList.response!) { for (dynamic favUser in favUList.response!) {
@ -230,16 +229,15 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
Future<dynamic> uploadAttachments(String userId, File file) async { Future<dynamic> uploadAttachments(String userId, File file) async {
dynamic result; dynamic result;
try { try {
StreamedResponse response = await ChatApiClient().uploadMedia(userId, file); Object? response = await ChatApiClient().uploadMedia(userId, file);
if (response.statusCode == 200) { if (response != null) {
result = jsonDecode(await response.stream.bytesToString()); result = response;
} else { } else {
result = []; result = [];
} }
} catch (e) { } catch (e) {
throw e; throw e;
} }
return result; return result;
} }
@ -365,6 +363,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
ChatUser( ChatUser(
id: data.first.currentUserId, id: data.first.currentUserId,
userName: data.first.currentUserName, userName: data.first.currentUserName,
email: data.first.currentUserEmail,
unreadMessageCount: 0, unreadMessageCount: 0,
isImageLoading: false, isImageLoading: false,
image: chatImages!.first.profilePicture ?? "", image: chatImages!.first.profilePicture ?? "",
@ -404,6 +403,28 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
notifyListeners(); notifyListeners();
} }
void OnSubmitChatAsync(List<Object?>? parameters) {
logger.d(parameters);
List<SingleUserChatModel> data = [], temp = [];
for (dynamic msg in parameters!) {
data = getSingleUserChatModel(jsonEncode(msg));
temp = getSingleUserChatModel(jsonEncode(msg));
data.first.targetUserId = temp.first.currentUserId;
data.first.targetUserName = temp.first.currentUserName;
data.first.targetUserEmail = temp.first.currentUserEmail;
data.first.currentUserId = temp.first.targetUserId;
data.first.currentUserName = temp.first.targetUserName;
data.first.currentUserEmail = temp.first.targetUserEmail;
}
if (isChatScreenActive && data.first.currentUserId == receiverID) {
int index = userChatHistory.indexWhere((SingleUserChatModel element) => element.userChatHistoryId == 0);
logger.d(index);
userChatHistory[index] = data.first;
}
notifyListeners();
}
void sort() { void sort() {
searchedChats!.sort( searchedChats!.sort(
(ChatUser a, ChatUser b) => b.unreadMessageCount!.compareTo(a.unreadMessageCount!), (ChatUser a, ChatUser b) => b.unreadMessageCount!.compareTo(a.unreadMessageCount!),
@ -454,6 +475,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
return 2; return 2;
case ".rar": case ".rar":
return 2; return 2;
case ".aac":
return 13;
case ".mp3":
return 14;
default: default:
return 0; return 0;
} }
@ -487,6 +512,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
return "application/octet-stream"; return "application/octet-stream";
case ".rar": case ".rar":
return "application/octet-stream"; return "application/octet-stream";
case ".aac":
return "audio/aac";
case ".mp3":
return "audio/mp3";
default: default:
return ""; return "";
} }
@ -501,11 +530,13 @@ 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}) async { required bool isImageLoaded}) async {
Uuid uuid = const Uuid(); Uuid uuid = const Uuid();
String contentNo = uuid.v4(); String contentNo = uuid.v4();
String msg = message.text; String msg = message.text;
SingleUserChatModel data = SingleUserChatModel( SingleUserChatModel data = SingleUserChatModel(
userChatHistoryId: 0,
chatEventId: chatEventId, chatEventId: chatEventId,
chatSource: 1, chatSource: 1,
contant: msg, contant: msg,
@ -530,7 +561,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
: null, : null,
image: image, image: image,
isImageLoaded: isImageLoaded, isImageLoaded: isImageLoaded,
voice: voice,
); );
print("Model data---------------------------");
logger.d(data.toJson());
userChatHistory.insert(0, data); userChatHistory.insert(0, data);
isFileSelected = false; isFileSelected = false;
isMsgReply = false; isMsgReply = false;
@ -569,9 +603,11 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
isReply: false, isReply: false,
isImageLoaded: true, isImageLoaded: true,
image: selectedFile.readAsBytesSync()); image: selectedFile.readAsBytesSync());
} // normal attachemnt msg }
if (!isFileSelected && isMsgReply) { if (!isFileSelected && isMsgReply) {
print("Normal Text To Text Reply"); if (kDebugMode) {
print("Normal Text To Text Reply");
}
if (message.text == null || message.text.isEmpty) { if (message.text == null || message.text.isEmpty) {
return; return;
} }
@ -723,6 +759,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
return "assets/icons/chat/zip.svg"; return "assets/icons/chat/zip.svg";
case ".rar": case ".rar":
return "assets/icons/chat/zip.svg"; return "assets/icons/chat/zip.svg";
case ".aac":
return "assets/icons/chat/aac.svg";
case ".mp3":
return "assets/icons/chat/zip.mp3";
default: default:
return "assets/images/thumb.svg"; return "assets/images/thumb.svg";
} }
@ -889,6 +929,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
String dirPath = '${appDocumentsDirectory.path}/chat_images'; String dirPath = '${appDocumentsDirectory.path}/chat_images';
if (!await Directory(dirPath).exists()) { if (!await Directory(dirPath).exists()) {
await Directory(dirPath).create(); await Directory(dirPath).create();
await File('$dirPath/.nomedia').create();
} }
late File imageFile = File("$dirPath/$userID.jpg"); late File imageFile = File("$dirPath/$userID.jpg");
imageFile.writeAsBytesSync(decodedBytes); imageFile.writeAsBytesSync(decodedBytes);
@ -956,11 +997,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
} }
void userTypingInvoke({required int currentUser, required int reciptUser}) async { void userTypingInvoke({required int currentUser, required int reciptUser}) async {
logger.d([reciptUser, currentUser]);
await chatHubConnection.invoke("UserTypingAsync", args: [reciptUser, currentUser]); await chatHubConnection.invoke("UserTypingAsync", args: [reciptUser, currentUser]);
} }
// Audio Recoding Work // Audio Recoding Work
Timer? _timer; Timer? _timer;
int _recodeDuration = 0; int _recodeDuration = 0;
bool isRecoding = false; bool isRecoding = false;
@ -972,13 +1012,18 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
late RecorderController recorderController; late RecorderController recorderController;
late PlayerController playerController; late PlayerController playerController;
//////// Audio Recoding Work //////////////////// //////// Audio Recoding Work ////////////////////
Future<void> initAudio({required int receiverId}) async { Future<void> initAudio({required int receiverId}) async {
// final dir = Directory((Platform.isAndroid
// ? await getExternalStorageDirectory() //FOR ANDROID
// : await getApplicationSupportDirectory() //FOR IOS
// )!
appDirectory = await getApplicationDocumentsDirectory(); appDirectory = await getApplicationDocumentsDirectory();
String dirPath = '${appDirectory.path}/chat_audios'; String dirPath = '${appDirectory.path}/chat_audios';
if (!await Directory(dirPath).exists()) { if (!await Directory(dirPath).exists()) {
await Directory(dirPath).create(); await Directory(dirPath).create();
await File('$dirPath/.nomedia').create();
} }
path = "$dirPath/${AppState().chatDetails!.response!.id}-$receiverID-${DateTime.now().microsecondsSinceEpoch}.aac"; path = "$dirPath/${AppState().chatDetails!.response!.id}-$receiverID-${DateTime.now().microsecondsSinceEpoch}.aac";
recorderController = RecorderController() recorderController = RecorderController()
@ -986,6 +1031,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
..androidOutputFormat = AndroidOutputFormat.mpeg4 ..androidOutputFormat = AndroidOutputFormat.mpeg4
..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC ..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC
..sampleRate = 6000 ..sampleRate = 6000
..updateFrequency = const Duration(milliseconds: 100)
..bitRate = 18000; ..bitRate = 18000;
playerController = PlayerController(); playerController = PlayerController();
} }
@ -1014,15 +1060,23 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
} }
} }
void _startTimer() { Future<void> _startTimer() async {
_timer?.cancel(); _timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (Timer t) { _timer = Timer.periodic(const Duration(seconds: 1), (Timer t) async {
_recodeDuration++; _recodeDuration++;
buildTimer(); if (_recodeDuration <= 59) {
notifyListeners(); applyCounter();
} else {
pauseRecoding();
}
}); });
} }
void applyCounter() {
buildTimer();
notifyListeners();
}
Future<void> pauseRecoding() async { Future<void> pauseRecoding() async {
isPause = true; isPause = true;
isPlaying = true; isPlaying = true;
@ -1030,27 +1084,16 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
path = await recorderController.stop(false); path = await recorderController.stop(false);
File file = File(path!); File file = File(path!);
file.readAsBytesSync(); file.readAsBytesSync();
path = file.path;
await playerController.preparePlayer(file.path, 1.0); await playerController.preparePlayer(file.path, 1.0);
// var tempDuration = _recodeDuration;
// _recodeDuration = tempDuration;
_timer?.cancel(); _timer?.cancel();
notifyListeners(); notifyListeners();
} }
void resumeRecoding() {
isPause = false;
isPlaying = false;
isRecoding = true;
recorderController.record(path);
_startTimer();
}
Future<void> deleteRecoding() async { Future<void> deleteRecoding() async {
_recodeDuration = 0; _recodeDuration = 0;
_timer?.cancel(); _timer?.cancel();
// path = await recorderController.stop(true); recorderController.stop(true);
recorderController.reset();
print(path);
if (path != null && path!.isNotEmpty) { if (path != null && path!.isNotEmpty) {
File delFile = File(path!); File delFile = File(path!);
double fileSizeInKB = delFile.lengthSync() / 1024; double fileSizeInKB = delFile.lengthSync() / 1024;
@ -1095,8 +1138,9 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
} }
void sendVoiceMessage(BuildContext context, {required int targetUserId, required int userStatus, required String userEmail, required String targetUserName}) async { void sendVoiceMessage(BuildContext context, {required int targetUserId, required int userStatus, required String userEmail, required String targetUserName}) async {
//recorderController.pause(); if (!isPause) {
path = await recorderController.stop(false); path = await recorderController.stop(false);
}
if (kDebugMode) { if (kDebugMode) {
print(path); print(path);
} }
@ -1110,17 +1154,156 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile); dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile);
logger.d(value); logger.d(value);
String? ext = getFileExtension(voiceFile.path); String? ext = getFileExtension(voiceFile.path);
logger.d(voiceFile.path!.split("/").last);
Utils.hideLoading(context); Utils.hideLoading(context);
// sendChatToServer( sendVoiceMessageToServer(
// chatEventId: 2, msgText: voiceFile.path!.split("/").last,
// fileTypeId: getFileType(ext.toString()), chatEventId: 2,
// targetUserId: targetUserId, fileTypeId: getFileType(ext.toString()),
// targetUserName: targetUserName, targetUserId: targetUserId,
// isAttachment: true, targetUserName: targetUserName,
// chatReplyId: null, isVoiceAttached: true,
// isReply: false, voice: voiceFile.readAsBytesSync(),
// isImageLoaded: true, userEmail: userEmail,
// image: voiceFile.readAsBytesSync()); userStatus: userStatus,
chatReplyId: null,
isAttachment: true,
isReply: false,
voicFile: voiceFile,
);
notifyListeners(); 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? voicFile}) 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(voicFile!.path).toString(),
fileKind: "file",
fileName: msgText,
fileTypeDescription: getFileTypeDescription(getFileExtension(voicFile!.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(
BuildContext context, {
required SingleUserChatModel data,
}) async {
Utils.showLoading(context);
Uint8List encodedString = await ChatApiClient().downloadURL(fileName: data.contant!, fileTypeDescription: getFileTypeDescription(data.fileTypeResponse!.fileTypeName ?? ""));
try {
String path = await downChatVoice(encodedString, data.fileTypeResponse!.fileTypeName ?? "", data);
logger.d(path);
File file = File(path!);
file.readAsBytesSync();
Utils.hideLoading(context);
await playerController.preparePlayer(file.path, 1.0);
notifyListeners();
playerController.startPlayer(finishMode: FinishMode.pause);
} catch (e) {
Utils.showToast("Cannot open file.");
}
}
Future<String> downChatVoice(Uint8List bytes, String ext, SingleUserChatModel data) async {
String dirPath = '${(await getApplicationDocumentsDirectory()).path}/chat_audios';
if (!await Directory(dirPath).exists()) {
await Directory(dirPath).create();
await File('$dirPath/.nomedia').create();
}
File file = File("$dirPath/${data.currentUserId}-${data.targetUserId}-${DateTime.now().microsecondsSinceEpoch}." + ext);
await file.writeAsBytes(bytes);
return file.path;
}
// data.scrollController.animateTo(
// data.scrollController.position.maxScrollExtent,
// duration: const Duration(milliseconds: 100),
// curve: Curves.easeOut,
// );
} }

@ -20,8 +20,6 @@ import 'package:mohem_flutter_app/widgets/bottom_sheet.dart';
import 'package:open_file/open_file.dart'; import 'package:open_file/open_file.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
// todo: @aamir use extension methods, and use correct widgets.
class ChatBubble extends StatelessWidget { class ChatBubble extends StatelessWidget {
ChatBubble({Key? key, required this.dateTime, required this.cItem}) : super(key: key); ChatBubble({Key? key, required this.dateTime, required this.cItem}) : super(key: key);
final String dateTime; final String dateTime;
@ -102,7 +100,8 @@ class ChatBubble extends StatelessWidget {
], ],
), ),
), ),
).paddingOnly(bottom: 7), ).paddingOnly(bottom: 7).onPress(() {
}),
if (fileTypeID == 12 || fileTypeID == 4 || fileTypeID == 3) if (fileTypeID == 12 || fileTypeID == 4 || fileTypeID == 3)
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(5.0), borderRadius: BorderRadius.circular(5.0),
@ -117,18 +116,23 @@ class ChatBubble extends StatelessWidget {
); );
}), }),
), ),
).paddingOnly(bottom: 4) ).paddingOnly(bottom: 4),
if (fileTypeID == 13)
currentWaveBubble(context).onPress(() {
data.playVoice(context, data: cItem);
})
else else
Row( Row(
children: [ children: [
if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8 if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8
// || fileTypeID == 2 // || fileTypeID == 2
) )
SvgPicture.asset(data.getType(fileTypeName ?? ""), height: 30, width: 22, alignment: Alignment.center, fit: BoxFit.cover).paddingOnly(left: 0, right: 10), SvgPicture.asset(data.getType(fileTypeName ?? ""), height: 30, width: 22, alignment: Alignment.center, fit: BoxFit.cover).paddingOnly(left: 0, right: 10),
(cItem.contant ?? "").toText12().expanded, (cItem.contant ?? "").toText12().expanded,
if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8 if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8
//|| fileTypeID == 2 //|| fileTypeID == 2
) const Icon(Icons.remove_red_eye, size: 16) )
const Icon(Icons.remove_red_eye, size: 16)
], ],
), ),
Align( Align(
@ -157,10 +161,7 @@ class ChatBubble extends StatelessWidget {
transform: GradientRotation(.83), transform: GradientRotation(.83),
begin: Alignment.topRight, begin: Alignment.topRight,
end: Alignment.bottomLeft, end: Alignment.bottomLeft,
colors: <Color>[ colors: <Color>[MyColors.gradiantEndColor, MyColors.gradiantStartColor],
MyColors.gradiantEndColor,
MyColors.gradiantStartColor,
],
), ),
), ),
child: Column( child: Column(
@ -203,7 +204,8 @@ class ChatBubble extends StatelessWidget {
], ],
), ),
), ),
).paddingOnly(bottom: 7), ).paddingOnly(bottom: 7).onPress(() {
}),
if (fileTypeID == 12 || fileTypeID == 4 || fileTypeID == 3) if (fileTypeID == 12 || fileTypeID == 4 || fileTypeID == 3)
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(5.0), borderRadius: BorderRadius.circular(5.0),
@ -218,7 +220,11 @@ class ChatBubble extends StatelessWidget {
); );
}), }),
), ),
).paddingOnly(bottom: 4) ).paddingOnly(bottom: 4),
if (fileTypeID == 13)
recipetWaveBubble(context).onPress(() {
data.playVoice(context, data: cItem);
})
else else
Row( Row(
children: [ children: [
@ -283,6 +289,102 @@ class ChatBubble extends StatelessWidget {
); );
} }
} }
Widget currentWaveBubble(BuildContext context) {
return Container(
margin: const EdgeInsets.all(0),
decoration: BoxDecoration(
border: Border(
left: BorderSide(width: 6, color: isCurrentUser ? MyColors.gradiantStartColor : MyColors.white),
),
color: isCurrentUser ? MyColors.black.withOpacity(0.10) : MyColors.black.withOpacity(0.30),
// gradient: const LinearGradient(
// transform: GradientRotation(.83),
// begin: Alignment.topRight,
// end: Alignment.bottomLeft,
// colors: <Color>[
// MyColors.gradiantEndColor,
// MyColors.gradiantStartColor,
// ],
// ),
),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
const Icon(
Icons.play_arrow,
color: MyColors.lightGreenColor,
).paddingAll(10),
AudioFileWaveforms(
size: Size(MediaQuery.of(context).size.width * 0.3, 10),
playerController: data.playerController,
padding: EdgeInsets.zero,
margin: EdgeInsets.zero,
enableSeekGesture: true,
density: 1,
playerWaveStyle: const PlayerWaveStyle(
fixedWaveColor: Colors.white,
liveWaveColor: MyColors.greenColor,
showTop: true,
showBottom: true,
waveCap: StrokeCap.round,
seekLineThickness: 2,
visualizerHeight: 4,
backgroundColor: Colors.transparent,
),
).expanded,
],
),
).circle(5);
}
Widget recipetWaveBubble(BuildContext context) {
return Container(
margin: const EdgeInsets.all(0),
decoration: BoxDecoration(
border: Border(
left: BorderSide(width: 6, color: isCurrentUser ? MyColors.gradiantStartColor : MyColors.white),
),
color: isCurrentUser ? MyColors.black.withOpacity(0.10) : MyColors.black.withOpacity(0.30),
// gradient: const LinearGradient(
// transform: GradientRotation(.83),
// begin: Alignment.topRight,
// end: Alignment.bottomLeft,
// colors: <Color>[
// MyColors.gradiantEndColor,
// MyColors.gradiantStartColor,
// ],
// ),
),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
const Icon(
Icons.play_arrow,
color: MyColors.white,
).paddingAll(10),
AudioFileWaveforms(
size: Size(MediaQuery.of(context).size.width * 0.3, 10),
playerController: data.playerController,
padding: EdgeInsets.zero,
margin: EdgeInsets.zero,
enableSeekGesture: true,
density: 1,
playerWaveStyle: const PlayerWaveStyle(
fixedWaveColor: Colors.white,
liveWaveColor: MyColors.greenColor,
showTop: true,
showBottom: true,
waveCap: StrokeCap.round,
seekLineThickness: 2,
visualizerHeight: 4,
backgroundColor: Colors.transparent,
),
).expanded,
],
),
).circle(5);
}
} }
class WaveBubble extends StatelessWidget { class WaveBubble extends StatelessWidget {
@ -329,15 +431,15 @@ class WaveBubble extends StatelessWidget {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
enableSeekGesture: true, enableSeekGesture: true,
density: 2, density: 1,
playerWaveStyle: const PlayerWaveStyle( playerWaveStyle: const PlayerWaveStyle(
fixedWaveColor: Colors.white, fixedWaveColor: Colors.white,
liveWaveColor:MyColors.greenColor, liveWaveColor: MyColors.greenColor,
showTop: true, showTop: true,
showBottom: true, showBottom: true,
waveCap: StrokeCap.round, waveCap: StrokeCap.round,
seekLineThickness: 2, seekLineThickness: 2,
visualizerHeight: 5, visualizerHeight: 4,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
), ),
), ),

@ -144,7 +144,13 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
); );
}, },
).onPress(() async { ).onPress(() async {
if (m.userChatHistory[i].fileTypeResponse != null) { logger.d(m.userChatHistory[i].toJson());
if (m.userChatHistory[i].fileTypeResponse != null && m.userChatHistory[i].fileTypeId! == 1 ||
m.userChatHistory[i].fileTypeId! == 5 ||
m.userChatHistory[i].fileTypeId! == 7 ||
m.userChatHistory[i].fileTypeId! == 6 ||
m.userChatHistory[i].fileTypeId! == 8 ||
m.userChatHistory[i].fileTypeId! == 2) {
m.getChatMedia(context, m.getChatMedia(context,
fileTypeName: m.userChatHistory[i].fileTypeResponse!.fileTypeName ?? "", fileTypeID: m.userChatHistory[i].fileTypeId!, fileName: m.userChatHistory[i].contant!); fileTypeName: m.userChatHistory[i].fileTypeResponse!.fileTypeName ?? "", fileTypeID: m.userChatHistory[i].fileTypeId!, fileName: m.userChatHistory[i].contant!);
} }

Loading…
Cancel
Save