Hibok
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

752 line
24 KiB

  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:chat/data/translate_hk_data_mgr.dart';
  4. import 'package:chat/home/audio_chat_view.dart';
  5. import 'package:chat/utils/screen.dart';
  6. import 'package:chat/utils/upload_util.dart';
  7. import 'package:fixnum/fixnum.dart';
  8. import 'package:cached_network_image/cached_network_image.dart';
  9. import 'package:chat/chat/translate_state.dart';
  10. import 'package:chat/data/UserData.dart';
  11. import 'package:chat/data/chat_data_mgr.dart';
  12. import 'package:chat/data/constants.dart';
  13. import 'package:chat/generated/i18n.dart';
  14. import 'package:chat/home/add_friend.dart';
  15. import 'package:chat/models/ChatMsg.dart';
  16. import 'package:chat/models/UserInfo.dart';
  17. import 'package:chat/models/keyboard_provider.dart';
  18. import 'package:chat/models/ref_name_provider.dart';
  19. import 'package:chat/models/voucher_change.dart';
  20. import 'package:chat/proto/all.pbserver.dart';
  21. import 'package:chat/utils/CustomUI.dart';
  22. import 'package:chat/utils/HttpUtil.dart';
  23. import 'package:chat/utils/MessageMgr.dart';
  24. import 'package:chat/utils/TokenMgr.dart';
  25. import 'package:chat/utils/analyze_utils.dart';
  26. import 'package:chat/utils/app_navigator.dart';
  27. import 'package:chat/utils/blacklist_mgr.dart';
  28. import 'package:chat/utils/count_down_button.dart';
  29. import 'package:chat/utils/friend_list_mgr.dart';
  30. import 'package:chat/utils/msgHandler.dart';
  31. import 'package:chat/utils/net_state_widget.dart';
  32. import 'package:chat/utils/sound_util.dart';
  33. import 'package:chat/utils/sp_utils.dart';
  34. import 'package:chat/utils/sql_util.dart';
  35. import 'package:dio/dio.dart';
  36. import 'package:extended_text/extended_text.dart';
  37. import 'package:flutter/cupertino.dart';
  38. import 'package:flutter/material.dart';
  39. import 'package:flutter/services.dart';
  40. import 'package:oktoast/oktoast.dart';
  41. import 'package:provider/provider.dart';
  42. import '../r.dart';
  43. import 'ChatPageItem.dart';
  44. import 'input_bar.dart';
  45. import 'package:chat/utils/PopUpMenu.dart' as myPop;
  46. import 'package:chat/models/money_change.dart';
  47. class ChatPage extends StatefulWidget {
  48. final int friendId;
  49. final int enterType; // 0默认 1图片
  50. final dynamic enterContent;
  51. final bool isTranslateButler;
  52. ChatPage(
  53. {Key key,
  54. this.friendId,
  55. this.enterType = 0,
  56. this.enterContent,
  57. this.isTranslateButler = false})
  58. : super(key: key);
  59. _ChatPageState createState() => _ChatPageState();
  60. }
  61. class _ChatPageState extends State<ChatPage> {
  62. ScrollController _scrollCtrl = ScrollController();
  63. MessageMgr msgMgr = MessageMgr();
  64. UserInfo friendInfo;
  65. List<MsgModel> msgList;
  66. KeyboardIndexProvider _keyboardIndexProvider = KeyboardIndexProvider();
  67. TextEditingController nickNameController = new TextEditingController();
  68. //统计聊天时长
  69. int startTime;
  70. bool isTranslateButler;
  71. bool isTranslateButlerFinish=false;///翻译管家是否已经结束
  72. @override
  73. void dispose() {
  74. var endTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
  75. AnalyzeUtils.commitChatDuration(startTime, endTime);
  76. MessageMgr().off('Send CoinBag', _sendCoin);
  77. msgMgr.off('New Chat Message', receiveMsg);
  78. msgMgr.off('Keyboard Hide', dealWithKeyboardHide);
  79. msgMgr.off('Delete Select Message', _deleteItem);
  80. msgMgr.off(MessageMgr.TRANSLATE_HK_END_CHAT, translateHKChatEnd);
  81. MsgHandler.curActiveSession = 0;
  82. SoundUtils().stop();
  83. nickNameController.dispose();
  84. _scrollCtrl.dispose();
  85. super.dispose();
  86. }
  87. @override
  88. void initState() {
  89. super.initState();
  90. print('init chatpage');
  91. getDefaultSetting();
  92. getUserInfo();
  93. isTranslateButler = widget.isTranslateButler;
  94. ///todo 这里再判断是否还在服务时间
  95. startTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
  96. msgList = ChatDataMgr().getRecord();
  97. msgMgr.on('New Chat Message', receiveMsg);
  98. msgMgr.on('Keyboard Hide', dealWithKeyboardHide);
  99. msgMgr.on('Send CoinBag', _sendCoin);
  100. msgMgr.on('Delete Select Message', _deleteItem);
  101. msgMgr.on(MessageMgr.TRANSLATE_HK_END_CHAT, translateHKChatEnd);
  102. }
  103. translateHKChatEnd(args){
  104. setState(() {
  105. isTranslateButlerFinish=true;
  106. });
  107. }
  108. void _sendFile(File file) async {
  109. // File file = await FilePicker.getFile();
  110. int fileSize = file.lengthSync();
  111. print('选择的文件 ${file.path} 大小 $fileSize');
  112. if (fileSize > 33 * 1024 * 1024) {
  113. showToast(I18n.of(context).max_file.replaceFirst('/s1', 33.toString()));
  114. return;
  115. }
  116. var fileName = file.path.split('/').last;
  117. print('fileName $fileName');
  118. var ext = '';
  119. var extList = fileName.split('.');
  120. if (extList.length > 1) {
  121. ext = extList.last;
  122. }
  123. print('ext $ext');
  124. var fileMsg = FileChat.create();
  125. fileMsg.type = ext;
  126. fileMsg.size = fileSize;
  127. fileMsg.name = fileName;
  128. var msg = MsgHandler.createSendMsg(
  129. ChatType.FileChatType, fileMsg.writeToBuffer(),
  130. friendId: widget.friendId,
  131. localFile: file.path,
  132. channelType: ChatChannelType.Session);
  133. sendMsg(msg);
  134. }
  135. _sendCoin(args) async {
  136. var res = await _checkCoinBeforeSend(args['amount']);
  137. if (res != null) {
  138. args['redNo'] = res['redNo'];
  139. args['friendId'] = widget.friendId;
  140. var msg = MsgHandler.createCoinBagMsg(args);
  141. sendMsg(msg);
  142. }
  143. }
  144. //校验
  145. _checkCoinBeforeSend(int amount) async {
  146. Map data = {
  147. "userId": UserData().basicInfo.userId,
  148. "rUserId": friendInfo.userId,
  149. "price": amount,
  150. };
  151. data['sign'] = TokenMgr().getSign(data);
  152. Response res = await HttpUtil().post('red/packet/send', data: data);
  153. if (res == null) {
  154. return null;
  155. }
  156. Map resData = res.data;
  157. if (resData['code'] == 0) {
  158. print(resData['data']);
  159. return resData['data'];
  160. }
  161. return null;
  162. }
  163. void getDefaultSetting() async {
  164. bool soundPlayMode =
  165. (await SPUtils.getBool(Constants.SOUND_PLAY_MODE)) ?? false;
  166. _keyboardIndexProvider.init(soundPlayMode);
  167. }
  168. dealWithKeyboardHide(args) {
  169. if (_keyboardIndexProvider.curKeyboardIndex == 0) {
  170. hideKeyBoard();
  171. }
  172. }
  173. getUserInfo() async {
  174. //先从本地获取用户信息
  175. friendInfo = await HttpUtil().getFriendInfo(widget.friendId, true);
  176. //如果是新的聊天,向服务器发送创建会话消息
  177. if (msgList.length == 0) {
  178. MsgHandler.getActiveSesstion(
  179. [friendInfo.userId, UserData().basicInfo.userId]);
  180. }
  181. if (mounted) {
  182. setState(() {});
  183. }
  184. WidgetsBinding.instance.addPostFrameCallback((_) {
  185. if (widget.enterType == 1) {
  186. print('接收到的:${widget.enterContent}');
  187. _sendFile(File(widget.enterContent));
  188. } else if (widget.enterType == 2) {
  189. //转发消息
  190. MsgModel originMsg = widget.enterContent;
  191. MsgModel msg = MsgHandler.createSendMsg(
  192. ChatType.valueOf(originMsg.msgType), originMsg.msgContent);
  193. msg.extraInfo = originMsg.extraInfo;
  194. if (originMsg.extraFile == null ||
  195. originMsg.extraFile.contains('http')) {
  196. msg.extraFile = originMsg.extraFile;
  197. } else {
  198. msg.extraFile = UploadUtil().getFullUrl(
  199. originMsg.extraFile, originMsg.sessionId, originMsg.channelType);
  200. }
  201. msg.localFile = originMsg.localFile;
  202. msg.friendId = widget.friendId;
  203. if (msg.localFile != null) {
  204. msg.state = MsgState.Uploaded;
  205. }
  206. sendMsg(msg);
  207. }
  208. });
  209. }
  210. @override
  211. Widget build(BuildContext context) {
  212. print('build chatpage');
  213. if (friendInfo == null) {
  214. return Scaffold(
  215. backgroundColor: const Color(0xFFE2E9F1),
  216. body: SafeArea(
  217. child: Center(
  218. child: CircularProgressIndicator(),
  219. ),
  220. ),
  221. );
  222. }
  223. List<Widget> actions = [];
  224. int voucher = Provider.of<VoucherChangeProvider>(context).voucher;
  225. actions.add(Row(
  226. children: <Widget>[
  227. CustomUI.buildImageLabel("assets/images/voucher.png", voucher,
  228. imgOpc: 0.5, imgHeight: 13),
  229. CustomUI.buildImageLabel(
  230. R.assetsImagesCoin, Provider.of<MoneyChangeProvider>(context).money,
  231. isLeft: false)
  232. ],
  233. ));
  234. actions.add(TranslateSateWidget(friendId: friendInfo.userId));
  235. actions.add(Container(
  236. margin: EdgeInsets.only(top: 1),
  237. child: myPop.PopupMenuButton(
  238. icon: Icon(
  239. Icons.more_horiz,
  240. size: 24,
  241. ),
  242. offset: Offset(0, 100),
  243. onSelected: (int index) {
  244. if (index == 2) {
  245. AppNavigator.pushInformUserPage(
  246. context, friendInfo.sex == 1, friendInfo.userId);
  247. } else if (index == 1) {
  248. Navigator.of(context).push(
  249. new MaterialPageRoute(
  250. builder: (context) {
  251. return AddFriendPage(
  252. userId: friendInfo.userId,
  253. pageType: SendMessagePageType.Remark,
  254. originalName: Provider.of<RefNameProvider>(context)
  255. .getRefName(
  256. friendInfo.userId, friendInfo.nickName));
  257. },
  258. ),
  259. );
  260. }
  261. },
  262. itemBuilder: (BuildContext context) {
  263. return <myPop.PopupMenuItem<int>>[
  264. myPop.PopupMenuItem(
  265. child: Container(
  266. alignment: Alignment.center,
  267. color: Colors.white,
  268. padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
  269. child: Text(I18n.of(context).anonymous_report,
  270. textScaleFactor: 1.0,
  271. maxLines: 1,
  272. style: TextStyle(
  273. color: Color(AppColors.AppBarColor), fontSize: 12)),
  274. ),
  275. value: 2,
  276. ),
  277. myPop.PopupMenuItem(
  278. child: Container(
  279. decoration: BoxDecoration(
  280. border: Border(
  281. top: BorderSide(width: 1, color: Colors.grey[300])),
  282. color: Colors.white,
  283. ),
  284. alignment: Alignment.center,
  285. padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
  286. child: Text(I18n.of(context).Remark,
  287. textScaleFactor: 1.0,
  288. maxLines: 1,
  289. style: TextStyle(
  290. color: Color(AppColors.AppBarColor), fontSize: 12)),
  291. ),
  292. value: 1,
  293. ),
  294. ];
  295. })));
  296. return Stack(
  297. children: <Widget>[
  298. MultiProvider(
  299. providers: [
  300. ChangeNotifierProvider(create: (_) => _keyboardIndexProvider),
  301. Provider<bool>.value(value: false),
  302. Provider<int>.value(value: widget.friendId),
  303. ],
  304. child: GestureDetector(
  305. onTap: hideKeyBoard,
  306. child: ExtendedTextSelectionPointerHandler(
  307. ///选择文字,消除弹窗
  308. builder: (states) {
  309. return Listener(
  310. child: Scaffold(
  311. resizeToAvoidBottomInset: false,
  312. backgroundColor: const Color(0xFFE2E9F1),
  313. appBar: AppBar(
  314. title: Text(
  315. '${Provider.of<RefNameProvider>(context).getRefName(friendInfo.userId, friendInfo.nickName)}',
  316. textScaleFactor: 1.0,
  317. style: TextStyle(
  318. color: Constants.BlackTextColor,
  319. fontSize: 16.47),
  320. ),
  321. leading: CustomUI.buildCustomLeading(context,onTap:(){
  322. if(isTranslateButler && !isTranslateButlerFinish){
  323. if(UserData().isTranslateUser){
  324. showToast('翻译服务未结束不能主动结束');
  325. }else{
  326. CustomUI.buildTowConfirm(
  327. context,
  328. '是否提前结束翻译管家服务?',
  329. I18n.of(context).confirm,
  330. (){
  331. setState(() {
  332. isTranslateButlerFinish=true;
  333. });
  334. MsgHandler.sendEndTransHKOrderReq();
  335. Navigator.of(context).pop();
  336. },
  337. '取消',
  338. (){ Navigator.of(context).pop();});
  339. }
  340. }else{
  341. Navigator.of(context).pop();
  342. }
  343. }), //todo
  344. titleSpacing: -10,
  345. centerTitle: false,
  346. elevation: 1,
  347. actions: actions),
  348. body: SafeArea(
  349. child: Column(
  350. children: <Widget>[
  351. NetStateWidget(),
  352. (isTranslateButler&!isTranslateButlerFinish)
  353. ? _buildTranslationButler()
  354. : Container(),
  355. Expanded(child: _buildMessageList()),
  356. InputBar(sendMsg: sendMsg,isTranslateHK: isTranslateButler,),
  357. ],
  358. ))),
  359. behavior: HitTestBehavior.translucent,
  360. onPointerDown: (value) {
  361. for (var state in states) {
  362. if (!state.containsPosition(value.position)) {
  363. //clear other selection
  364. state.clearSelection();
  365. }
  366. }
  367. },
  368. onPointerMove: (value) {
  369. //clear other selection
  370. for (var state in states) {
  371. if (!state.containsPosition(value.position)) {
  372. //clear other selection
  373. state.clearSelection();
  374. }
  375. }
  376. },
  377. );
  378. },
  379. ))),
  380. isTranslateButler ? getAudioChatView() : Container(),
  381. isTranslateButler ? zoomAudioButton() : Container()
  382. ],
  383. );
  384. }
  385. Widget getAudioChatView() {
  386. if (friendInfo == null || isTranslateButlerFinish) {
  387. return Container();
  388. }
  389. return isActive == null
  390. ? Container()
  391. : Offstage(
  392. offstage: !isShowAudio,
  393. child: AudioChatPage(
  394. userInfo: friendInfo,
  395. isTranslateButler: true,
  396. isReplay: !TranslateHKMgr().isUser,
  397. translateButlerCloseCallBack: audioChatPageCallBack,
  398. ),
  399. );
  400. }
  401. audioChatPageCallBack(int args) {
  402. switch (args) {
  403. case 1:
  404. setState(() {
  405. isShowAudio = !isShowAudio;
  406. isShowZoomButton = true;
  407. });
  408. break;
  409. case 2:
  410. setState(() {
  411. isActive = false;
  412. });
  413. break;
  414. }
  415. }
  416. bool isShowAudio = false;
  417. ///控住连麦窗口是否显示
  418. bool isActive = true;
  419. ///控制连麦是否关闭
  420. bool isShowZoomButton = true;
  421. GlobalKey mykey = GlobalKey();
  422. double dx = Screen.width , dy = Screen.height/2;
  423. double zoomButtonSize = 60;
  424. void dragEvent(DragUpdateDetails details) {
  425. final RenderObject box = context.findRenderObject();
  426. // 获得自定义Widget的大小,用来计算Widget的中心锚点
  427. dx = details.globalPosition.dx - mykey.currentContext.size.width / 2;
  428. dy = details.globalPosition.dy - mykey.currentContext.size.height / 2;
  429. // print('dx $dx dy $dy screen width:${Screen.width}');
  430. if (dx > Screen.width - zoomButtonSize) {
  431. dx = Screen.width - zoomButtonSize;
  432. } else if (dx < 0) {
  433. dx = 0;
  434. }
  435. if (dy > Screen.height - zoomButtonSize) {
  436. dy = Screen.height - zoomButtonSize;
  437. } else if (dy < 0) {
  438. dy = 0;
  439. }
  440. setState(() {});
  441. }
  442. Widget zoomAudioButton() {
  443. if (friendInfo == null || !isShowZoomButton || isTranslateButlerFinish) {
  444. return Container();
  445. }
  446. Widget button = Container(
  447. key: mykey,
  448. width: 57,
  449. height: 74,
  450. child: Card(child: Align(child: Icon(
  451. IconData(0xe67d, fontFamily: Constants.IconFontFamily),
  452. color: Color(0xFF008AFF),
  453. size: 35.0,
  454. ),alignment: Alignment.center,),),
  455. );
  456. return GestureDetector(
  457. onHorizontalDragUpdate: dragEvent,
  458. onVerticalDragUpdate: dragEvent,
  459. onTap: (){
  460. setState(() {
  461. isShowAudio = !isShowAudio;
  462. isShowZoomButton = !isShowZoomButton;
  463. });
  464. },
  465. child: Container(
  466. child: Transform.translate(
  467. offset: Offset(dx, dy),
  468. child: Align(
  469. alignment: Alignment.topLeft,
  470. child: button,
  471. ),
  472. ),
  473. ),
  474. );
  475. }
  476. Widget _buildTranslationButler() {
  477. bool hasHeadImg = true;
  478. if (friendInfo.headimgurl == null || friendInfo.headimgurl.length == 0) {
  479. hasHeadImg = false;
  480. }
  481. /// 5H币/1分钟
  482. String coinTIme = I18n.of(context).translation_butler_coin_time;
  483. coinTIme = coinTIme.replaceAll('/s1', '5');
  484. coinTIme = coinTIme.replaceAll('/s2', '1');
  485. return Container(
  486. padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
  487. color: Colors.white,
  488. child: Row(
  489. children: <Widget>[
  490. ClipRRect(
  491. borderRadius: BorderRadius.circular(8),
  492. child: hasHeadImg
  493. ? CachedNetworkImage(
  494. imageUrl: friendInfo.headimgurl,
  495. placeholder: (context, url) => Image.asset(
  496. Constants.DefaultHeadImgUrl,
  497. width: 54,
  498. height: 54,
  499. ),
  500. width: 54,
  501. height: 54,
  502. )
  503. : SizedBox(
  504. width: 54,
  505. height: 54,
  506. child: Image.asset(R.assetsImagesDefaultNorAvatar))),
  507. Padding(
  508. padding: EdgeInsets.only(left: 10),
  509. child: Container(
  510. child: Column(
  511. crossAxisAlignment: CrossAxisAlignment.start,
  512. children: <Widget>[
  513. Text(
  514. I18n.of(context).translation_butler,
  515. textScaleFactor: 1.0,
  516. overflow: TextOverflow.ellipsis,
  517. maxLines: 1,
  518. style: TextStyle(
  519. color: AppColors.NewAppbarTextColor, fontSize: 15),
  520. ),
  521. SizedBox(
  522. height: 5,
  523. ),
  524. Text(
  525. coinTIme,
  526. textScaleFactor: 1.0,
  527. maxLines: 1,
  528. style: TextStyle(color: Color(0xFF797979), fontSize: 13),
  529. )
  530. ],
  531. ),
  532. constraints: BoxConstraints(maxWidth: 135),
  533. ),
  534. ),
  535. TranslateHKMgr().isUser?Expanded(
  536. child: Container(
  537. constraints: BoxConstraints(maxWidth: 130),
  538. width: double.maxFinite,
  539. child: CountDownButton(
  540. I18n.of(context).translation_butler_end_service,
  541. () {
  542. // Navigator.of(context).pop();
  543. },
  544. countDownTime: 60*5,
  545. align: Alignment.centerRight,
  546. onPress: () {
  547. MsgHandler.sendEndTransHKOrderReq();
  548. // Navigator.of(context).pop();
  549. },
  550. ),
  551. // alignment: Alignment(1,0),
  552. )):Container(),
  553. ],
  554. ),
  555. );
  556. }
  557. Widget _buildMessageList() {
  558. return Container(
  559. alignment: Alignment.topCenter,
  560. child: msgList.length == 0
  561. ? Padding(
  562. padding: EdgeInsets.all(8),
  563. child: Text(
  564. I18n.of(context).chat_tips,
  565. textAlign: TextAlign.center,
  566. textScaleFactor: 1.0,
  567. style: TextStyle(color: Colors.grey, fontSize: 12),
  568. ))
  569. : NotificationListener(
  570. child: Scrollbar(
  571. child: ListView.builder(
  572. reverse: true,
  573. shrinkWrap: true,
  574. itemCount: msgList.length,
  575. controller: _scrollCtrl,
  576. padding: EdgeInsets.all(8.0),
  577. itemBuilder: _buildItem,
  578. )),
  579. onNotification: (notification) {
  580. if (notification is ScrollNotification) {
  581. // var offset = notification.metrics.pixels;
  582. // print('滚动事件 offset $offset');
  583. }
  584. return true;
  585. },
  586. ),
  587. );
  588. }
  589. hideKeyBoard() {
  590. _keyboardIndexProvider.changeSelectIndex(-1);
  591. }
  592. readOnly() {
  593. _keyboardIndexProvider.changeReadOnlyKey(true);
  594. }
  595. sendMsg(MsgModel msg) {
  596. print('对方是否拉黑你 ${friendInfo.isBlackened}');
  597. if (BlacklistMgr.isBlack(friendInfo.userId)) {
  598. return;
  599. }
  600. print('chat page session id:${msg.sessionId}');
  601. if (!friendInfo.isCanStrangerNews &&
  602. !FriendListMgr().isMyFriend(friendInfo.userId)) {
  603. showToast(I18n.of(context).stranger_close_tips);
  604. return;
  605. }
  606. MsgHandler.insertMsgToDB(msg);
  607. MsgHandler.sendChatMsg(msg);
  608. if (mounted) {
  609. setState(() {});
  610. if (_scrollCtrl.hasClients) {
  611. _scrollCtrl.animateTo(0,
  612. duration: new Duration(milliseconds: 500), curve: Curves.ease);
  613. }
  614. }
  615. // testBig(msg);
  616. }
  617. MsgModel msg;
  618. int count = 0;
  619. testBig(MsgModel msg) async {
  620. for (int k = 0; k < 100; k++) {
  621. msg.msgContent = utf8.encode('测试$count');
  622. Int64 time = Int64((DateTime.now()).millisecondsSinceEpoch);
  623. msg.time = time.toInt();
  624. MsgHandler.insertMsgToDB(msg);
  625. MsgHandler.sendChatMsg(msg);
  626. await Future.delayed(Duration(milliseconds: 500), () {});
  627. count++;
  628. }
  629. count = 0;
  630. print('发送完毕');
  631. showToast('发送完毕');
  632. }
  633. void receiveMsg(args) {
  634. if (mounted) {
  635. setState(() {});
  636. if (_scrollCtrl.hasClients) {
  637. _scrollCtrl.animateTo(0,
  638. duration: new Duration(milliseconds: 500), curve: Curves.ease);
  639. }
  640. }
  641. }
  642. _deleteItem(msg) {
  643. MessageMgr().emit('Cancel Request', msg);
  644. print('#### 开始删除--');
  645. msgList.remove(msg);
  646. setState(() {});
  647. SqlUtil().deleteSigleRecordWith(msg.sessionId, msg.time);
  648. }
  649. Widget _buildItem(BuildContext context, int index) {
  650. var lastMsgTime;
  651. if (index < msgList.length - 1) {
  652. lastMsgTime = msgList[index + 1].time;
  653. }
  654. MsgModel msg = msgList[index];
  655. return ChatPageItem(
  656. key: Key(msg.time.toString()),
  657. msg: msg,
  658. hideKeyboard: readOnly,
  659. friendInfo: friendInfo,
  660. lastMsgTime: lastMsgTime);
  661. }
  662. }