Flutter高仿微信-第29篇-单聊
创始人
2024-02-10 23:15:00

Flutter高仿微信系列共59篇,从Flutter客户端、Kotlin客户端、Web服务器、数据库表结构、Xmpp即时通讯服务器、视频通话服务器、腾讯云服务器全面讲解。

详情请查看

效果图:

实现代码:

单聊包含:文本、表情、语音、图片、小视频、红包、转账、视频通话、语音通话功能,有4个widget:

home_chat_page.dart、chat_add_view.dart、chat_content_view.dart、chat_voice_view.dart

 home_chat_page.dart实现:

/*** Author : wangning* Email : maoning20080809@163.com* Date : 2022/8/24 14:48* Description : 单聊页面*/
class HomeChatPage extends StatefulWidget {String toChatId;String account = SpUtils.getString(CommonUtils.LOGIN_ACCOUNT);HomeChatPage({required this.toChatId});@override_HomeChatPageState createState() => _HomeChatPageState(toChatId);
}class _HomeChatPageState extends State with TickerProviderStateMixin {String _toChatId;_HomeChatPageState(this._toChatId);//好友账户UserBean? _otherUserBean;//我的账户UserBean? _meUserBean;List addTimeList = [];List items = [];ScrollController _controller = ScrollController();var chatEvent;//每页13条static const PAGE_SIZE = 13;//当前页var PAGE_NUM = 1;//从那一条开始(为保证最新的先显示, 先查询最后的,并且不能用desc查询)var startNum = 0;//总共多少条var CHAT_TOTAL = 0;@overridevoid initState() {super.initState();AppManager.getInstance().toChatId = _toChatId;_checkAvailable();_updateChatStatus();chatEvent = eventBus.on((chatBean) {if(mounted){setState(() {chatBean as ChatBean;items.add(chatBean);});}});chatEvent = eventBus.on((redPacketBean) {setState(() {_updateRedpacketBalance(redPacketBean);});});loadUserBean();loadAllChat();jumpToBottom(400);// 监听滚动事件_controller.addListener((){if(_controller.position.pixels>_controller.position.maxScrollExtent-40){}});WidgetsBinding.instance.addPostFrameCallback((timeStamp) {_initScreenUtil(context);});}//更新红包金额void _updateRedpacketBalance(RedPacketBean redPacketBean){LogUtils.d("home_chat_page 的金额:${redPacketBean?.position}");ChatBean chatBean = items[redPacketBean.position??0];String messageId = chatBean.messageId??"";int isClick = 1;chatBean.isClick = isClick;ChatRepository.getInstance().updateChatRedPacketStatus(messageId, isClick);setState(() {});Navigator.push(context, MaterialPageRoute(builder: (context) => ReceiveRedpacketSuccess(fromUser: widget.account, toUser: widget.toChatId, balance: "${chatBean.content}", addTime: chatBean.addTime??"",)));}@overridevoid dispose() {super.dispose();eventBus.off(chatEvent);AppManager.getInstance().toChatId = "";}final controller = TextEditingController();final FocusNode _chatContentFocus = FocusNode();@overrideWidget build(BuildContext context) {if(!isLoadMore){//每次发送消息滚动到底部0.1秒jumpToBottom(100);}return Scaffold(appBar: WnAppBar.getAppBar(context, Text("${_otherUserBean?.nickName??""}")),bottomNavigationBar: Text(""),//占用底部位置body: GestureDetector(onTap: (){_processClickBlank();},child: Container(child: Column(children: [Expanded(child: RefreshIndicator(displacement: 2,onRefresh: _onRefresh,child: ListView.builder(controller: _controller,itemBuilder: (BuildContext context, int index) {return ChatContentView(items: items,account: widget.account, chatBean: items[index], index: index, meUserBean: _meUserBean,addTimeList: addTimeList,otherUserBean: _otherUserBean, deleteCallback: (data){setState(() {items.remove(items[index]);});},clickVoiceCallback: (data){//点击播放录音,暂停后再播放for(int i = 0; i < items.length; i++){if(i == index){items[i].isPlayVoice = true;} else {items[i].isPlayVoice = false;}}setState(() {});}, refreshTransfer: (position){LogUtils.d("回调刷新:${position}");_refreshTransfer(position);},);},itemCount: items.length,)),),Divider(height: 12.0, color: Color(0xFFF7F8F8),),Container(padding: EdgeInsets.only(top: 5.0, bottom: 5.0, right: 2.0, left: 2.0),color: Color(0xFFF3F3F3),width: double.infinity,child: Row(children: [Container(margin: EdgeInsets.symmetric(horizontal: 2.0),child: IconButton(//按下语音说活icon: isPressVoice ? Image.asset("assets/chat/button_keyboard.png"):Image.asset("assets/chat/button_voice.png"),onPressed: () =>{_processPressVoice()}), //触发发送消息事件执行的函数_handleSubmitted),Expanded(child: Stack(children: [Offstage(offstage: !isPressVoice,child: ChatVoiceView(refreshMediaCallback: (type, mediaURL, thumbnailFileName, second, messageId){_refreshMedia(type, mediaURL, thumbnailFileName, mediaSecond: second, messageId: messageId);},sendMedialCallback: (type, mediaURL, second, messageId){_sendMedia(type, mediaURL, mediaSecond: second, messageId: messageId);},stopPlayVoiceCallback: (){hidePlayVoiceList();},),),Offstage(offstage: isPressVoice,child: Container(padding: EdgeInsets.only(top: 8.0, bottom: 8.0, left: 8.0),decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(5.0),),color: Colors.white),child: TextField(controller: controller,focusNode: _chatContentFocus,decoration: InputDecoration.collapsed(hintText: null),autocorrect: true,//是否自动更正autofocus: false,maxLines: 5,minLines: 1,textAlign: TextAlign.start,style: TextStyle(color: Colors.black, fontSize: 20),cursorColor: Colors.green,onTap: (){//点击编辑框jumpToBottom(400);hideEmoji = true;hideAdd = true;setState(() {});},onChanged: (value){//录入文字setState(() {if(value.length>0){hideSend = false;hideAddIcon = true;} else {hideSend = true;hideAddIcon = false;}});},onSubmitted: _handleSubmitted,enabled: true, //是否禁用),),),],),),Container(child: IconButton(icon: Image.asset("assets/chat/button_emoji.png"),onPressed: () => _processEmoji()),),Offstage(offstage: hideAddIcon,child: Container(//margin: EdgeInsets.only(right: 4.0),child: IconButton(//添加按钮icon: Image.asset("assets/chat/button_add.png"),onPressed: () => {_processAdd()}),),),Offstage(offstage: hideSend,child: Container(margin: EdgeInsets.symmetric(horizontal: 4.0),child: IconButton(//发送按钮icon: new Icon(Icons.send), //发送按钮图标onPressed: () => _handleSubmitted(controller.text)), //触发发送消息事件执行的函数_handleSubmitted),),],),),Offstage(offstage: hideAdd,child: ChatAddView(viewType: CommonUtils.VIEW_TYPE_SINGLE_CHAT,toChatId: widget.toChatId,refreshMediaCallback: (type, mediaURL, thumbnailFileName, second, messageId){_refreshMedia(type, mediaURL, thumbnailFileName, mediaSecond: second , messageId: messageId);},sendMedialCallback: (type, mediaURL, second, messageId){_sendMedia(type, mediaURL, mediaSecond: second, messageId: messageId);},refreshRedpacketAndTransfer: (type, text){_refreshRedpacketAndTransfer(type, text);},),),Offstage(offstage: hideEmoji,child: getEmojiWidget(),),],),),),);}//进入聊天页面,把聊天状态更新为已读void _updateChatStatus() async{int newMessageCount = await ChatRepository.getInstance().getAllChatUnReadByAccount(_toChatId)??0;if(newMessageCount >= 0){await ChatRepository.getInstance().updateChatReadByAccount(_toChatId);Map result = HashMap();result["from_account"] = _toChatId;eventBus.emit(BaseEvent(BaseEvent.TYPE_UPDATE_CHAT_STATUS, result: result));}}// 下拉刷新Future _onRefresh() async{//延迟0.02秒await Future.delayed(Duration(milliseconds:20),(){if(startNum >= PAGE_SIZE){startNum = CHAT_TOTAL - PAGE_SIZE * PAGE_NUM;_loadMoreData(widget.account, widget.toChatId, startNum, PAGE_SIZE);} else if(startNum > 0 && startNum < PAGE_SIZE){//不够1页数据,查询全部,然后就不能下一页_loadMoreData(widget.account, widget.toChatId, 0, startNum);startNum = 0;}});}bool isLoadMore = false;//上拉加载更多数据void _loadMoreData(String fromAccount, String toAccount, int sNum , int pageSize){isLoadMore = true;ChatRepository.getInstance().findAllChatByAccountPage(fromAccount, toAccount, sNum, pageSize).then((chatList) {if(startNum > 0){PAGE_NUM++;}Timer(Duration(milliseconds: 100),() => _controller.jumpTo(AppManager.getInstance().getHeight(context)/3));setState(() {items.insertAll(0, chatList??[]);});Timer(Duration(milliseconds: 100),() => isLoadMore = false);});}//检查状态, 如果不可以,先登录void _checkAvailable() async{var isAvailable = await XmppManager.getInstance().isAvailable();if(!isAvailable){String account = SpUtils.getString(CommonUtils.LOGIN_ACCOUNT);String password = SpUtils.getString(CommonUtils.LOGIN_PASSWORD);XmppManager.getInstance().connect(account, password);}}//加载聊天信息void loadAllChat() async {CHAT_TOTAL = await ChatRepository.getInstance().getChatCountByAccount(widget.account, widget.toChatId)??0;startNum = CHAT_TOTAL - PAGE_SIZE * PAGE_NUM;ChatRepository.getInstance().findAllChatByAccountPage(widget.account, widget.toChatId, startNum, CHAT_TOTAL).then((chatList) {if(startNum > 0){PAGE_NUM++;}setState(() {items = chatList??[];});});}//加载我的、好友信息void loadUserBean() async {_otherUserBean = await UserRepository.getInstance().findUserByAccount(_toChatId);_meUserBean = await UserRepository.getInstance().findUserByAccount(widget.account);}//发送消息_sendMessage(var message){int id = DateTime.now().millisecondsSinceEpoch;String account = SpUtils.getString(CommonUtils.LOGIN_ACCOUNT);String toJid = "${widget.toChatId}@wangning";XmppManager.getInstance().sendMessageWithType(toJid, message, "$account", id);Map result = HashMap();eventBus.emit(BaseEvent(BaseEvent.TYPE_NEW_MESSAGE, result: result));}//默认滚动到底部void jumpToBottom(int milliseconds){if (items.length > 0) {Timer(Duration(milliseconds: milliseconds),() => _controller.jumpTo(_controller.position.maxScrollExtent));}}//隐藏播放列表,停止播放录音hidePlayVoiceList(){for(int i = 0; i < items.length;i++){items[i].isPlayVoice = false;}AudioPlayer.getInstance().stop();setState(() {});}//刷新多媒体(图片、语音、小视频) (先刷新本地,然后小视频压缩完成再慢慢发送)void _refreshMedia(int type, String mediaURL, String thumbnailFileName, {int mediaSecond=0, String messageId = "" }) async {bool isNetwork = await CommonNetwork.isNetwork();if(!isNetwork) {CommonUtils.showNetworkError(context);return;}bool deleteContacts = await isDeleteContacts(widget.account, widget.toChatId);if(deleteContacts){WnBaseDialog.showAddFriendsDialog(context, widget.toChatId);return;}String addTime = WnDateUtils.getCurrentTime();//先刷新本地聊天ChatBean chatBean = ChatBean(fromAccount: widget.account, toAccount: widget.toChatId,addTime:addTime,messageId: messageId,isRead: 1);chatBean.contentType = type;if(type == CommonUtils.CHAT_CONTENT_TYPE_VOICE){chatBean.voiceLocal = mediaURL;chatBean.second = mediaSecond;//状态变更,向聊天记录中插入新记录setState(() {items.add(chatBean);});ChatRepository.getInstance().insertChat(chatBean);} else if(type == CommonUtils.CHAT_CONTENT_TYPE_IMG){chatBean.imgPathLocal = mediaURL;//状态变更,向聊天记录中插入新记录setState(() {items.add(chatBean);});ChatRepository.getInstance().insertChat(chatBean);} else if(type == CommonUtils.CHAT_CONTENT_TYPE_VIDEO){//小视频会刷新本地2次。但messageId都是一样的chatBean.videoLocal = mediaURL;chatBean.imgPathLocal = thumbnailFileName;chatBean.second = mediaSecond;ChatBean? localChatBean = await ChatRepository.getInstance().findChatByMessageId(messageId);//状态变更,向聊天记录中插入新记录if(localChatBean == null){items.add(chatBean);ChatRepository.getInstance().insertChat(chatBean);} else {chatBean.id = localChatBean.id;ChatRepository.getInstance().updateChat(chatBean);//如果已经存在,先删除在添加for(int i = 0; i < items.length; i++){ChatBean item = items[i];if(item.messageId == messageId){items.remove(item);break;}}items.add(chatBean);}setState(() {});}//LogUtils.d("滚动到底部3");jumpToBottom(100);}//发送多媒体(图片、语音、小视频)void _sendMedia(int type, String mediaURL, {int mediaSecond = 0, String messageId = ""}) async {bool isNetwork = await CommonNetwork.isNetwork();if(!isNetwork) {return;}bool deleteContacts = await isDeleteContacts(widget.account, widget.toChatId);if(deleteContacts){return;}//上传文件ChatBean serverChatBean;String message = "";ChatSendBean chatSendBean = ChatSendBean();chatSendBean.contentType = type;chatSendBean.messageId = messageId;chatSendBean.addTime = WnDateUtils.getCurrentTime();if(type == CommonUtils.CHAT_CONTENT_TYPE_IMG){//图片serverChatBean = await UploadUtils.getInstance().uploadChatImage(widget.account, widget.toChatId, mediaURL);chatSendBean.content = serverChatBean.imgPath??"";} else if(type == CommonUtils.CHAT_CONTENT_TYPE_VOICE){//语音serverChatBean = await UploadUtils.getInstance().uploadChatVoice(widget.account, widget.toChatId, mediaURL);chatSendBean.content = serverChatBean.voice??"";chatSendBean.second = mediaSecond;} else if(type == CommonUtils.CHAT_CONTENT_TYPE_VIDEO){//小视频serverChatBean = await UploadUtils.getInstance().uploadChatVideo(widget.account, widget.toChatId, mediaURL);message = "${type}${CommonUtils.CHAT_MESSAGE_SPILE}${serverChatBean.video}";chatSendBean.content = serverChatBean.video??"";chatSendBean.second = mediaSecond;} else {return ;}message = jsonEncode(chatSendBean);_sendMessage(message);}//是否隐藏文件bool hideAdd = true;//是否隐藏emoji表情bool hideEmoji = true;//是否隐藏发送按钮bool hideSend = true;//是否隐藏添加按钮bool hideAddIcon = false;//是否按下语音说话bool isPressVoice = false;//点击空白地方,隐藏文件、emojivoid _processClickBlank(){setState(() {hideAdd = true;hideEmoji = true;_chatContentFocus.unfocus();    // 失去焦点});}//按下录音void _processPressVoice(){setState(() {isPressVoice = !isPressVoice;hideEmoji = true;hideAdd = true;_processFocus();});}//点击emoji表情void _processEmoji(){setState(() {hideEmoji = !hideEmoji;isPressVoice = false;hideAdd = true;_processFocus();});}//点击+按钮void _processAdd(){setState(() {hideAdd = !hideAdd;isPressVoice = false;hideEmoji = true;_processFocus();});}//处理焦点void _processFocus(){if(!hideAdd || !hideEmoji || isPressVoice){_chatContentFocus.unfocus();    // 失去焦点} else {FocusScope.of(context).requestFocus(_chatContentFocus);     // 获取焦点}}emoticonClick(String name){controller.text = name;}///选中表情_onEmojiSelected(Emoji emoji) {controller..text += emoji.emoji..selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length));hideAddIcon = true;hideSend = false;setState(() {});}///表情删除按钮_onBackspacePressed() {controller..text = controller.text.characters.skipLast(1).toString()..selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length));if (controller.text.isNotEmpty) {setState(() {});}}//是否已经删除联系人Future isDeleteContacts(String fromAccount, String toAccount) async {bool delete = false;ContactsBean? contactsBean = await ContactsRepository.getInstance().findContactByFromOrToAccount(fromAccount, toAccount);if(contactsBean != null){delete = (contactsBean.type == ContactsBean.typeDelete);}return Future.value(delete);}//定义发送文本事件的处理函数void _handleSubmitted(String text) async {if (text.length > 0) {bool isNetwork = await CommonNetwork.isNetwork();if(!isNetwork) {CommonUtils.showNetworkError(context);return;}bool deleteContacts = await isDeleteContacts(widget.account, widget.toChatId);if(deleteContacts){WnBaseDialog.showAddFriendsDialog(context, widget.toChatId);return;}int contentType = CommonUtils.CHAT_CONTENT_TYPE_TEXT;String addTime = WnDateUtils.getCurrentTime();String messageId = UUID.getUUID();ChatSendBean chatSendBean = ChatSendBean();chatSendBean.contentType = contentType;chatSendBean.content = text;chatSendBean.addTime = addTime;chatSendBean.second = 0;chatSendBean.messageId = messageId;String message = jsonEncode(chatSendBean);_sendMessage(message);controller.clear(); //清空输入框ChatBean chatBean = ChatBean(fromAccount: widget.account, toAccount: widget.toChatId, content: text,contentType: contentType, addTime: addTime, isRead: 1, messageId: messageId);LogUtils.d("插入数据:${chatBean.toJson()}");//状态变更,向聊天记录中插入新记录setState(() {hideAddIcon = false;hideSend = true;items.add(chatBean);});await ChatRepository.getInstance().insertChat(chatBean);jumpToBottom(100);}}//Emoji表情控件Widget getEmojiWidget(){return SizedBox(height: 200.0,width: 1000.0,child: EmojiPicker(onEmojiSelected: (Category category, Emoji emoji) {_onEmojiSelected(emoji);},onBackspacePressed: _onBackspacePressed,config: const Config(columns: 7,emojiSizeMax: 25.0,verticalSpacing: 0,horizontalSpacing: 0,initCategory: Category.RECENT,bgColor: Color(0xFFF2F2F2),indicatorColor: Color(0xff65DAC5),iconColor: Colors.orange,iconColorSelected: Color(0xff65DAC5),progressIndicatorColor: Color(0xff65DAC5),backspaceColor: Color(0xff65DAC5),showRecentsTab: true,recentsLimit: 28,categoryIcons: CategoryIcons(),buttonMode: ButtonMode.MATERIAL)),);}/**刷新红包、转账*@contentType 类型*@text 内容*/void _refreshRedpacketAndTransfer(int contentType, String text) async {if (text.length > 0) {bool isNetwork = await CommonNetwork.isNetwork();if(!isNetwork) {CommonUtils.showNetworkError(context);return;}String messageId = UUID.getUUID();String addTime = WnDateUtils.getCurrentTime();ChatSendBean chatSendBean = ChatSendBean();chatSendBean.contentType = contentType;chatSendBean.content = text;chatSendBean.addTime = addTime;chatSendBean.second = 0;chatSendBean.messageId = messageId;String message = jsonEncode(chatSendBean);_sendMessage(message);controller.clear(); //清空输入框ChatBean chatBean = ChatBean(fromAccount: widget.account, toAccount: widget.toChatId, content: text,contentType: contentType, addTime: addTime, isRead: 1, messageId: messageId);await ChatRepository.getInstance().insertChat(chatBean);//状态变更,向聊天记录中插入新记录setState(() {items.add(chatBean);});jumpToBottom(100);}}//刷新转账void _refreshTransfer(int position) async {ChatBean chatBean = items[position];chatBean.isClick = 1;setState(() {});}void _initScreenUtil(BuildContext context) {ScreenUtil.init(BoxConstraints(maxWidth: MediaQuery.of(context).size.width,maxHeight: MediaQuery.of(context).size.height),designSize: const Size(375, 812),context: context);}}

chat_add_view.dart实现:

/*** Author : wangning* Email : maoning20080809@163.com* Date : 2022/9/24 14:46* Description : 聊天点击+按钮* 1单聊支持:相册、拍照、视频通话、语音通话、红包、转账* 2群聊支持:相册、拍照*/
class ChatAddView extends StatefulWidget{//刷新列表final refreshMediaCallback;//发送信息final sendMedialCallback;//聊天idfinal String toChatId;//刷新红包、转账final refreshRedpacketAndTransfer;//1单聊, 2群聊final int viewType;ChatAddView({required this.viewType, required this.toChatId, required this.refreshMediaCallback, required this.sendMedialCallback, required this.refreshRedpacketAndTransfer});@overrideState createState() => _ChatAddState();}class _ChatAddState extends State{@overrideWidget build(BuildContext context) {return getAddWidget();}//相册List ablums = [CommonUtils.getBaseIconUrlPng("wc_chat_album_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_album_selected")];int ablumsPosition = 0;//拍照List takePhotos = [CommonUtils.getBaseIconUrlPng("wc_chat_video_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_video_selected")];int takePhotosPosition = 0;//视频通话List videoCalls = [CommonUtils.getBaseIconUrlPng("wc_chat_video_call_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_video_call_selected")];int videoCallsPosition = 0;//语音通话List voiceCalls = [CommonUtils.getBaseIconUrlPng("wc_chat_voice_call_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_voice_call_selected")];int voiceCallsPosition = 0;//红包List redPackets = [CommonUtils.getBaseIconUrlPng("wc_chat_redpacket_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_redpacket_selected")];int redPacketsPosition = 0;//转账List transfers = [CommonUtils.getBaseIconUrlPng("wc_chat_transfer_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_transfer_selected")];int transfersPosition = 0;//相册final TYPE_ABLUM = 1;//拍照final TYPE_TAKE_PHOTO = 2;//视频通话final TYPE_VIDEO_CALL = 3;//语音通话final TYPE_VOICE_CALL = 4;//红包final TYPE_RED_PACKET = 5;//转账final TYPE_TRANSFER = 6;//改变状态void _changeStatus(int type, int position){if(type == TYPE_ABLUM){ablumsPosition = position;ablums[ablumsPosition];} else if(type == TYPE_TAKE_PHOTO){takePhotosPosition = position;takePhotos[takePhotosPosition];} else if(type == TYPE_VIDEO_CALL){videoCallsPosition = position;videoCalls[videoCallsPosition];} else if(type == TYPE_VOICE_CALL){voiceCallsPosition = position;voiceCalls[voiceCallsPosition];} else if(type == TYPE_RED_PACKET){redPacketsPosition = position;redPackets[redPacketsPosition];} else if(type == TYPE_TRANSFER){transfersPosition = position;transfers[transfersPosition];}setState(() {});}Widget getAddWidget(){return Container(//margin: EdgeInsets.only(top: 40, bottom: AppManager.getInstance().getBottom(context) + 20),alignment: Alignment.center,child: Center(child: Column(children: [Row(mainAxisAlignment: MainAxisAlignment.spaceAround,//交叉轴的布局方式,对于column来说就是水平方向的布局方式crossAxisAlignment: CrossAxisAlignment.center,//就是字child的垂直布局方向,向上还是向下verticalDirection: VerticalDirection.down,children: [_buildBottomItem(TYPE_ABLUM),_buildBottomItem(TYPE_TAKE_PHOTO),Offstage(offstage: widget.viewType == CommonUtils.VIEW_TYPE_GROUP_CHAT,child: _buildBottomItem(TYPE_VIDEO_CALL),),Offstage(offstage: widget.viewType == CommonUtils.VIEW_TYPE_GROUP_CHAT,child: _buildBottomItem(TYPE_VOICE_CALL),),],),Offstage(offstage: widget.viewType == CommonUtils.VIEW_TYPE_GROUP_CHAT,child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround,//交叉轴的布局方式,对于column来说就是水平方向的布局方式crossAxisAlignment: CrossAxisAlignment.center,//就是字child的垂直布局方向,向上还是向下verticalDirection: VerticalDirection.down,children: [_buildBottomItem(TYPE_RED_PACKET),_buildBottomItem(TYPE_TRANSFER),_buildBottomItem(-1),_buildBottomItem(-1),],),),],),),);}Widget _buildBottomItem(int type){return Container(alignment: Alignment.center,margin: EdgeInsets.only(top: 10, bottom: 10),child: GestureDetector(child: _getBottomWidget(type),onTap: (){_changeStatus(type,0);if(type == TYPE_ABLUM){_openAblumPermission();} else if(type == TYPE_TAKE_PHOTO){_takePhotoPermission();} else if (type == TYPE_VIDEO_CALL){_openVideoCall();} else if (type == TYPE_VOICE_CALL){_openVoiceCall();} else if (type == TYPE_RED_PACKET){_openRedPacket();} else if (type == TYPE_TRANSFER){_openTransfer();}},onTapCancel: (){_changeStatus(type,0);},onTapDown: (data){_changeStatus(type,1);},),);}Widget _getBottomWidget(int type){if(type == TYPE_ABLUM){return Column(children: [Image.asset(ablums[ablumsPosition], width: 50, height: 50,),const Text("相册"),],);} else if(type == TYPE_TAKE_PHOTO){return Column(children: [Image.asset(takePhotos[takePhotosPosition], width: 50, height: 50,),const Text("拍照"),],);} else if(type == TYPE_VIDEO_CALL){return Column(children: [Image.asset(videoCalls[videoCallsPosition], width: 50, height: 50,),const Text("视频通话"),],);} else if(type == TYPE_VOICE_CALL){return Column(children: [Image.asset(voiceCalls[voiceCallsPosition], width: 50, height: 50,),const Text("语音通话"),],);} else if(type == TYPE_RED_PACKET){return Column(children: [Image.asset(redPackets[redPacketsPosition], width: 50, height: 50,),const Text("红包"),],);} else if(type == TYPE_TRANSFER){return Column(children: [Image.asset(transfers[transfersPosition], width: 50, height: 50,),const Text("转账"),],);} else {//空白占位符return Column(children: [Container(width: 50,height: 50,),Text(""),],);}}//打开相册权限void _openAblumPermission() async {bool isPhotosGranted = await Permission.photos.isGranted;bool isPhotosDenied = await Permission.photos.isDenied;if(isPhotosGranted){_openAblum();} else {if(isPhotosDenied){_openAblum();} else {//跳转到设置页面提示_showPhotosConfirmationAlert(context);}}}// 为正常拍摄,请前往设备中的【设置】> 【隐私】> 【相机】中允许无他相机使用_showPhotosConfirmationAlert(BuildContext context) {showPlatformDialog(context: context,builder: (_) => BasicDialogAlert(title: Text("无法使用相册"),content: Text("为编辑照片,请前往设备中的【设置】> 【隐私】> 【照片】中允许${AppManager.getInstance().appName}使用"),actions: [BasicDialogAction(title: Text("知道了"),onPressed: () {Navigator.pop(context);},),BasicDialogAction(title: Text("去设置"),onPressed: () {// 跳转到系统设置页AppSettings.openAppSettings();},),],),);}//打开相册void _openAblum() {LogUtils.d("打开相册");List selectedAssets = [];AssetPicker.pickAssets(context,pickerConfig: AssetPickerConfig(maxAssets: 1,selectedAssets: selectedAssets,),).then((imageList) {if(imageList == null){return;}imageList as List;for(int i = 0; i < imageList.length; i++){AssetEntity ae = imageList[i];ae.file.then((file) async {String resultFilePath = file?.path??"";_processVideoAndPicture(resultFilePath);});}});}//拍照权限_takePhotoPermission() async{bool isCameraGranted = await Permission.camera.isGranted;bool isCameraDenied = await Permission.camera.isDenied;bool isMicrophoneGranted = await Permission.microphone.isGranted;bool isMicrophoneDenied = await Permission.microphone.isDenied;LogUtils.d("拍照:${isCameraGranted}, ${isCameraDenied} , ${isMicrophoneGranted} , ${isMicrophoneDenied}");//如果2个权限都同意,直接打开if(isCameraGranted && isMicrophoneGranted){_takePhoto();} else if(isCameraDenied && isMicrophoneDenied){//如果2个权限都拒绝,直接打开_takePhoto();} else if(!isCameraGranted && isMicrophoneGranted){_takePhoto();} else if(isCameraGranted && !isCameraDenied){//提示设置麦克风权限String title = "无法使用麦克风";String content = "为正常录制声音,请前往设备中的【设置】> 【隐私】> 【麦克风】中允许${AppManager.getInstance().appName}使用";WnBaseDialog.showPermissionDialog(context, title: title, content: content);} else if(!isCameraDenied){String title = "无法使用相机";String content = "为正常拍摄,请前往设备中的【设置】> 【隐私】> 【相机】中允许${AppManager.getInstance().appName}使用";WnBaseDialog.showPermissionDialog(context, title: title, content: content);} else if(!isMicrophoneDenied){//提示设置麦克风权限LogUtils.d("拍照7");String title = "无法使用麦克风";String content = "为正常录制声音,请前往设备中的【设置】> 【隐私】> 【麦克风】中允许${AppManager.getInstance().appName}使用";WnBaseDialog.showPermissionDialog(context, title: title, content: content);}}//拍照void _takePhoto(){LogUtils.d("拍照");Feedback.forTap(context);CameraPicker.pickFromCamera(context,pickerConfig: const CameraPickerConfig(enableRecording: true, textDelegate: CameraPickerTextDelegate()),useRootNavigator: false).then((resultAssetEntity) {resultAssetEntity?.file.then((resultFile) {LogUtils.d("2拍照返回:${resultFile?.path}");_processVideoAndPicture(resultFile?.path??"");});});}//打开红包void _openRedPacket() async {var balanceStr = await Navigator.push(context, MaterialPageRoute(builder: (context) => RedPacketWidget()));if(balanceStr == null){return;}widget.refreshRedpacketAndTransfer(CommonUtils.CHAT_CONTENT_TYPE_REDPACKET, balanceStr);}//打开转账void _openTransfer() async {var balanceStr = await Navigator.push(context, MaterialPageRoute(builder: (context) => PaymentTransfer(toUser: widget.toChatId,)));if(balanceStr == null){return;}widget.refreshRedpacketAndTransfer(CommonUtils.CHAT_CONTENT_TYPE_TRANSFER, balanceStr);}//打开语音通话void _openVoiceCall() async{Navigator.push(context, MaterialPageRoute(builder: (context) => VideoCallWidget(videoPeerId: widget.toChatId, mediaFlag: CommonUtils.MEDIA_FLAG_VOICE,)));}//打开视频通话void _openVideoCall(){Navigator.push(context, MaterialPageRoute(builder: (context) => VideoCallWidget(videoPeerId: widget.toChatId, mediaFlag: CommonUtils.MEDIA_FLAG_VIDEO,)));}//处理图片和小视频(相册、拍照)void _processVideoAndPicture(String resultFilePath) async {if(resultFilePath == null || "" == resultFilePath){return;}String messageId = UUID.getUUID();if(CommonUtils.isImage(resultFilePath)){//压缩图片完成再发送String compressImagePath = await CompressImageUtils.compressFile(fileName: resultFilePath);widget.sendMedialCallback(CommonUtils.CHAT_CONTENT_TYPE_IMG, compressImagePath,0 ,messageId);widget.refreshMediaCallback(CommonUtils.CHAT_CONTENT_TYPE_IMG, compressImagePath, "",0, messageId);} else if(CommonUtils.isVideo(resultFilePath)){/*** 小视频发送流程,因为小视频比较大,压缩时间比较长。发送的视频,先本地优先显示,查看播放小视频* 1、先复制一份小视频* 2、生成缩略图显示* 3、压缩小视频* 4、删除复制的小视频*///_testmp4(resultFilePath);//最大100M的视频, 不是1024*1024*500 , 使用1000*100*500int maxSize = 100000000;int fileSize = File(resultFilePath).lengthSync();if(fileSize > maxSize){CommonToast.show(context, "上传视频大小不能超过100M", duration: 3);return ;}//小视频生成缩略图大概5秒左右,先刷新站位图widget.refreshMediaCallback(CommonUtils.CHAT_CONTENT_TYPE_VIDEO, resultFilePath, "", 0, messageId);String videoFormat = await getVideoFormat(resultFilePath);File srcFile = File(resultFilePath);String newVideoFileName = await FileUtils.getBaseFile("new_${DateUtil.getNowDateMs()}.mp4");srcFile.copySync(newVideoFileName);String thumbnailFileName = await FileUtils.getBaseFile("thum_${DateUtil.getNowDateMs()}.png");//生成缩略图await VideoThumbnail.thumbnailFile(video: resultFilePath, thumbnailPath: thumbnailFileName);//获取视频时间int second = await getVideoTime(resultFilePath);//int size = File(resultFilePath).lengthSync();//先刷新widget.refreshMediaCallback(CommonUtils.CHAT_CONTENT_TYPE_VIDEO, resultFilePath, thumbnailFileName, second, messageId);//压缩完成再发送MediaInfo? mediaInfo = await CompressVideoUtils.compressVideo(newVideoFileName);String compressVideoPath = mediaInfo?.path??"";//int csecond = await getVideoTime(resultFilePath);//int csize = File(compressVideoPath).lengthSync();widget.sendMedialCallback(CommonUtils.CHAT_CONTENT_TYPE_VIDEO, compressVideoPath, second, messageId);}}//获取视频格式Future getVideoFormat(String resultFilePath) async {String videoFormat = "";final FlutterFFprobe _flutterFFprobe =  FlutterFFprobe();MediaInformation info = await _flutterFFprobe.getMediaInformation(resultFilePath);if (info.getStreams() != null) {List? streams = info.getStreams();if (streams != null && streams.length > 0) {for (var stream in streams) {videoFormat =  stream.getAllProperties()['codec_tag_string'];}}}return videoFormat;}//获取视频时间Future getVideoTime(String resultFilePath) async {int time = 0;final FlutterFFprobe _flutterFFprobe =  FlutterFFprobe();MediaInformation info = await _flutterFFprobe.getMediaInformation(resultFilePath);if (info.getStreams() != null) {String duration = info.getMediaProperties()?['duration'];String size = info.getMediaProperties()?['size'];double durationDouble = double.parse(duration);time = durationDouble.toInt();LogUtils.d("多媒体文件大小:${size}");}return time;}void _testmp4(String resultFilePath){final FlutterFFprobe _flutterFFprobe = new FlutterFFprobe();_flutterFFprobe.getMediaInformation(resultFilePath).then((info) {LogUtils.d("测试视频信息:Media Information");LogUtils.d("测试视频信息:Path: ${info.getMediaProperties()?['filename']}");LogUtils.d("测试视频信息:Format: ${info.getMediaProperties()?['format_name']}");LogUtils.d("测试视频信息:Duration: ${info.getMediaProperties()?['duration']}");LogUtils.d("测试视频信息:Start time: ${info.getMediaProperties()?['start_time']}");LogUtils.d("测试视频信息:Bitrate: ${info.getMediaProperties()?['bit_rate']}");//LogUtils.d("测试视频信息:CodecTagString: ${info.getMediaProperties()?['codec_tag_string']}");Map tags = info.getMediaProperties()?['tags'];/*if (tags != null) {tags.forEach((key, value) {LogUtils.d("Tag: " + key + ":" + value + "\n");});}*/if (info.getStreams() != null) {List? streams = info.getStreams();if (streams != null && streams.length > 0) {for (var stream in streams) {LogUtils.d("测试视频信息:Stream id: ${stream.getAllProperties()['index']}");LogUtils.d("Stream type: ${stream.getAllProperties()['codec_type']}");LogUtils.d("Stream codec: ${stream.getAllProperties()['codec_name']}");LogUtils.d("Stream full codec: ${stream.getAllProperties()['codec_long_name']}");LogUtils.d("Stream format: ${stream.getAllProperties()['pix_fmt']}");LogUtils.d("Stream width: ${stream.getAllProperties()['width']}");LogUtils.d("Stream height: ${stream.getAllProperties()['height']}");LogUtils.d("Stream bitrate: ${stream.getAllProperties()['bit_rate']}");LogUtils.d("Stream sample rate: ${stream.getAllProperties()['sample_rate']}");LogUtils.d("Stream sample format: ${stream.getAllProperties()['sample_fmt']}");LogUtils.d("Stream channel layout: ${stream.getAllProperties()['channel_layout']}");LogUtils.d("Stream sar: ${stream.getAllProperties()['sample_aspect_ratio']}");LogUtils.d("Stream dar: ${stream.getAllProperties()['display_aspect_ratio']}");LogUtils.d("Stream average frame rate: ${stream.getAllProperties()['avg_frame_rate']}");LogUtils.d("Stream real frame rate: ${stream.getAllProperties()['r_frame_rate']}");LogUtils.d("Stream time base: ${stream.getAllProperties()['time_base']}");LogUtils.d("测试视频信息:Stream codec time base: ${stream.getAllProperties()['codec_time_base']}");LogUtils.d("A测试视频信息:Stream codec_tag_string: ${stream.getAllProperties()['codec_tag_string']}");/*Map tags = stream.getAllProperties()['tags'];if (tags != null) {tags.forEach((key, value) {LogUtils.d("Stream tag: " + key + ":" + value + "\n");});}*/}}}});}}

chat_content_view.dart实现:

/*** Author : wangning* Email : maoning20080809@163.com* Date : 2022/9/24 12:09* Description : 单聊内容控件*/
class ChatContentView extends StatefulWidget {final List items;final ChatBean chatBean;final int index;final UserBean? otherUserBean;final UserBean? meUserBean;final String account;final deleteCallback;final List? addTimeList;//点击语音播放回调final clickVoiceCallback;//点击领取转账,刷新页面final refreshTransfer;ChatContentView({required this.items,  required this.account, required this.chatBean, required this.index,required this.meUserBean, required this.otherUserBean, this.addTimeList, required this.deleteCallback, required this.clickVoiceCallback, required this.refreshTransfer});@overrideState createState() => _ChatContentViewState();}class _ChatContentViewState extends State {//判断是否已经存在转换好的时间@overridevoid initState() {super.initState();}void goNewFriends(String account) async{UserBean? userBean = await UserRepository.getInstance().findUserByAccount(account);if(userBean != null){Navigator.push(context,MaterialPageRoute(builder: (context)=>AddFriends(userBean: userBean!,)));} else {userBean = await UserRepository.getInstance().getUserServer(account);Navigator.push(context,MaterialPageRoute(builder: (context)=>AddFriends(userBean: userBean!,)));}}@overrideWidget build(BuildContext context) {String addTimeResult = _getAddTime("${widget.chatBean.addTime}");bool isExistTime = isExistAddTime(addTimeResult);if(!isExistTime){widget.addTimeList?.add(addTimeResult);}//如果是最后一个,清除标志if(widget.index == widget.items.length -1){widget.addTimeList?.clear();}return Column(children: [Offstage(offstage: isExistTime,child: Container(margin: EdgeInsets.only(top: 12),child: Text("${addTimeResult}"),),),Container(child: widget.account == widget.chatBean.fromAccount? fromAccountWidget(): toAccountWidget(),)],);}//小视频缩略图Widget getCommonThumbnail(int second){return CommonThumbnailWidget(padding: EdgeInsets.only(top: 0.0,right: (widget.account == widget.chatBean.fromAccount ? 0.0 : 5.0),left: (widget.account == widget.chatBean.toAccount ? 2.0 : 0.0)),image: widget.chatBean.imgPathLocal??"",second: second,onPressed: () {Navigator.push(context, MaterialPageRoute(builder: (context) => VideoPlayLocalPreview(widget.chatBean.videoLocal!)));});}//显示我的Widget fromAccountWidget(){return Container(margin: EdgeInsets.only(top: 8.0, left: 68.0, right: 8),padding: EdgeInsets.all(2.0),child: Row(children: [Expanded(child: GestureDetector(onLongPress: (){_showDeleteDialog(widget.chatBean);},onTap: () {},child: Stack(alignment: AlignmentDirectional.bottomEnd,children: [//文本widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_TEXT?meTextWidget():Container(),//语言widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_VOICE?meVoiceWidget():Container(),//图片widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_IMG?CommonUtils.showBaseImage(widget.chatBean.imgPathLocal??"", width:100, height:200, angle:1, onPressed: (data){Navigator.push(context,MaterialPageRoute(builder: (context)=>CommonImagePreview(fileName: data)));}):Container(),//小视频widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_VIDEO?getCommonThumbnail(widget.chatBean.second??0):Container(),//红包widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_REDPACKET?meRedpacketWidget():Container(),//转账widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_TRANSFER?meTransferWidget():Container(),],),),),//userImageContainer(padding: EdgeInsets.only(left: 6, right: 6),child: GestureDetector(onTap: (){Navigator.push(context,MaterialPageRoute(builder: (context)=>ContactsDetails(toChatId: widget.chatBean.fromAccount??"")));},child: CommonAvatarView.showBaseImage(widget.meUserBean?.avatar??"", 38, 38),),),],),);}//显示好友Widget toAccountWidget(){return Container(margin: EdgeInsets.only(top: 8.0, right: 68.0),padding: EdgeInsets.all(2.0),child: Row(//crossAxisAlignment: CrossAxisAlignment.start,children: [//userImage,Container(margin: EdgeInsets.only(left: 6, right: 6),child: GestureDetector(onTap : (){Navigator.push(context,MaterialPageRoute(builder: (context)=>ContactsDetails(toChatId: widget.otherUserBean?.account??"")));},child: CommonAvatarView.showBaseImage(widget.otherUserBean?.avatar??"", 38, 38),),),Expanded(child: GestureDetector(onLongPress: (){_showDeleteDialog(widget.chatBean);},onTap: () {},child: Stack(alignment: AlignmentDirectional.centerStart,children: [//文本widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_TEXT?toTextWidget():Container(),//语音widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_VOICE?toVoiceWidget():Container(),//图片widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_IMG?CommonUtils.showBaseImage(widget.chatBean.imgPathLocal??"", width:100, height:200, angle:1, onPressed: (data){Navigator.push(context,MaterialPageRoute(builder: (context)=>CommonImagePreview(fileName: data)));}):Container(),//小视频widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_VIDEO?getCommonThumbnail(widget.chatBean.second??0):Container(),//红包widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_REDPACKET?toRedpacketWidget():Container(),//转账widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_TRANSFER?toTransferWidget():Container(),],)),),/**/],),);}//打开红包对话框void _onOpenRedpacket(){}//朋友的文本Widget toTextWidget(){return Column(crossAxisAlignment: CrossAxisAlignment.start,children: [Container(padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 10.0),decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(5.0),),color: Color(0xFFEDEDED)),child: Text(widget.chatBean.content??"",textAlign: TextAlign.left,style: TextStyle(color: Colors.black, fontSize: 20.0),),)],);}//朋友的语音Widget toVoiceWidget(){return InkWell(onTap: () {setState(() {widget.chatBean.isPlayVoice = true;});LogUtils.d("点击语音");AudioPlayer.getInstance().playLocal(widget.chatBean.voiceLocal??"", callback: (data){LogUtils.d("录音回调:${data}");setState(() {widget.chatBean.isPlayVoice = false;});});},child : Container(width: 120,height: 40,padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 2.0),decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(5.0),),color: Color(0xFFEDEDED)),child: Row(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: [widget.chatBean.isPlayVoice?Image.asset("assets/chat/wn_chat_other_animator.gif", height: 34,):Image.asset("assets/chat/wn_chat_other_volume_3.png",  height: 34,),SizedBox(width: 4,),Text("${widget.chatBean.second}''"),],),));}//朋友的红包Widget toRedpacketWidget(){return GestureDetector(onTap: (){if(widget.chatBean.isClick == 1){Navigator.push(context, MaterialPageRoute(builder: (context) => ReceiveRedpacketSuccess(fromUser: widget.meUserBean?.account??"", toUser: widget.otherUserBean?.account??"", balance: widget.chatBean?.content??"", addTime: widget.chatBean.addTime??"",)));} else {showRedPacket(context, _onOpenRedpacket, widget.otherUserBean?.account, widget.chatBean?.content??"", widget.index);}},child: Opacity(opacity: widget.chatBean.isClick == 1 ? 0.6 :1,child: Container(child: Stack(children: [toRedpacketBackground(),Positioned(left: 38, top: 20,child:CommonUtils.getBaseIconPng("wc_redpacket_icon", width: 40, height: 40),),Positioned(left: 88, top: 30,child: Text("恭喜发财,大吉大利", style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold),),),Positioned(left: 88, top: 50,child: Container(margin: EdgeInsets.only(top:10),width: 120,height: 1,color: Colors.white,),),Positioned(left: 38, bottom: 14,child:Text("私人红包", style: TextStyle(fontSize:12, color: Colors.white38),),),],),),),);}//处理转账void _processTransferDetails() async{var data = await Navigator.push(context, MaterialPageRoute(builder: (context) => TransferDetails(toUser: widget.chatBean?.toAccount??"", balance: double.parse(widget.chatBean?.content??""), messageId: widget.chatBean?.messageId??"")));if(data != null && data > 0){widget.refreshTransfer(widget.index);}}//朋友的转账Widget toTransferWidget(){return GestureDetector(onTap: (){_processTransferDetails();},child: Opacity(opacity: widget.chatBean.isClick == 1 ? 0.6 :1,child: Container(child: Stack(children: [toRedpacketBackground(),Positioned(left: 42, top: 20,child:CommonUtils.getBaseIconPng("wc_chat_transfer_icon", width: 40, height: 40),),Positioned(left: 98, top: 14,child: Text("¥${double.parse(widget.chatBean.content??'0').toStringAsFixed(2)}", style: TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold),),),Positioned(left: 98, top: 40,child: Text("请收款", style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold),),),Positioned(left: 98, top: 54,child: Container(margin: EdgeInsets.only(top:10),width: 120,height: 1,color: Colors.white,),),Positioned(left: 38, bottom: 14,child:Text("私人转账", style: TextStyle(fontSize: 12, color: Colors.white38),),),],),),),);}Widget toRedpacketBackground(){return CustomPaint(painter: RedPacketOther(strokeColor: Color(0xFFf58220),paintingStyle:PaintingStyle.fill,),child: Container(height: 100,width: 280,),);}//我的文本Widget meTextWidget(){return Column(// Column被Expanded包裹起来,使其内部文本可自动换行crossAxisAlignment: CrossAxisAlignment.end,children: [Container(padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 10.0),decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(5.0),),color: Color(0xFF9EEA6A),),child: Text(widget.chatBean.content??"",textAlign: TextAlign.left,style: TextStyle(color: Colors.black, fontSize: 20.0),),)],);}//我的语言Widget meVoiceWidget(){return InkWell(onTap: () {widget.clickVoiceCallback(true);setState(() {widget.chatBean.isPlayVoice = true;});//点击语音AudioPlayer.getInstance().playLocal(widget.chatBean.voiceLocal??"", callback: (data){//录音回调setState(() {widget.chatBean.isPlayVoice = false;});});},child : Container(width: 120,height: 40,padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 2.0),decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(1.0),),color: Color(0xFF9EEA6A),),child: Row(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: [Text("${widget.chatBean.second}''"),SizedBox(width: 4,),widget.chatBean.isPlayVoice?Image.asset("assets/chat/wn_chat_me_animator.gif", height: 24,):Image.asset("assets/chat/wn_chat_me_volume_3.png", height: 24,),],),));}//我的红包Widget meRedpacketWidget(){return GestureDetector(onTap: (){//点击红包Navigator.push(context, MaterialPageRoute(builder: (context) => ReceiveRedpacketSuccess(fromUser: widget.meUserBean?.account??"", toUser: widget.otherUserBean?.account??"", balance: widget.chatBean?.content??"", addTime: widget.chatBean.addTime??"",)));},child: Container(child: Stack(children: [meRedpacketBackground(),Positioned(left: 20, top: 20,child:CommonUtils.getBaseIconPng("wc_redpacket_icon", width: 40, height: 40),),Positioned(left: 70, top: 30,child: Text("恭喜发财,大吉大利", style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold),),),Positioned(left: 70, top: 50,child: Container(margin: EdgeInsets.only(top:10),width: 120,height: 1,color: Colors.white,),),Positioned(left: 20, bottom: 14,child:Text("私人红包", style: TextStyle(fontSize: 12, color: Colors.white38),),),],),),);}//我的转账Widget meTransferWidget(){return GestureDetector(onTap: (){//点击转账Navigator.push(context, MaterialPageRoute(builder: (context) => TransferDetails(toUser: widget.otherUserBean?.account??"", balance: double.parse(widget.chatBean?.content??""), messageId: widget.chatBean?.messageId??"")));},child: Container(child: Stack(children: [meRedpacketBackground(),Positioned(left: 20, top: 20,child:CommonUtils.getBaseIconPng("wc_chat_transfer_icon", width: 40, height: 40),),Positioned(left: 70, top: 14,child: Text("¥${double.parse(widget.chatBean.content??'0').toStringAsFixed(2)}", style: TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold),),),Positioned(left: 70, top: 40,child: Text("你发起了一笔转账", style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold),),),Positioned(left: 70, top: 54,child: Container(margin: EdgeInsets.only(top:10),width: 120,height: 1,color: Colors.white,),),Positioned(left: 20, bottom: 14,child:Text("私人转账", style: TextStyle(fontSize: 12, color: Colors.white38),),),],),),);}Widget meRedpacketBackground(){return CustomPaint(painter: RedPacketMe(strokeColor: Color(0xFFf58220),paintingStyle:PaintingStyle.fill,),child: Container(height: 100,width: 280,),);}bool isExistAddTime(String addTimeResult){return widget.addTimeList?.contains(addTimeResult)??false;}String _getAddTime(String addTime){return WnTimeUtils.timeUtils(startTime:  addTime);}//删除对话框Future _showDeleteDialog(ChatBean chatBean) async {return showDialog(context: context,barrierDismissible: false,builder: (BuildContext context) {return AlertDialog(title: Text('确定要删除该消息吗?', style: new TextStyle(fontSize: 17.0)),actions: [MaterialButton(child: Text('取消'),onPressed: (){LogUtils.d("确定取消");Navigator.of(context).pop();},),MaterialButton(child: Text('确定'),onPressed: (){LogUtils.d("确定删除");Navigator.pop(context);//_deleteContacts(contactsBeanComb);_deleteChatBean(chatBean);},)],);});}//删除消息_deleteChatBean(ChatBean chatBean) async{int id = chatBean.id??0;await ChatRepository.getInstance().deleteChatById(id);widget.deleteCallback(true);}}

chat_voice_view.dart实现:

/*** Author : wangning* Email : maoning20080809@163.com* Date : 2022/9/24 12:21* Description : 语音动画*/class ChatVoiceView extends StatefulWidget{//刷新列表final refreshMediaCallback;//发送信息final sendMedialCallback;//停止语音播放final stopPlayVoiceCallback;ChatVoiceView({required this.refreshMediaCallback, required this.sendMedialCallback, required this.stopPlayVoiceCallback});@overrideState createState() =>  ChatVoiceState();}class ChatVoiceState extends State{// 倒计时总时长int _countTotal = 60;double starty = 0.0;double offset = 0.0;bool isUp = false;String textShow = "按住 说话";String toastShow = "手指上滑,取消发送";String voiceIco = CommonUtils.getChatUrlPng("voice_volume_1");///默认隐藏状态bool voiceState = true;Timer? _timer;int _count = 0;//录音总数int _soundSecond = 0;OverlayEntry? overlayEntry;final _audioRecorder = Record();@overridevoid initState() {super.initState();}@overridevoid dispose() {super.dispose();_audioRecorder.dispose();_timer?.cancel();}@overrideWidget build(BuildContext context) {return getPressVoiceWidget();}//打开录音权限void _openMicrophonePermission(details) async {bool isMicrophoneGranted = await Permission.microphone.isGranted;bool isMicrophoneDenied = await Permission.microphone.isDenied;if(isMicrophoneGranted){_onLongPressStart(details);} else {if(isMicrophoneDenied){PermissionUtils.requestMicrophonePermission();} else {//跳转到设置页面提示_showMicrophoneConfirmationAlert(context);}}}//无法使用相机// 为正常拍摄,请前往设备中的【设置】> 【隐私】> 【相机】中允许无他相机使用_showMicrophoneConfirmationAlert(BuildContext context) {showPlatformDialog(context: context,builder: (_) => BasicDialogAlert(title: Text("无法使用麦克风"),content: Text("为正常录制声音,请前往设备中的【设置】> 【隐私】> 【麦克风】中允许${AppManager.getInstance().appName}使用"),actions: [BasicDialogAction(title: Text("知道了"),onPressed: () {Navigator.pop(context);},),BasicDialogAction(title: Text("去设置"),onPressed: () {// 跳转到系统设置页AppSettings.openAppSettings();},),],),);}void _onLongPressStart(details){starty = details.globalPosition.dy;_timer = Timer.periodic(Duration(milliseconds: 1000), (t) {_count++;if (_count == _countTotal) {hideVoiceView();}});showVoiceView();}void _onLongPressEnd() async {bool isMicrophoneGranted = await Permission.microphone.isGranted;if(isMicrophoneGranted){hideVoiceView();}}void _onLongPressMoveUpdate(details) async {bool isMicrophoneGranted = await Permission.microphone.isGranted;if(isMicrophoneGranted){offset = details.globalPosition.dy;moveVoiceView();}}Widget getPressVoiceWidget() {return GestureDetector(onLongPressStart: (details) {_openMicrophonePermission(details);},onLongPressEnd: (details) {_onLongPressEnd();},onLongPressMoveUpdate: (details) {_onLongPressMoveUpdate(details);},child: Container(alignment: Alignment.bottomCenter,padding: EdgeInsets.only(top: 10.0, bottom: 10.0),decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(5.0),),color: Colors.black12),width: double.infinity,child: Text("按住 说话"),),);}///显示录音悬浮布局buildOverLayView(BuildContext context) {if (overlayEntry == null) {overlayEntry = new OverlayEntry(builder: (content) {return CustomOverlay(icon: Column(children: [Container(margin: const EdgeInsets.only(top: 10),child: _countTotal - _count < 11? Center(child: Padding(padding: const EdgeInsets.only(bottom: 15.0),child: Text((_countTotal - _count).toString(),style: TextStyle(fontSize: 70.0,color: Colors.white,),),),): new Image.asset(voiceIco,width: 100,height: 100,//package: 'flutter_plugin_record',),),Container(
//                      padding: const EdgeInsets.only(right: 20, left: 20, top: 0),child: Text(toastShow,style: TextStyle(fontStyle: FontStyle.normal,color: Colors.white,fontSize: 14,),),)],),);});Overlay.of(context)!.insert(overlayEntry!);}}showVoiceView() {setState(() {textShow = "松开结束";voiceState = false;});///显示录音悬浮布局buildOverLayView(context);hidePlayVoiceList();start();playAnimation();}//隐藏播放列表,停止播放录音hidePlayVoiceList(){widget.stopPlayVoiceCallback();}hideVoiceView() async {if (_timer!.isActive) {if (_count < 1) {CommonToast.showView(context: context,msg: '说话时间太短',icon: Text('!',style: TextStyle(fontSize: 80, color: Colors.white),));isUp = true;}_timer?.cancel();_soundSecond = _count;_count = 0;}setState(() {textShow = "按住 说话";voiceState = true;});stop();if (overlayEntry != null) {overlayEntry?.remove();overlayEntry = null;}if (isUp) {LogUtils.d("取消发送");File file = File(fileName);if(fileName != null && file.existsSync()){file.deleteSync();}} else {LogUtils.d("进行发送 ${_soundSecond}");await _audioRecorder.stop();String messageId = UUID.getUUID();widget.refreshMediaCallback(CommonUtils.CHAT_CONTENT_TYPE_VOICE, fileName, "", _soundSecond, messageId);widget.sendMedialCallback(CommonUtils.CHAT_CONTENT_TYPE_VOICE, fileName, _soundSecond, messageId);}}moveVoiceView() {setState(() {isUp = starty - offset > 100 ? true : false;if (isUp) {textShow = "松开手指,取消发送";toastShow = textShow;} else {textShow = "松开结束";toastShow = "手指上滑,取消发送";}});}String fileName = "";///开始语音录制的方法void start() async {try {fileName = await FileUtils.getBaseFile("voice_${DateUtil.getNowDateMs()}.m4a");File file = File(fileName);if(!file.existsSync()){file.createSync();}if (await _audioRecorder.hasPermission()) {final isSupported = await _audioRecorder.isEncoderSupported(AudioEncoder.aacLc,);LogUtils.d("录音文件:${fileName}");await _audioRecorder.start(path: fileName, encoder: AudioEncoder.aacLc, bitRate: 262144, samplingRate: 48000);}} catch (e) {LogUtils.d("${e}");}}Timer? periodicTimer;//播放录音动画void playAnimation(){int i =0;periodicTimer = Timer.periodic(const Duration(milliseconds: 150),(timer) {i++;if (i == 1) {voiceIco = CommonUtils.getChatUrlPng("voice_volume_2");} else if (i == 2) {voiceIco = CommonUtils.getChatUrlPng("voice_volume_3");} else if (i == 3) {voiceIco = CommonUtils.getChatUrlPng("voice_volume_4");} else if (i == 4) {voiceIco = CommonUtils.getChatUrlPng("voice_volume_5");} else if (i == 5) {voiceIco = CommonUtils.getChatUrlPng("voice_volume_6");} else if (i == 6) {voiceIco = CommonUtils.getChatUrlPng("voice_volume_7");} else {i = 0;}if (overlayEntry != null) {overlayEntry!.markNeedsBuild();}},);}///停止语音录制的方法void stop() async{await _audioRecorder.stop();periodicTimer?.cancel();}}

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...