From abd1fa93756ea164fae672f26dd07a26258f0433 Mon Sep 17 00:00:00 2001 From: "Aamir.Muhammad" Date: Wed, 21 Dec 2022 16:30:02 +0300 Subject: [PATCH] chat voice message implementation --- assets/icons/chat/aac.svg | 54 ++++ assets/icons/chat/mp3.svg | 57 ++++ lib/api/chat/chat_api_client.dart | 19 +- .../chat/get_single_user_chat_list_model.dart | 58 ++-- lib/provider/chat_provider_model.dart | 269 +++++++++++++++--- lib/ui/chat/chat_bubble.dart | 134 +++++++-- lib/ui/chat/chat_detailed_screen.dart | 8 +- 7 files changed, 503 insertions(+), 96 deletions(-) create mode 100644 assets/icons/chat/aac.svg create mode 100644 assets/icons/chat/mp3.svg diff --git a/assets/icons/chat/aac.svg b/assets/icons/chat/aac.svg new file mode 100644 index 0000000..61d50bb --- /dev/null +++ b/assets/icons/chat/aac.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/chat/mp3.svg b/assets/icons/chat/mp3.svg new file mode 100644 index 0000000..ed8e31e --- /dev/null +++ b/assets/icons/chat/mp3.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/api/chat/chat_api_client.dart b/lib/api/chat/chat_api_client.dart index 2f08f2f..46dcce7 100644 --- a/lib/api/chat/chat_api_client.dart +++ b/lib/api/chat/chat_api_client.dart @@ -96,6 +96,7 @@ class ChatApiClient { } return response; } catch (e) { + getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal); throw e; } } @@ -119,7 +120,6 @@ class ChatApiClient { if (!kReleaseMode) { logger.i("res: " + response.body); } - fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body); return favoriteChatUser; } catch (e) { @@ -128,29 +128,28 @@ class ChatApiClient { } } - Future uploadMedia(String userId, File file) async { + Future uploadMedia(String userId, File file) async { + print("${ApiConsts.chatMediaImageUploadUrl}upload"); + print(AppState().chatDetails!.response!.token); dynamic request = MultipartRequest('POST', Uri.parse('${ApiConsts.chatMediaImageUploadUrl}upload')); request.fields.addAll({'userId': userId, 'fileSource': '1'}); request.files.add(await MultipartFile.fromPath('files', file.path)); request.headers.addAll({'Authorization': 'Bearer ${AppState().chatDetails!.response!.token}'}); StreamedResponse response = await request.send(); - if (!kReleaseMode) {} - return response; + String data = await response.stream.bytesToString(); + if (!kReleaseMode) { + logger.i("res: " + data); + } + return jsonDecode(data); } // Download File For Chat - Future 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( "${ApiConsts.chatMediaImageUploadUrl}download", {"fileType": fileTypeDescription, "fileName": fileName, "fileSource": 1}, token: AppState().chatDetails!.response!.token, ); - Uint8List data = Uint8List.fromList(response.bodyBytes); return data; } diff --git a/lib/models/chat/get_single_user_chat_list_model.dart b/lib/models/chat/get_single_user_chat_list_model.dart index 246a515..c585af7 100644 --- a/lib/models/chat/get_single_user_chat_list_model.dart +++ b/lib/models/chat/get_single_user_chat_list_model.dart @@ -32,7 +32,8 @@ class SingleUserChatModel { this.userChatReplyResponse, this.isReplied, this.isImageLoaded, - this.image}); + this.image, + this.voice}); int? userChatHistoryId; int? userChatHistoryLineId; @@ -58,6 +59,7 @@ class SingleUserChatModel { bool? isReplied; bool? isImageLoaded; Uint8List? image; + Uint8List? voice; factory SingleUserChatModel.fromJson(Map json) => SingleUserChatModel( userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], @@ -83,7 +85,8 @@ class SingleUserChatModel { userChatReplyResponse: json["userChatReplyResponse"] == null ? null : UserChatReplyResponse.fromJson(json["userChatReplyResponse"]), isReplied: false, isImageLoaded: false, - image: null); + image: null, + voice: null); Map toJson() => { "userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId, @@ -143,19 +146,19 @@ class FileTypeResponse { } class UserChatReplyResponse { - UserChatReplyResponse({ - this.userChatHistoryId, - this.chatEventId, - this.contant, - this.contantNo, - this.fileTypeId, - this.createdDate, - this.targetUserId, - this.targetUserName, - this.fileTypeResponse, - this.isImageLoaded, - this.image, - }); + UserChatReplyResponse( + {this.userChatHistoryId, + this.chatEventId, + this.contant, + this.contantNo, + this.fileTypeId, + this.createdDate, + this.targetUserId, + this.targetUserName, + this.fileTypeResponse, + this.isImageLoaded, + this.image, + this.voice}); int? userChatHistoryId; int? chatEventId; @@ -168,19 +171,22 @@ class UserChatReplyResponse { FileTypeResponse? fileTypeResponse; bool? isImageLoaded; Uint8List? image; + Uint8List? voice; factory UserChatReplyResponse.fromJson(Map json) => UserChatReplyResponse( - userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], - chatEventId: json["chatEventId"] == null ? null : json["chatEventId"], - contant: json["contant"] == null ? null : json["contant"], - contantNo: json["contantNo"] == null ? null : json["contantNo"], - fileTypeId: json["fileTypeId"], - createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), - targetUserId: json["targetUserId"] == null ? null : json["targetUserId"], - targetUserName: json["targetUserName"] == null ? null : json["targetUserName"], - fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), - isImageLoaded: false, - image: null); + userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], + chatEventId: json["chatEventId"] == null ? null : json["chatEventId"], + contant: json["contant"] == null ? null : json["contant"], + contantNo: json["contantNo"] == null ? null : json["contantNo"], + fileTypeId: json["fileTypeId"], + createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), + targetUserId: json["targetUserId"] == null ? null : json["targetUserId"], + targetUserName: json["targetUserName"] == null ? null : json["targetUserName"], + fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), + isImageLoaded: false, + image: null, + voice: null, + ); Map toJson() => { "userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId, diff --git a/lib/provider/chat_provider_model.dart b/lib/provider/chat_provider_model.dart index 4e1d421..974bc30 100644 --- a/lib/provider/chat_provider_model.dart +++ b/lib/provider/chat_provider_model.dart @@ -85,12 +85,13 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { void registerEvents() { chatHubConnection.on("OnUpdateUserStatusAsync", changeStatus); // chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); - // hubConnection.on("OnSeenChatUserAsync", onChatSeen); + chatHubConnection.on("OnSubmitChatAsync", OnSubmitChatAsync); chatHubConnection.on("OnUserTypingAsync", onUserTyping); chatHubConnection.on("OnUserCountAsync", userCountAsync); - // hubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow); + // chatHubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow); chatHubConnection.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered); chatHubConnection.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus); + print("Alll Registered"); } void getUserRecentChats() async { @@ -107,9 +108,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { if (favUList.response != null && recentChat.response != null) { favUsersList = favUList.response!; favUsersList.sort( - (ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo( - b.userName!.toLowerCase(), - ), + (ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase()), ); for (dynamic user in recentChat.response!) { for (dynamic favUser in favUList.response!) { @@ -230,16 +229,15 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { Future uploadAttachments(String userId, File file) async { dynamic result; try { - StreamedResponse response = await ChatApiClient().uploadMedia(userId, file); - if (response.statusCode == 200) { - result = jsonDecode(await response.stream.bytesToString()); + Object? response = await ChatApiClient().uploadMedia(userId, file); + if (response != null) { + result = response; } else { result = []; } } catch (e) { throw e; } - return result; } @@ -365,6 +363,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { ChatUser( id: data.first.currentUserId, userName: data.first.currentUserName, + email: data.first.currentUserEmail, unreadMessageCount: 0, isImageLoading: false, image: chatImages!.first.profilePicture ?? "", @@ -404,6 +403,28 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); } + void OnSubmitChatAsync(List? parameters) { + logger.d(parameters); + List 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() { searchedChats!.sort( (ChatUser a, ChatUser b) => b.unreadMessageCount!.compareTo(a.unreadMessageCount!), @@ -454,6 +475,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { return 2; case ".rar": return 2; + case ".aac": + return 13; + case ".mp3": + return 14; default: return 0; } @@ -487,6 +512,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { return "application/octet-stream"; case ".rar": return "application/octet-stream"; + case ".aac": + return "audio/aac"; + case ".mp3": + return "audio/mp3"; default: return ""; } @@ -501,11 +530,13 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { required bool isAttachment, required bool isReply, Uint8List? image, + Uint8List? voice, required bool isImageLoaded}) async { Uuid uuid = const Uuid(); String contentNo = uuid.v4(); String msg = message.text; SingleUserChatModel data = SingleUserChatModel( + userChatHistoryId: 0, chatEventId: chatEventId, chatSource: 1, contant: msg, @@ -530,7 +561,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { : null, image: image, isImageLoaded: isImageLoaded, + voice: voice, ); + print("Model data---------------------------"); + logger.d(data.toJson()); userChatHistory.insert(0, data); isFileSelected = false; isMsgReply = false; @@ -569,9 +603,11 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isReply: false, isImageLoaded: true, image: selectedFile.readAsBytesSync()); - } // normal attachemnt msg + } if (!isFileSelected && isMsgReply) { - print("Normal Text To Text Reply"); + if (kDebugMode) { + print("Normal Text To Text Reply"); + } if (message.text == null || message.text.isEmpty) { return; } @@ -723,6 +759,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { return "assets/icons/chat/zip.svg"; case ".rar": return "assets/icons/chat/zip.svg"; + case ".aac": + return "assets/icons/chat/aac.svg"; + case ".mp3": + return "assets/icons/chat/zip.mp3"; default: return "assets/images/thumb.svg"; } @@ -889,6 +929,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { String dirPath = '${appDocumentsDirectory.path}/chat_images'; if (!await Directory(dirPath).exists()) { await Directory(dirPath).create(); + await File('$dirPath/.nomedia').create(); } late File imageFile = File("$dirPath/$userID.jpg"); imageFile.writeAsBytesSync(decodedBytes); @@ -956,11 +997,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } void userTypingInvoke({required int currentUser, required int reciptUser}) async { - logger.d([reciptUser, currentUser]); await chatHubConnection.invoke("UserTypingAsync", args: [reciptUser, currentUser]); } - // Audio Recoding Work +// Audio Recoding Work Timer? _timer; int _recodeDuration = 0; bool isRecoding = false; @@ -972,13 +1012,18 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { late RecorderController recorderController; late PlayerController playerController; - //////// Audio Recoding Work //////////////////// +//////// Audio Recoding Work //////////////////// Future initAudio({required int receiverId}) async { + // final dir = Directory((Platform.isAndroid + // ? await getExternalStorageDirectory() //FOR ANDROID + // : await getApplicationSupportDirectory() //FOR IOS + // )! appDirectory = await getApplicationDocumentsDirectory(); String dirPath = '${appDirectory.path}/chat_audios'; if (!await Directory(dirPath).exists()) { await Directory(dirPath).create(); + await File('$dirPath/.nomedia').create(); } path = "$dirPath/${AppState().chatDetails!.response!.id}-$receiverID-${DateTime.now().microsecondsSinceEpoch}.aac"; recorderController = RecorderController() @@ -986,6 +1031,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { ..androidOutputFormat = AndroidOutputFormat.mpeg4 ..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC ..sampleRate = 6000 + ..updateFrequency = const Duration(milliseconds: 100) ..bitRate = 18000; playerController = PlayerController(); } @@ -1014,15 +1060,23 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } } - void _startTimer() { + Future _startTimer() async { _timer?.cancel(); - _timer = Timer.periodic(const Duration(seconds: 1), (Timer t) { + _timer = Timer.periodic(const Duration(seconds: 1), (Timer t) async { _recodeDuration++; - buildTimer(); - notifyListeners(); + if (_recodeDuration <= 59) { + applyCounter(); + } else { + pauseRecoding(); + } }); } + void applyCounter() { + buildTimer(); + notifyListeners(); + } + Future pauseRecoding() async { isPause = true; isPlaying = true; @@ -1030,27 +1084,16 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { path = await recorderController.stop(false); File file = File(path!); file.readAsBytesSync(); + path = file.path; await playerController.preparePlayer(file.path, 1.0); - // var tempDuration = _recodeDuration; - // _recodeDuration = tempDuration; _timer?.cancel(); notifyListeners(); } - void resumeRecoding() { - isPause = false; - isPlaying = false; - isRecoding = true; - recorderController.record(path); - _startTimer(); - } - Future deleteRecoding() async { _recodeDuration = 0; _timer?.cancel(); - // path = await recorderController.stop(true); - recorderController.reset(); - print(path); + recorderController.stop(true); if (path != null && path!.isNotEmpty) { File delFile = File(path!); 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 { - //recorderController.pause(); - path = await recorderController.stop(false); + if (!isPause) { + path = await recorderController.stop(false); + } if (kDebugMode) { print(path); } @@ -1110,17 +1154,156 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), voiceFile); logger.d(value); String? ext = getFileExtension(voiceFile.path); + logger.d(voiceFile.path!.split("/").last); Utils.hideLoading(context); - // sendChatToServer( - // chatEventId: 2, - // fileTypeId: getFileType(ext.toString()), - // targetUserId: targetUserId, - // targetUserName: targetUserName, - // isAttachment: true, - // chatReplyId: null, - // isReply: false, - // isImageLoaded: true, - // image: voiceFile.readAsBytesSync()); + 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: false, + voicFile: voiceFile, + ); notifyListeners(); } + + Future 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: [json.decode(chatData)]); + + if (searchedChats != null) { + dynamic contain = searchedChats!.where((ChatUser element) => element.id == targetUserId); + if (contain.isEmpty) { + List emails = []; + emails.add(await EmailImageEncryption().encrypt(val: userEmail!)); + List 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 emails = []; + emails.add(await EmailImageEncryption().encrypt(val: userEmail!)); + List 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 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, +// ); } diff --git a/lib/ui/chat/chat_bubble.dart b/lib/ui/chat/chat_bubble.dart index ed20c24..ea87450 100644 --- a/lib/ui/chat/chat_bubble.dart +++ b/lib/ui/chat/chat_bubble.dart @@ -20,8 +20,6 @@ import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:open_file/open_file.dart'; import 'package:provider/provider.dart'; -// todo: @aamir use extension methods, and use correct widgets. - class ChatBubble extends StatelessWidget { ChatBubble({Key? key, required this.dateTime, required this.cItem}) : super(key: key); 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) ClipRRect( 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 Row( children: [ 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), (cItem.contant ?? "").toText12().expanded, if (fileTypeID == 1 || fileTypeID == 5 || fileTypeID == 7 || fileTypeID == 6 || fileTypeID == 8 - //|| fileTypeID == 2 - ) const Icon(Icons.remove_red_eye, size: 16) + //|| fileTypeID == 2 + ) + const Icon(Icons.remove_red_eye, size: 16) ], ), Align( @@ -157,10 +161,7 @@ class ChatBubble extends StatelessWidget { transform: GradientRotation(.83), begin: Alignment.topRight, end: Alignment.bottomLeft, - colors: [ - MyColors.gradiantEndColor, - MyColors.gradiantStartColor, - ], + colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor], ), ), child: Column( @@ -203,7 +204,8 @@ class ChatBubble extends StatelessWidget { ], ), ), - ).paddingOnly(bottom: 7), + ).paddingOnly(bottom: 7).onPress(() { + }), if (fileTypeID == 12 || fileTypeID == 4 || fileTypeID == 3) ClipRRect( 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 Row( 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: [ + // 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: [ + // 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 { @@ -329,15 +431,15 @@ class WaveBubble extends StatelessWidget { padding: EdgeInsets.zero, margin: EdgeInsets.zero, enableSeekGesture: true, - density: 2, + density: 1, playerWaveStyle: const PlayerWaveStyle( fixedWaveColor: Colors.white, - liveWaveColor:MyColors.greenColor, + liveWaveColor: MyColors.greenColor, showTop: true, showBottom: true, waveCap: StrokeCap.round, seekLineThickness: 2, - visualizerHeight: 5, + visualizerHeight: 4, backgroundColor: Colors.transparent, ), ), diff --git a/lib/ui/chat/chat_detailed_screen.dart b/lib/ui/chat/chat_detailed_screen.dart index c10a8c6..1ec1bcf 100644 --- a/lib/ui/chat/chat_detailed_screen.dart +++ b/lib/ui/chat/chat_detailed_screen.dart @@ -144,7 +144,13 @@ class _ChatDetailScreenState extends State { ); }, ).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, fileTypeName: m.userChatHistory[i].fileTypeResponse!.fileTypeName ?? "", fileTypeID: m.userChatHistory[i].fileTypeId!, fileName: m.userChatHistory[i].contant!); }