import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:chat/chat/keyboard_icon.dart'; import 'package:chat/chat/my_extended_text_selection_controls.dart'; import 'package:chat/chat/record_view.dart'; import 'package:chat/chat/util_keyboard.dart'; import 'package:chat/data/UserData.dart'; import 'package:chat/data/constants.dart'; import 'package:chat/generated/i18n.dart'; import 'package:chat/home/alter_select_view.dart'; import 'package:chat/models/ChatMsg.dart'; import 'package:chat/models/group_info_model.dart'; import 'package:chat/models/keyboard_provider.dart'; import 'package:chat/photo/entity/options.dart'; import 'package:chat/photo/photo.dart'; import 'package:chat/proto/all.pbserver.dart'; import 'package:chat/utils/CustomUI.dart'; import 'package:chat/utils/MessageMgr.dart'; import 'package:chat/utils/group_member_model.dart'; import 'package:chat/utils/image_util.dart'; import 'package:chat/utils/keyboard_utils.dart'; import 'package:chat/utils/msgHandler.dart'; import 'package:chat/utils/screen.dart'; import 'package:chat/utils/sound_util.dart'; import 'package:extended_text_field/extended_text_field.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:oktoast/oktoast.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'emoji_gif_text.dart'; import 'emoji_text.dart'; import 'my_special_text_span_builder.dart'; class InputBar extends StatefulWidget { final Function sendMsg; InputBar({this.sendMsg}); @override InputBarState createState() => InputBarState(); } class InputBarState extends State with SingleTickerProviderStateMixin { final TextEditingController _textCtrl = TextEditingController(); FocusNode editFocus = NoKeyboardEditableTextFocusNode(); bool _isComposingMessage = false; PageController pageController; double keyboardHeight; KeyboardBloc _bloc = KeyboardBloc(); GlobalKey _key = GlobalKey(); int currentStickersPage = 0; //回复相关 MsgModel refMsg; //@成员数组 List alterMemberList = []; int lastTxtLen = 0; @override void initState() { super.initState(); print('~~~~~~~~~~inputbar initState~~~~~~~~~~~'); pageController = new PageController(); getKeyboardHeight(); _bloc.start(); //处理引用事件 MessageMgr().on('Reply Select Message', replySelectMsg); //处理@事件 MessageMgr().on('Alter User Message', alterOtherMember); } @override void didChangeDependencies() { super.didChangeDependencies(); editFocus?.requestFocus(); } //获取键盘高度,之前保存的 getKeyboardHeight() async { var sp = await SharedPreferences.getInstance(); keyboardHeight = sp.getDouble(Constants.KeyboardHeight); if (keyboardHeight == null || keyboardHeight < 100) { keyboardHeight = 280; } } @override void dispose() { _key = null; _textCtrl.dispose(); _bloc.dispose(); editFocus.dispose(); editFocus = null; MessageMgr().off('Reply Select Message', replySelectMsg); MessageMgr().off('Alter User Message', alterOtherMember); super.dispose(); } //引用某句话 replySelectMsg(args) async { refMsg = args; print('处理引用消息'); setState(() { showKeyBoard(); }); } //快捷@某成员 alterOtherMember(memberInfo) { if (!alterMemberList.contains(memberInfo)) { alterMemberList.add(memberInfo); } print('选中的成员~~~~~~~~~~~~ ${memberInfo.refName}'); String textStr; if (_textCtrl.text.length > 0) { textStr = '${_textCtrl.text} @${memberInfo.refName} '; } else { textStr = '@${memberInfo.refName} '; } _textCtrl.value = TextEditingValue( text: textStr, selection: TextSelection.fromPosition(TextPosition(offset: textStr.length))); setState(() { showKeyBoard(); _isComposingMessage = _textCtrl.text.length > 0; }); } //显示键盘 showKeyBoard() { Provider.of(context, listen: false) .changeSelectIndex(0); editFocus.requestFocus(); Provider.of(context, listen: false) .changeReadOnlyKey(false); // SystemChannels.textInput.invokeMethod('TextInput.show'); } _getRefShortText() { if (refMsg == null) { return null; } String desc = ''; if (refMsg == null || refMsg.msgType == null) { return ''; } switch (ChatType.valueOf(refMsg.msgType)) { case ChatType.TextChatType: desc = utf8.decode(refMsg.msgContent); break; case ChatType.EmoticonType: desc = '[${I18n.of(context).emoji}]'; break; case ChatType.ImageChatType: desc = '[${I18n.of(context).picture}]'; break; case ChatType.ShortVideoChatType: desc = '[${I18n.of(context).video}]'; break; case ChatType.PlaceChatType: desc = '[${I18n.of(context).locate}]'; break; case ChatType.ShortVoiceChatType: desc = '[${I18n.of(context).voice}]'; break; case ChatType.GiftChatType: GiftChat giftChat = GiftChat.fromBuffer(refMsg.msgContent); if (giftChat.tuId == UserData().basicInfo.userId) { desc = I18n.of(context).you_get; } else { desc = I18n.of(context).you_give; } break; case ChatType.RedWalletChatType: desc = '[${I18n.of(context).red_money}]'; break; case ChatType.GroupChatNoticeType: desc = '[${I18n.of(context).msg_notice}]'; break; case ChatType.FileChatType: desc = '[${I18n.of(context).file}]'; break; default: } GroupInfoModel groupInfoModel = Provider.of(context); var member = groupInfoModel.getMember(refMsg.from); return '${member.refName}: $desc'; } bool isReceiver = false; @override Widget build(BuildContext context) { int curKeyboardIndex = Provider.of(context).curKeyboardIndex; bool readOnly = Provider.of(context, listen: false).readOnly; bool isGroup = Provider.of(context); Widget centerWidget; Widget input = Container( child: TextField( // textSelectionControls: ExtendedMaterialTextSelectionControls(), keyboardAppearance: Brightness.light, onChanged: (String messageText) { if (_textCtrl.text.length > 0) { var last = messageText.substring(_textCtrl.text.length - 1); if (last == '@' && _textCtrl.text.length > lastTxtLen) { editFocus.unfocus(); _openAlterSelectPage(); } } lastTxtLen = _textCtrl.text.length; setState(() { _isComposingMessage = _textCtrl.text.length > 0; }); }, key: _key, readOnly: readOnly, autofocus: true, cursorColor: Constants.BlueTextColor, style: TextStyle( fontSize: ScreenUtil().setSp(16), textBaseline: TextBaseline.alphabetic), maxLines: 3, minLines: 1, // specialTextSpanBuilder: MySpecialTextSpanBuilder( // showAtBackground: true, // ), controller: _textCtrl, textInputAction: TextInputAction.newline, inputFormatters: [ LengthLimitingTextInputFormatter(600) //限制长度 ], onSubmitted: _textMessageSubmitted, focusNode: editFocus, onTap: () { showKeyBoard(); Provider.of(context, listen: false) .changeReadOnlyKey(false); }, decoration: InputDecoration( hintText: I18n.of(context).input_content, hintStyle: TextStyle(color: const Color(0xffBDBDBD), fontSize: 16), border: null, hintMaxLines: 1, contentPadding: EdgeInsets.only(left: 5, right: 5, top: 8, bottom: 8), ), ), ); if (refMsg == null) { centerWidget = input; } else { var desc = _getRefShortText(); centerWidget = Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( child: Row(mainAxisSize: MainAxisSize.min, children: [ Container( child: Text( desc, maxLines: 1, overflow: TextOverflow.ellipsis, textScaleFactor: 1.0, ), constraints: BoxConstraints(maxWidth: Screen.width - 220), ), InkWell( child: Container( child: Icon(Icons.close, size: 10), padding: EdgeInsets.fromLTRB(10, 5, 5, 5), ), onTap: () { refMsg = null; setState(() {}); }, ) ]), padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), decoration: BoxDecoration( color: Colors.grey[200], border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(10))), input ], ); } return GestureDetector( onTap: () { print('放置触控隐藏键盘'); }, child: Container( width: Screen.width, color: Colors.white, child: Column( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 7, vertical: 7), alignment: Alignment.topCenter, decoration: BoxDecoration( color: Colors.white, border: Border(top: BorderSide(color: Color(0xFFDFDFDF)))), child: Row( children: [ //emoji图标 KeyboardIcon( iconCode: 0xe64d, selectIconCode: 0xe651, isSelect: curKeyboardIndex == 1, size: 27, onTap: () { isReceiver = !isReceiver; SoundUtils.instance.setReceiver(isReceiver); // if (curKeyboardIndex == 1) { Provider.of(context, listen: false) .changeReadOnlyKey(false); showKeyBoard(); } else { Provider.of(context, listen: false) .changeSelectIndex(1); currentStickersPage = 0; } }), SizedBox(width: 10), //输入框 Expanded(child: centerWidget), SizedBox(width: 10), _isComposingMessage ? InkWell( child: Padding( padding: EdgeInsets.fromLTRB(50, 2, 10, 2), child: Icon( Icons.send, color: Color(0xFF087FF3), size: 28, )), onTap: _sendTextMessage) : Container( child: Row( children: [ KeyboardIcon( iconCode: 0xe64b, isSelect: curKeyboardIndex == 2, size: 28, onTap: () { if (curKeyboardIndex == 2) { showKeyBoard(); } else { Provider.of( context, listen: false) .changeSelectIndex(2); } }), SizedBox(width: 10), KeyboardIcon( iconCode: 0xe64f, isSelect: curKeyboardIndex == 3, size: 28, onTap: () { if (curKeyboardIndex == 3) { showKeyBoard(); } else { Provider.of( context, listen: false) .changeSelectIndex(3); } }), SizedBox(width: 10), KeyboardIcon( iconCode: 0xe60c, isSelect: curKeyboardIndex == 4, size: 26, onTap: () { _openPhotoView(); }), ], ), ) ], ), ), Divider(height: 1, color: const Color(0xffE0E0E0)), StreamBuilder( stream: _bloc.stream, builder: (BuildContext context, AsyncSnapshot snapshot) { double keyHeight = MediaQuery.of(context).viewInsets.bottom; if (keyHeight > 10) { keyboardHeight = keyHeight; UserData().setKeyboardHeight(keyHeight); } return Container( width: double.infinity, color: Colors.white, height: curKeyboardIndex == -1 || curKeyboardIndex == 4 ? 0 : keyboardHeight, child: curKeyboardIndex >= 1 && curKeyboardIndex < 4 ? IndexedStack( children: [ Container( color: Colors.white, width: double.infinity, height: double.infinity, child: InkWell( onTap: () {}, child: Container( height: keyboardHeight, child: Column( children: [ Container( height: keyboardHeight - 50, child: PageView.custom( controller: pageController, onPageChanged: (page) { setState(() { currentStickersPage = page; }); }, childrenDelegate: new SliverChildBuilderDelegate( (context, index) { return index == 0 ? buildEmojiGird() : buildGifGird(); }, childCount: 2, )), ), Divider( height: 1, color: Color(0xffE0E0E0), ), Container( height: 49, child: Row( children: [ InkWell( child: Container( width: 59, padding: EdgeInsets.all(10), color: currentStickersPage == 0 ? const Color( 0xFFEDEDED) : Colors.white, child: Image.asset( 'assets/images/chat/emoji.png'), ), onTap: () { pageController .jumpToPage(0); }, ), InkWell( child: Container( padding: EdgeInsets.all(8), width: 59, color: currentStickersPage == 1 ? const Color( 0xFFEDEDED) : Colors.white, child: Image.asset( 'assets/images/chat/onion.png'), ), onTap: () { pageController .jumpToPage(1); }, ) ], ), ), ], ), ), )), UtilKeyboard( keyboardHeight: keyboardHeight, isGroup: isGroup, sendMsg: widget.sendMsg), RecordView( keyboardHeight: keyboardHeight, sendMsg: _sendSoundMsg), ], index: curKeyboardIndex - 1, ) : Container()); }, ) ], )), ); } Widget buildEmojiGird() { return Container( height: keyboardHeight, child: Stack( children: [ Padding( padding: EdgeInsets.fromLTRB(5, 8, 50, 0), child: GridView.builder( controller: new ScrollController(keepScrollOffset: false), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 7, crossAxisSpacing: 0, mainAxisSpacing: 0), itemBuilder: (context, index) { return GestureDetector( child: Container( padding: EdgeInsets.all(7), child: Image.asset(EmojiUitl .instance.emojiMap[EmojiText.flag + "${index + 1}]"]), ), behavior: HitTestBehavior.translucent, onTap: () { insertText(EmojiText.flag + "${index + 1}]", _textCtrl); setState(() { _isComposingMessage = _textCtrl.text.length > 0; }); }, ); }, itemCount: EmojiUitl.instance.emojiMap.length, padding: EdgeInsets.all(5.0), ), ), Positioned.fill( child: Container( padding: EdgeInsets.fromLTRB(0, 0, 0, 14), alignment: Alignment(1, 1), child: InkWell( onTap: () { deleteText(_textCtrl); setState(() { _isComposingMessage = _textCtrl.text.length > 0; }); }, child: Container( decoration: BoxDecoration( color: Color(0x4d0E0E10), borderRadius: BorderRadius.only( topLeft: Radius.circular(4.0), bottomLeft: Radius.circular(4))), width: 37, height: 28, child: Icon( IconData( 0xe679, fontFamily: 'iconfont', ), size: 16, color: Colors.white, ), ), ), )), ], ), ); } Widget buildGifGird() { return Container( padding: EdgeInsets.fromLTRB(8, 6, 8, 0), height: keyboardHeight, child: GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 5, crossAxisSpacing: 12.0, mainAxisSpacing: 12.0), itemBuilder: (context, index) { return GestureDetector( child: Image.asset(EmojiGifUitl .instance.emojiMap[EmojiGifText.flag + "_${index + 1}>"]), behavior: HitTestBehavior.translucent, onTap: () { _sendMessage(EmojiGifText.flag + "_${index + 1}>", true); }, ); }, itemCount: EmojiGifUitl.instance.emojiMap.length, padding: EdgeInsets.all(5.0), ), ); } void _openAlterSelectPage() async { bool isGroup = Provider.of(context); if (isGroup) { GroupInfoModel groupInfoModel = Provider.of(context); editFocus.unfocus(); Provider.of(context, listen: false) .changeReadOnlyKey(true); GroupMemberModel member = await AlterSelectPage.pickAlterUser(context, groupInfoModel); if (member != null) { print('选中的成员~~~~~~~~~~~~ ${member.refName}'); _textCtrl.text = '${_textCtrl.text}${member.refName} '; alterMemberList.add(member); } editFocus.requestFocus(); Provider.of(context, listen: false) .changeReadOnlyKey(false); } } List getAlterUsers(String messageText) { List alterUserList = []; RegExp alterStr = RegExp(r'@+(\S+)'); Iterable matches = alterStr.allMatches(messageText); print('~~~~~~~~~~~~~~${matches.length}~~~~~~~~~~~~~~~'); for (Match m in matches) { print('~~~~~~~~~~~~~~${m.group(1)}~~~~~~~~~~~~~~~'); alterUserList.add(m.group(1)); } List finalAlterList = []; for (var member in alterMemberList) { if (alterUserList.contains(member.refName)) { finalAlterList.add(member.memberId); print('有效成员 ${member.refName}'); } } return finalAlterList; } void _openPhotoView() async { // Provider.of(context, listen: false) // .changeSelectIndex(4); var photos = await PhotoPicker.pickAsset( context: context, themeColor: Color(0xFFF0F0F0), textColor: Color(0xFF3F3F3F), pickType: PickType.onlyImage); if (photos != null && photos.length > 0) { for (var i = 0; i < photos.length; i++) { AssetEntity photoEntity = photos[i]; var file = await photoEntity.file; _sendPhotoFile(file); } } ///防止再点键盘不谈 } void _sendSoundMsg(String soundPath, int duration) { bool isGroup = Provider.of(context); int friendId = 0; if (!isGroup) { friendId = Provider.of(context); } print('群聊模式 $isGroup'); var msg = MsgHandler.createSendMsg( ChatType.ShortVoiceChatType, Uint8List(0), localFile: soundPath, friendId: friendId, extra: duration, channelType: isGroup ? ChatChannelType.Group : ChatChannelType.Session); widget.sendMsg(msg); } void _sendPhotoFile(File imgFile) async { var imgSize = await imgFile.length(); print('图片大小:${imgSize / 1024}KB'); var sendImg; if (imgSize > 33 * 1024 * 1024) { showToast(I18n.of(context).video_more_big); return; } bool isNeedUpload = false; if (imgSize > ImgSizeLimit) { print('图片大于 $ImgSizeLimit,压缩'); //发送压缩图 WidgetUtil.getCompressImg(path,quality: 80,percentage: 80); sendImg = await WidgetUtil.getCompressImg(imgFile.absolute.path ); isNeedUpload = true; } else { sendImg = imgFile.readAsBytesSync(); } var rect = await WidgetUtil.getImageWH( image: Image.memory(Uint8List.fromList(sendImg))); print('图片大小 Rect : $rect'); int aspectRatio = rect.width * 100 ~/ rect.height; bool isGroup = Provider.of(context); print('群聊输入模式 $isGroup'); int friendId = 0; if (!isGroup) { friendId = Provider.of(context); } var msg = MsgHandler.createSendMsg(ChatType.ImageChatType, sendImg, localFile: isNeedUpload ? imgFile.absolute.path : null, extra: aspectRatio, friendId: friendId, channelType: isGroup ? ChatChannelType.Group : ChatChannelType.Session); widget.sendMsg(msg); } Future _textMessageSubmitted(String text) async { if (!checkMessage()) { _textCtrl.clear(); alterMemberList.clear(); lastTxtLen = 0; return; } _textCtrl.clear(); _sendMessage(text); } _sendTextMessage() { if (!checkMessage()) { return; } var sendStr = _textCtrl.text; _textCtrl.clear(); _sendMessage(sendStr); } bool checkMessage() { if (_textCtrl.text.length == 0) { showToast(I18n.of(context).msg_not); return false; } return true; } _sendMessage(String messageText, [bool isGift = false]) { bool isGroup = Provider.of(context); int friendId = 0; List alterUsers; if (!isGroup) { friendId = Provider.of(context); } else { if (!isGift) { //检查@的人员 alterUsers = getAlterUsers(messageText); } } MsgModel msg = MsgHandler.createSendMsg( isGift ? ChatType.EmoticonType : ChatType.TextChatType, messageText, friendId: friendId, refMsg: refMsg, refShortTxt: _getRefShortText(), altUsers: alterUsers, channelType: isGroup ? ChatChannelType.Group : ChatChannelType.Session); widget.sendMsg(msg); if (!isGift) { if (refMsg != null) { refMsg = null; } alterMemberList.clear(); lastTxtLen = 0; } setState(() { _isComposingMessage = _textCtrl.text.length > 0; }); } ///emoji插入表情-处理换行 static void insertText(String text, TextEditingController _textCtrl) { var value = _textCtrl.value; var start = value.selection.baseOffset; var end = value.selection.extentOffset; if (value.selection.isValid) { String newText = ""; if (value.selection.isCollapsed) { if (end > 0) { newText += value.text.substring(0, end); } newText += text; if (value.text.length > end) { newText += value.text.substring(end, value.text.length); } } else { newText = value.text.replaceRange(start, end, text); end = start; } _textCtrl.value = value.copyWith( text: newText, selection: value.selection.copyWith( baseOffset: end + text.length, extentOffset: end + text.length)); } else { _textCtrl.value = TextEditingValue( text: text, selection: TextSelection.fromPosition(TextPosition(offset: text.length))); } } static void deleteText(TextEditingController _textController) { var value = _textController.value; var selection = value.selection; var text = value.text; String newText = ''; if (selection.baseOffset != selection.extentOffset) { newText = selection.textBefore(text) + selection.textAfter(text); _textController.value = TextEditingValue( text: newText, selection: selection.copyWith( baseOffset: selection.baseOffset, extentOffset: selection.baseOffset)); } else { int offsetLength = 1; String text = _textController.text; if (text.substring(text.length - 1, text.length) == ']') { ///删除表情 int end = _textController.text.lastIndexOf('['); offsetLength = text.length - end; } newText = text.substring(0, selection.baseOffset - offsetLength) + selection.textAfter(text); _textController.value = TextEditingValue( text: newText, selection: selection.copyWith( baseOffset: selection.baseOffset - offsetLength, extentOffset: selection.baseOffset - offsetLength)); } } }