import 'dart:convert'; import 'dart:io'; import 'package:chat/data/translate_hk_data_mgr.dart'; import 'package:chat/home/audio_chat_view.dart'; import 'package:chat/home/coin_anim.dart'; import 'package:chat/utils/screen.dart'; import 'package:chat/utils/upload_util.dart'; import 'package:fixnum/fixnum.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:chat/chat/translate_state.dart'; import 'package:chat/data/UserData.dart'; import 'package:chat/data/chat_data_mgr.dart'; import 'package:chat/data/constants.dart'; import 'package:chat/generated/i18n.dart'; import 'package:chat/home/add_friend.dart'; import 'package:chat/models/ChatMsg.dart'; import 'package:chat/models/UserInfo.dart'; import 'package:chat/models/keyboard_provider.dart'; import 'package:chat/models/ref_name_provider.dart'; import 'package:chat/models/voucher_change.dart'; import 'package:chat/proto/all.pbserver.dart'; import 'package:chat/utils/CustomUI.dart'; import 'package:chat/utils/HttpUtil.dart'; import 'package:chat/utils/MessageMgr.dart'; import 'package:chat/utils/TokenMgr.dart'; import 'package:chat/utils/analyze_utils.dart'; import 'package:chat/utils/app_navigator.dart'; import 'package:chat/utils/blacklist_mgr.dart'; import 'package:chat/utils/count_down_button.dart'; import 'package:chat/utils/friend_list_mgr.dart'; import 'package:chat/utils/msgHandler.dart'; import 'package:chat/utils/net_state_widget.dart'; import 'package:chat/utils/sound_util.dart'; import 'package:chat/utils/sp_utils.dart'; import 'package:chat/utils/sql_util.dart'; import 'package:dio/dio.dart'; import 'package:extended_text/extended_text.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:oktoast/oktoast.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; //import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import '../r.dart'; import 'ChatPageItem.dart'; import 'input_bar.dart'; import 'package:chat/utils/PopUpMenu.dart' as myPop; import 'package:chat/models/money_change.dart'; class ChatPage extends StatefulWidget { static bool isChatPageActive = false; final int friendId; final int enterType; // 0默认 1图片 final dynamic enterContent; final bool isTranslateButler; ChatPage( {Key key, this.friendId, this.enterType = 0, this.enterContent, this.isTranslateButler = false}) : super(key: key); _ChatPageState createState() => _ChatPageState(); } class _ChatPageState extends State { MessageMgr msgMgr = MessageMgr(); UserInfo friendInfo; List msgList; KeyboardIndexProvider _keyboardIndexProvider = KeyboardIndexProvider(); TextEditingController nickNameController = new TextEditingController(); //统计聊天时长 int startTime; bool isTranslateButler; bool isTranslateButlerFinish = false; ///翻译管家是否已经结束 int jumpTime; AutoScrollController controller; bool hasChatPermission = false; bool isPlayCoinAnim = false; @override void dispose() { var endTime = DateTime.now().millisecondsSinceEpoch ~/ 1000; AnalyzeUtils.commitChatDuration(startTime, endTime); MessageMgr().off('Send CoinBag', _sendCoin); msgMgr.off('New Chat Message', receiveMsg); msgMgr.off('Keyboard Hide', dealWithKeyboardHide); msgMgr.off('Delete Select Message', _deleteItem); msgMgr.off(MessageMgr.TRANSLATE_HK_END_CHAT, translateHKChatEnd); msgMgr.off('Jump to Msg', jumpToMsg); MsgHandler.curActiveSession = 0; SoundUtils().stop(); nickNameController.dispose(); super.dispose(); ChatPage.isChatPageActive = false; } jumpToMsg(time) async { hideKeyBoard(); int jumIndex = 0; jumpTime = time; for (int i = 0; i < msgList.length; i++) { if (time == msgList[i].time) { jumIndex = i; break; } } if (jumIndex < 0) { jumIndex = 0; } if (jumIndex > msgList.length - 1) { jumIndex = msgList.length - 1; } await controller.scrollToIndex(jumIndex, preferPosition: AutoScrollPosition.middle); controller.highlight(jumIndex, highlightDuration: new Duration(milliseconds: 100)); } @override void initState() { super.initState(); print('init chatpage'); ChatPage.isChatPageActive = true; getDefaultSetting(); getUserInfo(); controller = AutoScrollController( viewportBoundaryGetter: () => Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom), axis: Axis.vertical); isTranslateButler = widget.isTranslateButler; ///todo 这里再判断是否还在服务时间 startTime = DateTime.now().millisecondsSinceEpoch ~/ 1000; msgList = ChatDataMgr().getRecord(); msgMgr.on('New Chat Message', receiveMsg); msgMgr.on('Keyboard Hide', dealWithKeyboardHide); msgMgr.on('Send CoinBag', _sendCoin); msgMgr.on('Delete Select Message', _deleteItem); msgMgr.on(MessageMgr.TRANSLATE_HK_END_CHAT, translateHKChatEnd); msgMgr.on('Jump to Msg', jumpToMsg); WidgetsBinding.instance.addPostFrameCallback((_) { checkPermission(); }); } checkPermission() async { if (isTranslateButler) { if (await CustomUI.showPermissionSetting(context, PermissionGroup.microphone, I18n.of(context).video_permission)) { setState(() { hasChatPermission = true; dx = Screen.width - zoomButtonSizeWidth; dy = (Screen.height - zoomButtonSizeHeight) / 2; }); } else { hasChatPermission = false; showToast(I18n.of(context).need_record); } } } translateHKChatEnd(args) { setState(() { isShowZoomButton = false; isTranslateButlerFinish = true; TranslateHKMgr().order = null; }); if (!UserData().isTranslateUser) { var cancle = InkWell( onTap: () { Navigator.pop(context); }, child: Container( decoration: BoxDecoration( color: const Color(0XFFC7E5FF), borderRadius: BorderRadius.all( Radius.circular(Constants.LittleButtonRadius))), margin: EdgeInsets.only(top: 0, bottom: 18.5), height: 37.5, width: 200, alignment: Alignment.center, child: CountDownButton( I18n.of(context).cancel, () { Navigator.of(context).pop(); }, isOnlyRichText: true, countDownTime: 10, )), ); CustomUI.buildTowConfirmWithCountDown(context, '是否再来一单', '是的', () { MsgHandler.sendAnotherOrderReq(); Navigator.of(context).pop(); }, cancle); } } void _sendFile(File file) async { // File file = await FilePicker.getFile(); int fileSize = file.lengthSync(); print('选择的文件 ${file.path} 大小 $fileSize'); if (fileSize > 33 * 1024 * 1024) { showToast(I18n.of(context).max_file.replaceFirst('/s1', 33.toString())); return; } var fileName = file.path.split('/').last; print('fileName $fileName'); var ext = ''; var extList = fileName.split('.'); if (extList.length > 1) { ext = extList.last; } print('ext $ext'); var fileMsg = FileChat.create(); fileMsg.type = ext; fileMsg.size = fileSize; fileMsg.name = fileName; var msg = MsgHandler.createSendMsg( ChatType.FileChatType, fileMsg.writeToBuffer(), friendId: widget.friendId, localFile: file.path, channelType: ChatChannelType.Session); sendMsg(msg); } _sendCoin(args) async { var res = await _checkCoinBeforeSend(args['amount']); if (res != null) { args['redNo'] = res['redNo']; args['friendId'] = widget.friendId; var msg = MsgHandler.createCoinBagMsg(args); sendMsg(msg); } } //校验 _checkCoinBeforeSend(int amount) async { Map data = { "userId": UserData().basicInfo.userId, "rUserId": friendInfo.userId, "price": amount, }; data['sign'] = TokenMgr().getSign(data); Response res = await HttpUtil().post('red/packet/send', data: data); if (res == null) { return null; } Map resData = res.data; if (resData['code'] == 0) { print(resData['data']); return resData['data']; } return null; } void getDefaultSetting() async { bool soundPlayMode = (await SPUtils.getBool(Constants.SOUND_PLAY_MODE)) ?? false; _keyboardIndexProvider.init(soundPlayMode); } dealWithKeyboardHide(args) { if (_keyboardIndexProvider.curKeyboardIndex == 0) { hideKeyBoard(); } } getUserInfo() async { //先从本地获取用户信息 friendInfo = await HttpUtil().getFriendInfo(widget.friendId, true); //如果是新的聊天,向服务器发送创建会话消息 if (msgList.length == 0) { MsgHandler.getActiveSesstion( [friendInfo.userId, UserData().basicInfo.userId]); } if (mounted) { setState(() {}); } WidgetsBinding.instance.addPostFrameCallback((_) { if (widget.enterType == 1) { print('接收到的:${widget.enterContent}'); _sendFile(File(widget.enterContent)); } else if (widget.enterType == 2 || widget.enterType == 3) { //转发消息 MsgModel originMsg = widget.enterContent; MsgModel msg = MsgHandler.createSendMsg( ChatType.valueOf(originMsg.msgType), originMsg.msgContent); msg.extraInfo = originMsg.extraInfo; if (originMsg.extraFile == null || originMsg.extraFile.contains('http')) { msg.extraFile = originMsg.extraFile; } else { msg.extraFile = UploadUtil().getFullUrl( originMsg.extraFile, originMsg.sessionId, originMsg.channelType); } msg.localFile = originMsg.localFile; msg.friendId = widget.friendId; if (msg.localFile != null) { msg.state = MsgState.Uploaded; } sendMsg(msg); } }); } @override Widget build(BuildContext context) { print('build chatpage'); if (friendInfo == null) { return Scaffold( backgroundColor: const Color(0xFFE2E9F1), body: SafeArea( child: Center( child: CircularProgressIndicator(), ), ), ); } List actions = []; int voucher = Provider.of(context).voucher; actions.add(Row( children: [ CustomUI.buildImageLabel("assets/images/voucher.png", voucher, imgOpc: 0.5, imgHeight: 13), Stack(alignment: Alignment.centerLeft, children: [ CustomUI.buildImageLabel(R.assetsImagesCoin, Provider.of(context).money, isLeft: false, isPlayAnim: isPlayCoinAnim), isPlayCoinAnim ? CoinAnim( animFinishCallback: () { setState(() { isPlayCoinAnim = false; }); }, ) : Text('') ]), ], )); actions.add(TranslateSateWidget(friendId: friendInfo.userId)); actions.add(Container( margin: EdgeInsets.only(top: 1), child: myPop.PopupMenuButton( icon: Icon( Icons.more_horiz, size: 24, ), offset: Offset(0, 100), onSelected: (int index) { if (index == 2) { AppNavigator.pushInformUserPage( context, friendInfo.sex == 1, friendInfo.userId); } else if (index == 1) { Navigator.of(context).push( new MaterialPageRoute( builder: (context) { return AddFriendPage( userId: friendInfo.userId, pageType: SendMessagePageType.Remark, originalName: Provider.of(context) .getRefName( friendInfo.userId, friendInfo.nickName)); }, ), ); } }, itemBuilder: (BuildContext context) { return >[ myPop.PopupMenuItem( child: Container( alignment: Alignment.center, color: Colors.white, padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10), child: Text(I18n.of(context).anonymous_report, textScaleFactor: 1.0, maxLines: 1, style: TextStyle( color: Color(AppColors.AppBarColor), fontSize: 12)), ), value: 2, ), myPop.PopupMenuItem( child: Container( decoration: BoxDecoration( border: Border( top: BorderSide(width: 1, color: Colors.grey[300])), color: Colors.white, ), alignment: Alignment.center, padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10), child: Text(I18n.of(context).Remark, textScaleFactor: 1.0, maxLines: 1, style: TextStyle( color: Color(AppColors.AppBarColor), fontSize: 12)), ), value: 1, ), ]; }))); var allItem = Stack( children: [ MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => _keyboardIndexProvider), Provider.value(value: false), Provider.value(value: widget.friendId), ], child: GestureDetector( onTap: hideKeyBoard, child: ExtendedTextSelectionPointerHandler( ///选择文字,消除弹窗 builder: (states) { return Listener( child: Scaffold( resizeToAvoidBottomInset: false, backgroundColor: const Color(0xFFE2E9F1), appBar: AppBar( title: Text( '${Provider.of(context).getRefName(friendInfo.userId, friendInfo.nickName)}', textScaleFactor: 1.0, style: TextStyle( color: Constants.BlackTextColor, fontSize: 16.47), ), leading: CustomUI.buildCustomLeading(context, onTap: () { goBackCheck(); }), titleSpacing: -10, centerTitle: false, elevation: 1, actions: actions), body: SafeArea( child: Column( children: [ NetStateWidget(), (isTranslateButler) ? _buildTranslationButler() : Container(), Expanded(child: _buildMessageList()), InputBar( sendMsg: sendMsg, isTranslateHK: isTranslateButler, ), ], ))), behavior: HitTestBehavior.translucent, onPointerDown: (value) { for (var state in states) { if (!state.containsPosition(value.position)) { //clear other selection state.clearSelection(); } } }, onPointerMove: (value) { //clear other selection for (var state in states) { if (!state.containsPosition(value.position)) { //clear other selection state.clearSelection(); } } }, ); }, ))), isTranslateButler ? getAudioChatView() : Container(), isTranslateButler ? zoomAudioButton() : Container() ], ); return isTranslateButler ? WillPopScope( child: allItem, onWillPop: () { if (isTranslateButler && !isTranslateButlerFinish) { if (UserData().isTranslateUser) { showToast( I18n.of(context).translation_butler_order_close_tips); } else { CustomUI.buildTowConfirm( context, I18n.of(context).translation_butler_order_close_tips2, I18n.of(context).confirm, () { setState(() { isTranslateButlerFinish = true; }); MsgHandler.sendEndTransHKOrderReq(); Navigator.of(context).pop(); }, I18n.of(context).cancel, () { Navigator.of(context).pop(); }); } } else { Navigator.of(context).pop(); } return Future.value(false); }) : allItem; } addCoin() { setState(() { isPlayCoinAnim = true; }); MessageMgr().emit('addCoin'); } goBackCheck() { if (isTranslateButler && !isTranslateButlerFinish) { if (UserData().isTranslateUser) { showToast('翻译服务未结束不能主动结束'); } else { CustomUI.buildTowConfirm( context, '是否提前结束翻译管家服务?', I18n.of(context).confirm, () { setState(() { isTranslateButlerFinish = true; }); MsgHandler.sendEndTransHKOrderReq(); Navigator.of(context).pop(); }, I18n.of(context).cancel, () { Navigator.of(context).pop(); }); } } else { Navigator.of(context).pop(); } } Widget getAudioChatView() { if (friendInfo == null || isTranslateButlerFinish || !hasChatPermission) { return Container(); } return isActive ? Offstage( offstage: !isShowAudio, child: AudioChatPage( userInfo: friendInfo, isTranslateButler: true, isReplay: !TranslateHKMgr().isUser, translateButlerCloseCallBack: audioChatPageCallBack, ), ) : Container(); } audioChatPageCallBack(int args) { switch (args) { case 1: setState(() { isShowAudio = !isShowAudio; isShowZoomButton = true; }); break; case 2: setState(() { isActive = false; }); break; } } bool isShowAudio = false; ///控住连麦窗口是否显示 bool isActive = true; ///控制连麦是否关闭 bool isShowZoomButton = true; GlobalKey mykey = GlobalKey(); double dx = 0, dy = 0; double zoomButtonSizeWidth = 60; double zoomButtonSizeHeight = 74; void dragEvent(DragUpdateDetails details) { final RenderObject F = context.findRenderObject(); // 获得自定义Widget的大小,用来计算Widget的中心锚点 dx = details.globalPosition.dx - mykey.currentContext.size.width / 2; dy = details.globalPosition.dy - mykey.currentContext.size.height / 2; // print('dx $dx dy $dy screen width:${Screen.width}'); if (dx > Screen.width - zoomButtonSizeWidth) { dx = Screen.width - zoomButtonSizeWidth; } else if (dx < 0) { dx = 0; } if (dy > Screen.height - zoomButtonSizeHeight) { dy = Screen.height - zoomButtonSizeHeight; } else if (dy < 0) { dy = 0; } setState(() {}); } Widget zoomAudioButton() { if (friendInfo == null || !isShowZoomButton || !hasChatPermission) { return Container(); } Widget button = Container( key: mykey, width: zoomButtonSizeWidth, height: zoomButtonSizeHeight, child: Card( child: Align( child: Icon( IconData(0xe67d, fontFamily: Constants.IconFontFamily), color: Color(0xFF008AFF), size: 35.0, ), alignment: Alignment.center, ), ), ); return GestureDetector( onHorizontalDragUpdate: dragEvent, onVerticalDragUpdate: dragEvent, onTap: () { setState(() { isShowAudio = !isShowAudio; isShowZoomButton = !isShowZoomButton; }); }, child: Container( child: Transform.translate( offset: Offset(dx, dy), child: Align( alignment: Alignment.topLeft, child: button, ), ), ), ); } Widget _buildTranslationButler() { bool hasHeadImg = true; if (friendInfo.headimgurl == null || friendInfo.headimgurl.length == 0) { hasHeadImg = false; } /// 5H币/1分钟 String coinTIme = I18n.of(context).translation_butler_coin_time; coinTIme = coinTIme.replaceAll('/s1', '5'); coinTIme = coinTIme.replaceAll('/s2', '1'); return Container( padding: EdgeInsets.fromLTRB(10, 10, 10, 10), decoration: BoxDecoration(color: Colors.white,border: Border(bottom: BorderSide(color: Colors.grey, width: .3))), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(8), child: hasHeadImg ? CachedNetworkImage( imageUrl: friendInfo.headimgurl, placeholder: (context, url) => Image.asset( Constants.DefaultHeadImgUrl, width: 54, height: 54, ), width: 54, height: 54, ) : SizedBox( width: 54, height: 54, child: Image.asset(R.assetsImagesDefaultNorAvatar))), Padding( padding: EdgeInsets.only(left: 10), child: Container( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( I18n.of(context).translation_butler, textScaleFactor: 1.0, overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( color: AppColors.NewAppbarTextColor, fontSize: 15), ), SizedBox( height: 5, ), Text( coinTIme, textScaleFactor: 1.0, maxLines: 1, style: TextStyle(color: Color(0xFF797979), fontSize: 13), ) ], ), constraints: BoxConstraints(maxWidth: 135), ), ), (TranslateHKMgr().isUser && !isTranslateButlerFinish) ? Expanded( child: Container( constraints: BoxConstraints(maxWidth: 130), width: double.maxFinite, child: CountDownButton( I18n.of(context).translation_butler_end_service, () { // Navigator.of(context).pop(); }, countDownTime: 60 * 5, align: Alignment.centerRight, onPress: () { MsgHandler.sendEndTransHKOrderReq(); // Navigator.of(context).pop(); }, ), // alignment: Alignment(1,0), )) : Container(), ], ), ); } Widget _buildMessageList() { return Container( alignment: Alignment.topCenter, child: msgList.length == 0 ? Padding( padding: EdgeInsets.all(8), child: Text( I18n.of(context).chat_tips, textAlign: TextAlign.center, textScaleFactor: 1.0, style: TextStyle(color: Colors.grey, fontSize: 12), )) : NotificationListener( child: Scrollbar( child: ListView.builder( controller: controller, physics: new ClampingScrollPhysics(), itemCount: msgList.length, itemBuilder: _buildItem, padding: EdgeInsets.symmetric(vertical: 8), reverse: true, shrinkWrap: true, )), onNotification: (notification) { if (notification is ScrollNotification) {} return true; }, ), ); } hideKeyBoard() { _keyboardIndexProvider.changeSelectIndex(-1); } readOnly() { _keyboardIndexProvider.changeReadOnlyKey(true); } sendMsg(MsgModel msg) async { // if(widget.isTranslateButler){ ///翻译管家聊天通道 // msg.channelType = ChatChannelType.TransHK.value; // } print('对方是否拉黑你 ${friendInfo.isBlackened}'); if (BlacklistMgr.isBlack(friendInfo.userId)) { return; } print('chat page session id:${msg.sessionId}'); if (!friendInfo.isCanStrangerNews && !FriendListMgr().isMyFriend(friendInfo.userId)) { showToast(I18n.of(context).stranger_close_tips); return; } MsgHandler.insertMsgToDB(msg); MsgHandler.sendChatMsg(msg); if (mounted) { setState(() {}); await controller.scrollToIndex(0, preferPosition: AutoScrollPosition.begin); } } MsgModel msg; int count = 0; testBig(MsgModel msg) async { for (int k = 0; k < 100; k++) { msg.msgContent = utf8.encode('测试$count'); Int64 time = Int64((DateTime.now()).millisecondsSinceEpoch); msg.time = time.toInt(); MsgHandler.insertMsgToDB(msg); MsgHandler.sendChatMsg(msg); await Future.delayed(Duration(milliseconds: 500), () {}); count++; } count = 0; print('发送完毕'); showToast('发送完毕'); } void receiveMsg(args) { if (mounted) { setState(() {}); } } _deleteItem(msg) { MessageMgr().emit('Cancel Request', msg); print('#### 开始删除--'); msgList.remove(msg); setState(() {}); SqlUtil().deleteSigleRecordWith(msg.sessionId, msg.time); } Widget _wrapScrollTag({int index, Widget child}) => AutoScrollTag( key: ValueKey(index), controller: controller, index: index, child: child, highlightColor: Colors.white.withOpacity(1), ); Widget _buildItem(BuildContext context, int index) { var lastMsgTime; if (index < msgList.length - 1) { lastMsgTime = msgList[index + 1].time; } MsgModel msg = msgList[index]; return _wrapScrollTag( index: index, child: ChatPageItem( key: Key(msg.time.toString()), msg: msg, hideKeyboard: readOnly, friendInfo: friendInfo, lastMsgTime: lastMsgTime)); } }