Hibok
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

1541 Zeilen
50 KiB

  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'dart:math';
  4. import 'dart:typed_data';
  5. import 'package:cached_network_image/cached_network_image.dart';
  6. import 'package:chat/chat/download_item.dart';
  7. import 'package:chat/chat/file_msg_item.dart';
  8. import 'package:chat/chat/msg_state_widge.dart';
  9. import 'package:chat/chat/place_item.dart';
  10. import 'package:chat/chat/redbag_widget.dart';
  11. import 'package:chat/chat/upload_item.dart';
  12. import 'package:chat/chat/video_view.dart';
  13. import 'package:chat/data/UserData.dart';
  14. import 'package:chat/data/constants.dart';
  15. import 'package:chat/utils/friend_list_mgr.dart';
  16. import 'package:chat/utils/wpop/w_popup_menu.dart';
  17. import 'package:flutter/services.dart';
  18. import 'package:chat/generated/i18n.dart';
  19. import 'package:chat/home/invite_detail_page.dart';
  20. import 'package:chat/models/ChatMsg.dart';
  21. import 'package:chat/models/group_info_model.dart';
  22. import 'package:chat/models/keyboard_provider.dart';
  23. import 'package:chat/proto/chat.pbenum.dart';
  24. import 'package:chat/proto/chat.pbserver.dart';
  25. import 'package:chat/utils/CustomUI.dart';
  26. import 'package:chat/utils/MessageMgr.dart';
  27. import 'package:chat/utils/app_navigator.dart';
  28. import 'package:chat/utils/date_utils.dart';
  29. import 'package:chat/utils/group_member_model.dart';
  30. import 'package:chat/utils/msgHandler.dart';
  31. import 'package:chat/utils/screen.dart';
  32. import 'package:chat/utils/sound_util.dart';
  33. import 'package:chat/utils/upload_util.dart';
  34. import 'package:chat/utils/video_anim.dart';
  35. import 'package:dio/dio.dart';
  36. import 'package:flutter/cupertino.dart';
  37. import 'package:flutter/material.dart';
  38. import 'package:provider/provider.dart';
  39. import 'package:oktoast/oktoast.dart';
  40. import 'package:chat/utils/HttpUtil.dart';
  41. import 'package:share_extend/share_extend.dart';
  42. import 'package:url_launcher/url_launcher.dart';
  43. import '../r.dart';
  44. import '../utils/file_cache_mgr.dart';
  45. import 'full_img_view.dart';
  46. import 'upload_item.dart';
  47. import 'package:chat/models/ref_name_provider.dart';
  48. import 'package:chat/models/money_change.dart';
  49. import 'package:chat/models/voucher_change.dart';
  50. const double ChatRadius = 7.5;
  51. const Color SendMsgBg = Color(0xFFD4F0FF);
  52. const Color SendMsgText = Colors.black;
  53. const double TextHeight = 1.2;
  54. const double PaddingLeft = 9.5;
  55. const Color ReciveBorderColor = Color(0xFFDCDCDC);
  56. const double FontSize = 15;
  57. class GroupChatPageItem extends StatefulWidget {
  58. final MsgModel msg;
  59. final int lastMsgTime;
  60. final GroupMemberModel memberModel;
  61. final Function hideKeyboard;
  62. const GroupChatPageItem(
  63. {Key key,
  64. this.msg,
  65. this.lastMsgTime,
  66. this.hideKeyboard,
  67. this.memberModel})
  68. : assert(msg != null),
  69. super(key: key);
  70. @override
  71. _GroupChatPageItemState createState() => _GroupChatPageItemState();
  72. }
  73. class _GroupChatPageItemState extends State<GroupChatPageItem>
  74. with SingleTickerProviderStateMixin {
  75. int curTextType = 0; //文字、译文切换索引
  76. List<String> textList = [];
  77. String curSoundUrl;
  78. CancelToken _cancelToken = CancelToken();
  79. bool isLongPressed = false;
  80. double maxWidth = Screen.width - 140;
  81. @override
  82. void initState() {
  83. super.initState();
  84. if (widget.msg.from == UserData().basicInfo.userId &&
  85. widget.msg.state == MsgState.None) {
  86. print('重新发送消息');
  87. MsgHandler.sendChatMsg(widget.msg);
  88. }
  89. textList = widget.msg.getTransTextList();
  90. MessageMgr().on('Update Translate Message', updateTranslateMsg);
  91. MessageMgr().on('Cancel Request', _deleteItem);
  92. }
  93. @override
  94. void dispose() {
  95. MessageMgr().off('Cancel Request', _deleteItem);
  96. MessageMgr().off('Update Translate Message', updateTranslateMsg);
  97. super.dispose();
  98. }
  99. _deleteItem(msg) {
  100. if (msg == widget.msg) {
  101. print(widget.msg.state);
  102. if (widget.msg.state == MsgState.Uploading) {
  103. print('取消上传');
  104. UploadUtil().cancelRequests(_cancelToken);
  105. }
  106. }
  107. }
  108. updateTextList() {
  109. textList.clear();
  110. curTextType = 0;
  111. textList = widget.msg.getTransTextList();
  112. }
  113. updateTranslateMsg(msg) {
  114. if (msg.time == widget.msg.time) {
  115. if (mounted) {
  116. updateTextList();
  117. if (!mounted) {
  118. return;
  119. }
  120. setState(() {
  121. print('更新翻译文字');
  122. });
  123. }
  124. }
  125. }
  126. getTodayTime(msgDate, {var sendTime}) {
  127. String showTimeStr;
  128. sendTime = sendTime ?? widget.msg.time;
  129. var today = DateTime.now();
  130. //今天
  131. if (msgDate.year == today.year &&
  132. msgDate.month == today.month &&
  133. msgDate.day == today.day) {
  134. showTimeStr =
  135. DateUtils().getFormartData(timeSamp: sendTime, format: 'HH:mm');
  136. } else {
  137. showTimeStr = DateUtils()
  138. .getFormartData(timeSamp: sendTime, format: 'yyyy/MM/dd HH:mm');
  139. }
  140. return showTimeStr;
  141. }
  142. String getMsgTime() {
  143. String showTimeStr;
  144. var sendTime = widget.msg.time;
  145. var msgDate = DateTime.fromMillisecondsSinceEpoch(sendTime);
  146. if (widget.lastMsgTime == null) {
  147. showTimeStr = getTodayTime(msgDate);
  148. } else {
  149. if (sendTime - widget.lastMsgTime > 3 * 60 * 1000) {
  150. showTimeStr = getTodayTime(msgDate);
  151. }
  152. }
  153. return showTimeStr;
  154. }
  155. @override
  156. Widget build(BuildContext context) {
  157. var showTime = getMsgTime();
  158. return Container(
  159. width: Screen.width,
  160. color: Colors.white.withOpacity(0),
  161. margin: const EdgeInsets.symmetric(vertical: 10.0),
  162. child: Column(
  163. children: <Widget>[
  164. showTime == null
  165. ? SizedBox()
  166. : Container(
  167. decoration: BoxDecoration(
  168. color: Color(0xFF2C2F36).withOpacity(0.25),
  169. borderRadius: BorderRadius.all(Radius.circular(9.5))),
  170. padding:
  171. EdgeInsets.only(left: 7, right: 7, top: 3, bottom: 2),
  172. child: Text(showTime,
  173. textScaleFactor: 1.0,
  174. style: TextStyle(fontSize: 10.0, color: Colors.white)),
  175. ),
  176. SizedBox(height: 10),
  177. _msgWidget()
  178. ],
  179. ),
  180. );
  181. }
  182. _msgWidget() {
  183. if (widget.msg.from == 0) {
  184. return _serverNotifyMsg();
  185. } else {
  186. if (widget.msg.from == UserData().basicInfo.userId) {
  187. return _getSentMessageLayout(context);
  188. } else {
  189. return _getReceivedMessageLayout(context);
  190. }
  191. }
  192. }
  193. _serverNotifyMsg() {
  194. var type = widget.msg.msgType;
  195. if (type == ChatType.GroupChatNoticeType.value) {
  196. var res = GroupChatNotice.fromBuffer(widget.msg.msgContent);
  197. var groupInfoModel = Provider.of<GroupInfoModel>(context);
  198. var optId = res.operatuId;
  199. var showStr = MsgHandler.getGroupNoticeMsg(res, groupInfoModel);
  200. return Container(
  201. alignment: Alignment.center,
  202. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  203. child: extendedText(showStr,
  204. color: Constants.GreyTextColor,
  205. fontSize: 12, onSpecialTextTap: (dynamic parameter) {
  206. // print('点击事件 $parameter');
  207. if (parameter.startsWith("\$")) {
  208. // showToast('点击: ${res.operateduId[0]}');
  209. List<GroupMemberModel> list = [];
  210. for (int k = 0; k < res.operateduId.length; k++) {
  211. list.add(GroupMemberModel.fromBaseInfo(res.operateduId[k]));
  212. }
  213. AppNavigator.push(
  214. context,
  215. InviteDetailPage(
  216. originalList: list,
  217. opt: GroupMemberModel.fromBaseInfo(optId),
  218. groupId: groupInfoModel.sessionId,
  219. ));
  220. }
  221. }),
  222. );
  223. }
  224. return Container();
  225. }
  226. _textGif(List<int> msgContent) {
  227. var msg = utf8.decode(msgContent);
  228. return Container(
  229. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  230. child: extendedText(
  231. msg,
  232. selectionEnabled: false,
  233. hideKeyboard: widget.hideKeyboard,
  234. fontSize: FontSize,
  235. color: Colors.black,
  236. ),
  237. );
  238. }
  239. _textMsg(MsgModel msgModel) {
  240. List<Widget> showMsg = [];
  241. var width = 0.0;
  242. var msg = utf8.decode(msgModel.msgContent);
  243. if (msg.contains('[ ')) {
  244. msg = msg.replaceAll('[ ', '[');
  245. }
  246. if (msg.contains(' ]')) {
  247. msg = msg.replaceAll(' ]', ']');
  248. }
  249. bool isUrl = false;
  250. if (textList[curTextType].contains('http')) {
  251. isUrl = true;
  252. }
  253. if (msgModel.refMsgContent != null && msgModel.refMsgContent.length > 0) {
  254. QuoteMsg quoteMsg = QuoteMsg.fromBuffer(msgModel.refMsgContent);
  255. var refName = Provider.of<RefNameProvider>(context)
  256. .getGroupRefName(msgModel.sessionId, quoteMsg.sendUserId);
  257. var msgDate =
  258. DateTime.fromMillisecondsSinceEpoch(quoteMsg.sendTime.toInt());
  259. var showTimeStr =
  260. getTodayTime(msgDate, sendTime: quoteMsg.sendTime.toInt());
  261. var timetWidth = CustomUI.getRealTextWidht(showTimeStr, 13);
  262. var nameWidhth = CustomUI.getRealTextWidht(refName, 13);
  263. width = timetWidth + (nameWidhth > 70 ? 70 : nameWidhth) + 36;
  264. var tempIndex = quoteMsg.content.indexOf(':');
  265. var quoteContent = tempIndex == -1
  266. ? quoteMsg.content
  267. : quoteMsg.content.substring(quoteMsg.content.indexOf(':') + 2);
  268. var contentWidht = _getTextWidth(quoteContent, fontSize: 13);
  269. width = min(max(width, contentWidht), maxWidth);
  270. showMsg.add(InkWell(
  271. onTap: () {
  272. MessageMgr().emit('Jump to Msg', quoteMsg.sendTime.toInt());
  273. },
  274. child: Container(
  275. padding: EdgeInsets.all(8),
  276. margin: EdgeInsets.only(bottom: 5),
  277. decoration: BoxDecoration(
  278. color: Color(0xFFC4E0F5),
  279. borderRadius: BorderRadius.circular(5.5)),
  280. child: Column(
  281. crossAxisAlignment: CrossAxisAlignment.start,
  282. children: <Widget>[
  283. Row(children: <Widget>[
  284. Container(
  285. constraints: BoxConstraints(maxWidth: 70),
  286. child: Text(
  287. refName,
  288. style: TextStyle(fontSize: 13),
  289. overflow: TextOverflow.ellipsis,
  290. )),
  291. SizedBox(width: 20),
  292. Text(
  293. showTimeStr,
  294. style: TextStyle(fontSize: 13),
  295. )
  296. ]),
  297. SizedBox(height: 7.5),
  298. Text(
  299. quoteContent,
  300. style: TextStyle(fontSize: 13),
  301. )
  302. ],
  303. ),
  304. )));
  305. }
  306. var contentWidth = _getTextWidth(textList[curTextType]);
  307. if (contentWidth > width && contentWidth <= maxWidth) {
  308. width = contentWidth;
  309. }
  310. showMsg.add(Container(
  311. constraints: BoxConstraints(maxWidth: maxWidth, minHeight: 22),
  312. alignment: Alignment.centerLeft,
  313. child: extendedText(
  314. textList[curTextType],
  315. color: isUrl ? Colors.blue : Constants.BlackTextColor,
  316. hideKeyboard: widget.hideKeyboard,
  317. fontSize: FontSize,
  318. )));
  319. return Container(
  320. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  321. width: width + 20,
  322. child: Column(
  323. crossAxisAlignment: CrossAxisAlignment.start, children: showMsg),
  324. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  325. decoration: BoxDecoration(
  326. color: isLongPressed ? Colors.grey[300] : SendMsgBg,
  327. border: Border.all(color: Color(0xFFB9CBD7), width: 0.6),
  328. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  329. );
  330. }
  331. _soundMsg() {
  332. double time = widget.msg.extraInfo / 1000;
  333. if (time > 60) {
  334. time = 60.0;
  335. }
  336. bool isPlaying = false;
  337. var soundPath = widget.msg.localFile;
  338. isPlaying = SoundUtils().isPlaying(soundPath);
  339. var soundWidget = GestureDetector(
  340. child: Container(
  341. width: 120,
  342. child: Row(
  343. mainAxisAlignment: MainAxisAlignment.end,
  344. children: <Widget>[
  345. Container(
  346. alignment: Alignment.center,
  347. padding: EdgeInsets.only(bottom: 2),
  348. margin: EdgeInsets.only(right: 10),
  349. width: 25.5,
  350. height: 25.5,
  351. decoration: BoxDecoration(
  352. border:
  353. Border.all(color: const Color(0xFF1B92C7), width: 0.5),
  354. color: const Color(0xFF04A4FE),
  355. shape: BoxShape.circle),
  356. child: Icon(
  357. IconData(isPlaying ? 0xe652 : 0xe653,
  358. fontFamily: Constants.IconFontFamily),
  359. size: isPlaying ? 15 : 18,
  360. color: Colors.white,
  361. ),
  362. ),
  363. isPlaying
  364. ? Stack(
  365. children: <Widget>[
  366. Container(
  367. height: 18,
  368. width: 19,
  369. ),
  370. Positioned(
  371. bottom: 0,
  372. child: VideoAnim(
  373. begin: 18,
  374. start: 0.444,
  375. end: 4.5,
  376. )),
  377. Positioned(
  378. left: 7,
  379. bottom: 0,
  380. child: VideoAnim(
  381. begin: 4.5,
  382. end: 18,
  383. )),
  384. Positioned(
  385. left: 14,
  386. bottom: 0,
  387. child: VideoAnim(
  388. begin: 18,
  389. end: 4.5,
  390. ))
  391. ],
  392. )
  393. : Stack(
  394. children: <Widget>[
  395. Container(
  396. height: 18,
  397. width: 19,
  398. ),
  399. Positioned(
  400. bottom: 0, child: CustomUI.buildAudioContaniner(12)),
  401. Positioned(
  402. left: 7,
  403. bottom: 0,
  404. child: CustomUI.buildAudioContaniner(4.5)),
  405. Positioned(
  406. left: 14,
  407. bottom: 0,
  408. child: CustomUI.buildAudioContaniner(18))
  409. ],
  410. ),
  411. Expanded(child: SizedBox()),
  412. fixedText(time.toStringAsFixed(0), color: SendMsgText, fontSize: 16)
  413. ],
  414. ),
  415. padding: EdgeInsets.symmetric(horizontal: 15, vertical: 12),
  416. decoration: BoxDecoration(
  417. color: SendMsgBg,
  418. border: Border.all(color: Color(0xFFB9CBD7), width: 0.6),
  419. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  420. ),
  421. onTap: () async {
  422. print('播放状态 : $isPlaying');
  423. print('当前文件$soundPath ');
  424. if (isPlaying) {
  425. SoundUtils().pause();
  426. } else {
  427. SoundUtils().play(soundPath, onPlayed: () {
  428. if (mounted) {
  429. setState(() {});
  430. }
  431. }, complete: () {
  432. if (mounted) {
  433. setState(() {});
  434. }
  435. });
  436. }
  437. },
  438. );
  439. return UploadImgItem(
  440. msg: widget.msg,
  441. child: soundWidget,
  442. isShowProgress: false,
  443. );
  444. }
  445. Size _getImgSize() {
  446. double aspectRatio = widget.msg.extraInfo / 100;
  447. var maxWidth = Screen.width * 0.65;
  448. var maxHeight = Screen.height / 4;
  449. var width, height;
  450. if (maxWidth / maxHeight > aspectRatio) {
  451. height = maxHeight;
  452. width = maxHeight * aspectRatio;
  453. } else {
  454. width = maxWidth;
  455. height = maxWidth / aspectRatio;
  456. }
  457. return Size(width, height);
  458. }
  459. _imgMsg(List<int> imgData) {
  460. var imgSize = _getImgSize();
  461. ImageProvider provider = MemoryImage(widget.msg.localFile == null
  462. ? Uint8List.fromList(imgData)
  463. : File(widget.msg.localFile).readAsBytesSync());
  464. return GestureDetector(
  465. child: ClipRRect(
  466. child: UploadImgItem(
  467. msg: widget.msg,
  468. cancelToken: _cancelToken,
  469. child: Container(
  470. height: imgSize.height,
  471. width: imgSize.width,
  472. child: Image(
  473. fit: BoxFit.contain,
  474. image: provider,
  475. ),
  476. )),
  477. borderRadius: BorderRadius.circular(5),
  478. ),
  479. onTap: () async {
  480. showFullImg(context, widget.msg);
  481. });
  482. }
  483. _videoMsg(BuildContext context, List<int> thumbnail) {
  484. var imgSize = _getImgSize();
  485. return InkWell(
  486. child: ClipRRect(
  487. child: Stack(
  488. alignment: Alignment.center,
  489. children: <Widget>[
  490. UploadImgItem(
  491. msg: widget.msg,
  492. cancelToken: _cancelToken,
  493. child: Container(
  494. width: imgSize.width,
  495. height: imgSize.height,
  496. child: Image(
  497. fit: BoxFit.contain,
  498. image: MemoryImage(Uint8List.fromList(thumbnail)),
  499. ),
  500. ))
  501. ],
  502. ),
  503. borderRadius: BorderRadius.circular(5),
  504. ),
  505. onTap: () {
  506. showVideoPage(context, widget.msg.localFile);
  507. },
  508. );
  509. }
  510. _msgLayout(BuildContext context, MsgModel msg) {
  511. Widget item;
  512. switch (ChatType.valueOf(msg.msgType)) {
  513. case ChatType.TextChatType:
  514. item = _textMsg(msg);
  515. break;
  516. case ChatType.EmoticonType:
  517. item = _textGif(msg.msgContent);
  518. break;
  519. case ChatType.ImageChatType:
  520. item = _imgMsg(msg.msgContent);
  521. break;
  522. case ChatType.ShortVideoChatType:
  523. item = _videoMsg(context, msg.msgContent);
  524. break;
  525. case ChatType.ShortVoiceChatType:
  526. item = _soundMsg();
  527. break;
  528. case ChatType.RedWalletChatType:
  529. item = RedBagItem(
  530. Key(msg.time.toString()), UserData().basicInfo.userId, msg);
  531. break;
  532. case ChatType.PlaceChatType:
  533. item = PlaceItem(isMe: true, placeContent: msg.msgContent);
  534. break;
  535. case ChatType.FileChatType:
  536. item = _fileMsgItem();
  537. break;
  538. default:
  539. }
  540. return wrapItemWithMenu(item);
  541. }
  542. Widget _fileMsgItem() {
  543. return UploadImgItem(
  544. msg: widget.msg,
  545. cancelToken: _cancelToken,
  546. child: Container(
  547. height: 100,
  548. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  549. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  550. decoration: BoxDecoration(
  551. color: isLongPressed ? Colors.grey[300] : SendMsgBg,
  552. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  553. child: FileMsgItem(widget.msg)));
  554. }
  555. Widget wrapItemWithMenu(item) {
  556. List<Function> actionsFunc = [];
  557. List<String> actions = [
  558. I18n.of(context).reply,
  559. I18n.of(context).delete,
  560. ];
  561. actionsFunc.add(() {
  562. print('发送引用的消息');
  563. MessageMgr().emit('Reply Select Message', widget.msg);
  564. });
  565. actionsFunc.add(() {
  566. MessageMgr().emit('Delete Select Message', widget.msg);
  567. });
  568. ///转发
  569. if (widget.msg.msgType == ChatType.TextChatType.value ||
  570. widget.msg.msgType == ChatType.ImageChatType.value ||
  571. widget.msg.msgType == ChatType.ShortVideoChatType.value ||
  572. widget.msg.msgType == ChatType.PlaceChatType.value ||
  573. widget.msg.msgType == ChatType.EmoticonType.value ||
  574. widget.msg.msgType == ChatType.FileChatType.value) {
  575. actions.add(I18n.of(context).forward);
  576. actionsFunc.add(() {
  577. print('转发消息');
  578. if (widget.msg.msgType == ChatType.FileChatType.value &&
  579. widget.msg.localFile == null) {
  580. showToast('请先下载文件');
  581. return;
  582. }
  583. AppNavigator.pushForwardPage(context, widget.msg);
  584. });
  585. }
  586. if (widget.msg.msgType == ChatType.FileChatType.value &&
  587. widget.msg.localFile != null) {
  588. //分享文件
  589. actions.add(I18n.of(context).copy_download_url);
  590. actionsFunc.add(() async {
  591. //如果是文件增加复制下载地址
  592. String path = widget.msg.localFile;
  593. UploadUtil().copyFileUrl(widget.msg, context);
  594. String type = 'file';
  595. if (path.contains('mp4') || path.contains('mp3')) {
  596. type = 'video';
  597. } else if (path.contains('png') ||
  598. path.contains('jpg') ||
  599. path.contains('jpeg') ||
  600. path.contains('JPG') ||
  601. path.contains('PNG')) {
  602. type = 'image';
  603. }
  604. ShareExtend.share(FileCacheMgr.replacePath(path), type);
  605. });
  606. }
  607. if (widget.msg.msgType == ChatType.TextChatType.value) {
  608. actions.insert(0, I18n.of(context).copy);
  609. actionsFunc.insert(0, () {
  610. //复制当前的文字
  611. print('复制文字 ${textList[curTextType]}');
  612. ClipboardData clipboardData =
  613. ClipboardData(text: textList[curTextType]);
  614. Clipboard.setData(clipboardData);
  615. });
  616. }
  617. if (widget.msg.msgType == ChatType.ShortVoiceChatType.value) {
  618. var soundPlayMode =
  619. Provider.of<KeyboardIndexProvider>(context).soundPlayMode;
  620. actions.add(soundPlayMode
  621. ? I18n.of(context).handset_playback
  622. : I18n.of(context).speaker_play);
  623. actionsFunc.add(() {
  624. Provider.of<KeyboardIndexProvider>(context)
  625. .changeSoundPlayMode(!soundPlayMode);
  626. SoundUtils.instance.savePlayModeConfig(soundPlayMode);
  627. });
  628. }
  629. // String date2 = DateTime.fromMillisecondsSinceEpoch(widget.msg.time).toString();
  630. bool isUrl = false;
  631. if (widget.msg.msgType == ChatType.TextChatType.value) {
  632. if (textList[curTextType].contains('http')) {
  633. isUrl = true;
  634. }
  635. }
  636. return WPopupMenu(
  637. child: item,
  638. actions: actions,
  639. onTap: () async {
  640. if (isUrl) {
  641. if (await canLaunch(textList[curTextType])) {
  642. launch(textList[curTextType]);
  643. }
  644. }
  645. },
  646. onLongPressStart: () {
  647. isLongPressed = true;
  648. setState(() {});
  649. },
  650. onLongPressEnd: () {
  651. isLongPressed = false;
  652. setState(() {});
  653. },
  654. onValueChanged: (int value) {
  655. print('选择的是$value个菜单');
  656. if (value >= 0 && value < actionsFunc.length) {
  657. actionsFunc[value]();
  658. }
  659. },
  660. );
  661. }
  662. Widget _getSentMessageLayout(BuildContext context) {
  663. bool hasHeadImg = true;
  664. if (UserData().basicInfo.headimgurl == null ||
  665. UserData().basicInfo.headimgurl.length == 0) {
  666. hasHeadImg = false;
  667. }
  668. return Row(
  669. crossAxisAlignment: CrossAxisAlignment.start,
  670. mainAxisAlignment: MainAxisAlignment.end,
  671. children: <Widget>[
  672. MsgStateWidget(widget.msg),
  673. SizedBox(width: 3),
  674. _msgLayout(context, widget.msg),
  675. SizedBox(width: 10),
  676. Column(
  677. crossAxisAlignment: CrossAxisAlignment.end,
  678. children: <Widget>[
  679. ClipRRect(
  680. borderRadius: BorderRadius.circular(8),
  681. child: hasHeadImg
  682. ? CachedNetworkImage(
  683. imageUrl: UserData().basicInfo.headimgurl,
  684. placeholder: (context, url) => Image.asset(
  685. Constants.DefaultHeadImgUrl,
  686. width: 40,
  687. height: 40,
  688. ),
  689. width: 40,
  690. height: 40,
  691. )
  692. : SizedBox(
  693. width: 40,
  694. height: 40,
  695. child: Image.asset(R.assetsImagesDefaultNorAvatar))),
  696. ],
  697. )
  698. ]);
  699. }
  700. _receiveJIF(MsgModel msg) {
  701. var text = utf8.decode(msg.msgContent);
  702. return Container(
  703. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  704. child: extendedText(text,
  705. selectionEnabled: false, hideKeyboard: widget.hideKeyboard));
  706. }
  707. double _getTextWidth(String text, {double fontSize = FontSize}) {
  708. var tp = TextPainter(
  709. text: TextSpan(
  710. style: TextStyle(fontSize: fontSize), text: textList[curTextType]),
  711. textAlign: TextAlign.left,
  712. textDirection: TextDirection.ltr,
  713. textScaleFactor: 1,
  714. );
  715. tp.layout(maxWidth: maxWidth);
  716. if (text.contains('[')) {
  717. if (text.length < 5 && text.startsWith('[') && text.endsWith(']')) {
  718. ///单表情
  719. return 25;
  720. }
  721. int length = text.split('[').length;
  722. if (length > 1) {
  723. ///包含表情
  724. int scale = 6;
  725. if (length > 6) scale = 7;
  726. double width = tp.width + length * scale;
  727. return width > (maxWidth) ? maxWidth : width;
  728. }
  729. }
  730. return tp.width;
  731. ///单文字
  732. }
  733. blueDot(bool isShow) {
  734. return Container(
  735. margin: EdgeInsets.only(right: 5),
  736. decoration: BoxDecoration(
  737. shape: BoxShape.circle,
  738. color: isShow ? Color(0xFFFF7E00) : Color(0xFFCFCFCF),
  739. ),
  740. width: 6,
  741. height: 6,
  742. );
  743. }
  744. _receiveText(MsgModel msg) {
  745. List<Widget> showMsg = [];
  746. double width = 0.0;
  747. if (textList.length > 0) {
  748. bool isUrl = false;
  749. if (textList[curTextType].contains('http')) {
  750. isUrl = true;
  751. }
  752. if (msg.refMsgContent != null && msg.refMsgContent.length > 0) {
  753. QuoteMsg quoteMsg = QuoteMsg.fromBuffer(msg.refMsgContent);
  754. var refName = Provider.of<RefNameProvider>(context)
  755. .getGroupRefName(msg.sessionId, quoteMsg.sendUserId);
  756. var msgDate =
  757. DateTime.fromMillisecondsSinceEpoch(quoteMsg.sendTime.toInt());
  758. var showTimeStr =
  759. getTodayTime(msgDate, sendTime: quoteMsg.sendTime.toInt());
  760. var timetWidth = CustomUI.getRealTextWidht(showTimeStr, 13);
  761. var nameWidhth = CustomUI.getRealTextWidht(refName, 13);
  762. width = timetWidth + (nameWidhth > 70 ? 70 : nameWidhth) + 36;
  763. var quoteContent =
  764. quoteMsg.content.substring(quoteMsg.content.indexOf(':') + 2);
  765. var contentWidht = _getTextWidth(quoteContent, fontSize: 13);
  766. width = min(max(width, contentWidht), maxWidth);
  767. showMsg.add(InkWell(
  768. onTap: () {
  769. MessageMgr().emit('Jump to Msg', quoteMsg.sendTime.toInt());
  770. },
  771. child: Container(
  772. padding: EdgeInsets.all(8),
  773. margin: EdgeInsets.only(bottom: 5),
  774. decoration: BoxDecoration(
  775. color: Color(0xFFC4E0F5),
  776. borderRadius: BorderRadius.circular(5.5)),
  777. child: Column(
  778. crossAxisAlignment: CrossAxisAlignment.start,
  779. children: <Widget>[
  780. Row(children: <Widget>[
  781. Container(
  782. constraints: BoxConstraints(maxWidth: 70),
  783. child: Text(
  784. refName,
  785. style: TextStyle(fontSize: 13),
  786. overflow: TextOverflow.ellipsis,
  787. )),
  788. SizedBox(width: 20),
  789. Text(
  790. showTimeStr,
  791. style: TextStyle(fontSize: 13),
  792. )
  793. ]),
  794. SizedBox(height: 7.5),
  795. Text(
  796. quoteContent,
  797. style: TextStyle(fontSize: 13),
  798. )
  799. ],
  800. ),
  801. )));
  802. }
  803. var contentWidth = _getTextWidth(textList[curTextType]);
  804. if (contentWidth > width && contentWidth <= maxWidth) {
  805. width = contentWidth;
  806. }
  807. showMsg.add(InkWell(
  808. onTap: () {
  809. if (msg.transTag == 1) {
  810. return;
  811. }
  812. if (msg.transTag == 2 || msg.transTag == 3) {
  813. setState(() {
  814. curTextType += 1;
  815. curTextType %= textList.length;
  816. });
  817. return;
  818. }
  819. },
  820. child: Container(
  821. constraints: BoxConstraints(maxWidth: maxWidth, minHeight: 22),
  822. alignment: Alignment.centerLeft,
  823. child: extendedText(
  824. textList[curTextType],
  825. color: isUrl ? Colors.blue : Constants.BlackTextColor,
  826. hideKeyboard: widget.hideKeyboard,
  827. fontSize: FontSize,
  828. ))));
  829. }
  830. var minWidth = width + 20;
  831. if (msg.transTag != 0) {
  832. minWidth = 200;
  833. showMsg.add(Padding(
  834. padding: EdgeInsets.symmetric(vertical: 5),
  835. child: Divider(color: Color(0xFFECECEC), height: 1)));
  836. Widget tranWidget = _transProcessWidget(msg.transTag);
  837. showMsg.add(tranWidget);
  838. }
  839. Widget text = Stack(children: <Widget>[
  840. Container(
  841. width: width + 20,
  842. constraints:
  843. BoxConstraints(maxWidth: Screen.width - 120, minWidth: minWidth),
  844. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  845. child: Column(
  846. crossAxisAlignment: CrossAxisAlignment.start, children: showMsg),
  847. decoration: BoxDecoration(
  848. border: Border.all(color: ReciveBorderColor, width: 0.5),
  849. color: isLongPressed ? Colors.grey[300] : Colors.white,
  850. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  851. ),
  852. msg.transTag != 1 && msg.transTag != 0
  853. ? Positioned(
  854. right: 5,
  855. top: 5,
  856. child: Container(
  857. child: Row(children: <Widget>[
  858. blueDot(curTextType == 0),
  859. blueDot(curTextType == 1),
  860. blueDot(curTextType == 2),
  861. //blueDot(true),
  862. ])))
  863. : Container()
  864. ]);
  865. return text;
  866. }
  867. //用户评价人工翻译,差评
  868. rateTranslateResult() async {
  869. return await HttpUtil().rateTranslateResult(widget.msg, () {
  870. if (mounted) {
  871. setState(() {});
  872. }
  873. });
  874. }
  875. _translateItemWidget(int code, String title, Function onTap) {
  876. Color color = onTap == null ? Constants.GreyTextColor : Color(0xFF087FF3);
  877. return InkWell(
  878. onTap: onTap,
  879. splashColor: Colors.red,
  880. child: Container(
  881. width: 80,
  882. child: Row(
  883. children: <Widget>[
  884. Icon(
  885. IconData(code, fontFamily: Constants.IconFontFamily),
  886. color: color,
  887. size: 20,
  888. ),
  889. SizedBox(width: 5),
  890. Expanded(
  891. child: SizedBox(
  892. child: Text(
  893. title,
  894. style: TextStyle(color: color, fontSize: 10),
  895. textScaleFactor: 1.0,
  896. maxLines: 1,
  897. overflow: TextOverflow.ellipsis,
  898. ),
  899. ))
  900. ],
  901. )));
  902. }
  903. bool isTranslating = false;
  904. _transProcessWidget(int transTag) {
  905. double width = 160;
  906. Widget userTranslateWidget;
  907. Widget machineTranslateWidget;
  908. if (transTag == 1) {
  909. //机器翻译中
  910. userTranslateWidget =
  911. _translateItemWidget(0xe670, I18n.of(context).man_retranslate, null);
  912. machineTranslateWidget =
  913. _translateItemWidget(0xe671, I18n.of(context).robotTranslate, null);
  914. } else if (transTag == 2) {
  915. //人工翻译中
  916. userTranslateWidget =
  917. _translateItemWidget(0xe670, I18n.of(context).ManTranslate, null);
  918. machineTranslateWidget = _translateItemWidget(
  919. 0xe671,
  920. I18n.of(context).robot_retranslate,
  921. textList.length <= 1
  922. ? null
  923. : () {
  924. setState(() {
  925. curTextType += 1;
  926. curTextType %= textList.length;
  927. });
  928. });
  929. } else if (transTag == 3) {
  930. //机器翻译完成
  931. userTranslateWidget = _translateItemWidget(
  932. 0xe670,
  933. I18n.of(context).man_retranslate,
  934. isTranslating
  935. ? null
  936. : () async {
  937. isTranslating = true;
  938. int money =
  939. Provider.of<MoneyChangeProvider>(context, listen: false)
  940. .money;
  941. int voucher =
  942. Provider.of<VoucherChangeProvider>(context, listen: false)
  943. .voucher;
  944. int needMoney = widget.msg.getNeedMoney();
  945. if (needMoney > voucher + money) {
  946. showToast('翻译券和H币不足');
  947. return;
  948. }
  949. var res = await HttpUtil().getPersonalTranslate(widget.msg);
  950. if (res) {
  951. print('请求人工翻译成功,进行扣费');
  952. setState(() {
  953. widget.msg.transTag = 2;
  954. //优先扣券
  955. if (voucher > 0) {
  956. int costQuan = min(voucher, needMoney);
  957. Provider.of<VoucherChangeProvider>(context,
  958. listen: false)
  959. .subVoucher(costQuan);
  960. }
  961. //不足的话再扣H币
  962. if (needMoney > voucher) {
  963. Provider.of<MoneyChangeProvider>(context, listen: false)
  964. .subMoney(needMoney - voucher);
  965. }
  966. });
  967. }
  968. });
  969. machineTranslateWidget = _translateItemWidget(
  970. 0xe671,
  971. I18n.of(context).robot_retranslate,
  972. textList.length <= 1
  973. ? null
  974. : () {
  975. setState(() {
  976. curTextType += 1;
  977. curTextType %= textList.length;
  978. });
  979. });
  980. } else if (transTag == 4 || transTag == 10) {
  981. //4人工翻译完成,未评论 10人工翻译完成已评论
  982. userTranslateWidget = InkWell(
  983. onTap: () {
  984. setState(() {
  985. curTextType = 0;
  986. });
  987. },
  988. child: Container(
  989. width: width / 2,
  990. child: Row(
  991. children: <Widget>[
  992. InkWell(
  993. child: Icon(
  994. IconData(0xe641, fontFamily: Constants.IconFontFamily),
  995. size: 18,
  996. color:
  997. transTag == 10 ? Colors.grey : Color(0xFF087FF3)),
  998. onTap: transTag == 10
  999. ? null
  1000. : () {
  1001. CustomUI.showIosDialog(
  1002. context, I18n.of(context).bad_ev, () async {
  1003. bool isSuccess = await rateTranslateResult();
  1004. if (isSuccess) {
  1005. Navigator.of(context).pop(true);
  1006. showToast(I18n.of(context).success);
  1007. } else {
  1008. showToast(I18n.of(context).fail);
  1009. Navigator.of(context).pop();
  1010. }
  1011. }, () {
  1012. Navigator.of(context).pop();
  1013. });
  1014. },
  1015. ),
  1016. SizedBox(width: 5),
  1017. Expanded(
  1018. child: SizedBox(
  1019. child: Text(
  1020. I18n.of(context).over,
  1021. style: TextStyle(color: Color(0xFF087FF3), fontSize: 10),
  1022. textScaleFactor: 1.0,
  1023. maxLines: 1,
  1024. overflow: TextOverflow.ellipsis,
  1025. ),
  1026. ))
  1027. ],
  1028. )));
  1029. machineTranslateWidget =
  1030. _translateItemWidget(0xe675, I18n.of(context).see_original, () {
  1031. setState(() {
  1032. curTextType = textList.length - 1;
  1033. });
  1034. });
  1035. }
  1036. return Container(
  1037. height: 26,
  1038. alignment: Alignment.center,
  1039. child: Row(
  1040. children: <Widget>[
  1041. userTranslateWidget,
  1042. Expanded(
  1043. child: SizedBox(child: VerticalDivider(), height: 20, width: 2)),
  1044. machineTranslateWidget
  1045. ],
  1046. ),
  1047. );
  1048. }
  1049. _receiveImg(BuildContext context, List<int> imgData, {String downloadData}) {
  1050. // ImageProvider provider = MemoryImage(Uint8List.fromList(imgData));
  1051. ImageProvider provider = MemoryImage(widget.msg.localFile == null
  1052. ? Uint8List.fromList(imgData)
  1053. : File(widget.msg.localFile).readAsBytesSync());
  1054. var imgSize = _getImgSize();
  1055. return DownloadItem(
  1056. isAutoDown: false,
  1057. msg: widget.msg,
  1058. onFinishTap: () {
  1059. widget.hideKeyboard();
  1060. showFullImg(context, widget.msg);
  1061. },
  1062. child: Container(
  1063. alignment: Alignment.centerLeft,
  1064. width: imgSize.width,
  1065. height: imgSize.height,
  1066. child: ClipRRect(
  1067. child: Image(
  1068. image: provider ?? AssetImage(R.assetsImagesIcAlbum),
  1069. ),
  1070. borderRadius: BorderRadius.circular(5),
  1071. ),
  1072. ),
  1073. );
  1074. }
  1075. _receiveVideo(BuildContext context, List<int> imgData,
  1076. {String downloadData}) {
  1077. ImageProvider provider = MemoryImage(Uint8List.fromList(imgData));
  1078. var imgSize = _getImgSize();
  1079. return InkWell(
  1080. onTap: () {
  1081. if (widget.msg.localFile != null) {
  1082. showVideoPage(context, widget.msg.localFile);
  1083. }
  1084. },
  1085. child: DownloadItem(
  1086. isAutoDown: false,
  1087. msg: widget.msg,
  1088. child: Container(
  1089. width: imgSize.width,
  1090. height: imgSize.height,
  1091. child: ClipRRect(
  1092. child: Image(
  1093. image: provider ?? AssetImage(R.assetsImagesIcAlbum),
  1094. ),
  1095. borderRadius: BorderRadius.circular(5),
  1096. ),
  1097. ),
  1098. ));
  1099. }
  1100. showVideoPage(BuildContext context, String filePath) {
  1101. widget.hideKeyboard();
  1102. Navigator.push(context,
  1103. MaterialPageRoute<void>(builder: (BuildContext context) {
  1104. return VideoPage(videoPath: filePath);
  1105. }));
  1106. }
  1107. _receiveSound(BuildContext context, List<int> soundData) {
  1108. print(' build 收到的语音消息');
  1109. MsgModel msg = widget.msg;
  1110. var time = widget.msg.extraInfo / 1000;
  1111. if (time > 60) {
  1112. time = 60.0;
  1113. }
  1114. bool isPlaying = false;
  1115. if (curSoundUrl != null) {
  1116. isPlaying = SoundUtils().isPlaying(curSoundUrl);
  1117. }
  1118. var soundWidget = InkWell(
  1119. onTap: () async {
  1120. bool isLocal = true;
  1121. if (msg.localFile != null) {
  1122. isLocal = true;
  1123. curSoundUrl = msg.localFile;
  1124. } else {
  1125. isLocal = false;
  1126. var sessionId = msg.sessionId;
  1127. curSoundUrl = UploadUtil()
  1128. .getFullUrl(msg.extraFile, sessionId, msg.channelType);
  1129. }
  1130. print('当前文件$curSoundUrl 本地?$isLocal');
  1131. if (isPlaying) {
  1132. await SoundUtils().pause();
  1133. } else {
  1134. print('开始播放');
  1135. if (widget.msg.soundListened == 0) {
  1136. widget.msg.updateSoundListened();
  1137. }
  1138. await SoundUtils().play(curSoundUrl, isLocal: isLocal, onPlayed: () {
  1139. if (mounted) {
  1140. this.setState(() {});
  1141. }
  1142. }, complete: () {
  1143. if (mounted) {
  1144. this.setState(() {});
  1145. }
  1146. });
  1147. }
  1148. },
  1149. child: Container(
  1150. width: 130,
  1151. child: Row(children: <Widget>[
  1152. Container(
  1153. alignment: Alignment.center,
  1154. padding: EdgeInsets.only(bottom: 2),
  1155. margin: EdgeInsets.only(right: 10),
  1156. width: 25.5,
  1157. height: 25.5,
  1158. decoration: BoxDecoration(
  1159. border:
  1160. Border.all(color: const Color(0xFF1B92C7), width: 0.5),
  1161. color: const Color(0xFF04A4FE),
  1162. shape: BoxShape.circle),
  1163. child: Icon(
  1164. IconData(isPlaying ? 0xe652 : 0xe653,
  1165. fontFamily: Constants.IconFontFamily),
  1166. size: isPlaying ? 15 : 18,
  1167. color: Colors.white,
  1168. ),
  1169. ),
  1170. isPlaying
  1171. ? Stack(
  1172. children: <Widget>[
  1173. Container(
  1174. height: 18,
  1175. width: 19,
  1176. ),
  1177. Positioned(
  1178. bottom: 0,
  1179. child: VideoAnim(
  1180. begin: 18,
  1181. start: 0.444,
  1182. end: 4.5,
  1183. )),
  1184. Positioned(
  1185. left: 7,
  1186. bottom: 0,
  1187. child: VideoAnim(
  1188. begin: 4.5,
  1189. end: 18,
  1190. )),
  1191. Positioned(
  1192. left: 14,
  1193. bottom: 0,
  1194. child: VideoAnim(
  1195. begin: 18,
  1196. end: 4.5,
  1197. ))
  1198. ],
  1199. )
  1200. : Stack(
  1201. children: <Widget>[
  1202. Container(
  1203. height: 18,
  1204. width: 19,
  1205. ),
  1206. Positioned(
  1207. bottom: 0, child: CustomUI.buildAudioContaniner(12)),
  1208. Positioned(
  1209. left: 7,
  1210. bottom: 0,
  1211. child: CustomUI.buildAudioContaniner(4.5)),
  1212. Positioned(
  1213. left: 14,
  1214. bottom: 0,
  1215. child: CustomUI.buildAudioContaniner(18))
  1216. ],
  1217. ),
  1218. Expanded(child: SizedBox()),
  1219. fixedText(time.toStringAsFixed(0),
  1220. color: Constants.BlackTextColor, fontSize: 16)
  1221. ])),
  1222. );
  1223. List<Widget> showMsg = [];
  1224. showMsg.add(DownloadItem(
  1225. msg: widget.msg,
  1226. child: soundWidget,
  1227. isShowProgress: false,
  1228. ));
  1229. double width = 130;
  1230. double minWidth = 0;
  1231. if (textList.length > 0) {
  1232. width = _getTextWidth(textList[curTextType]) + 20;
  1233. width = min(width, Screen.width - 120);
  1234. minWidth = max(width, 150);
  1235. showMsg.add(Padding(
  1236. padding: EdgeInsets.symmetric(vertical: 5),
  1237. child: Divider(
  1238. height: 1,
  1239. )));
  1240. showMsg.add(Container(
  1241. child: extendedText(
  1242. textList[curTextType],
  1243. color: Constants.BlackTextColor,
  1244. hideKeyboard: widget.hideKeyboard,
  1245. fontSize: FontSize,
  1246. ),
  1247. alignment: Alignment.centerLeft,
  1248. constraints:
  1249. BoxConstraints(maxWidth: Screen.width - 120, minHeight: 22),
  1250. ));
  1251. }
  1252. if (msg.transTag != 0) {
  1253. minWidth = 200;
  1254. showMsg.add(Divider(color: Color(0xFFECECEC), height: 3));
  1255. Widget tranWidget = _transProcessWidget(msg.transTag);
  1256. showMsg.add(tranWidget);
  1257. }
  1258. return Container(
  1259. width: width + 20,
  1260. constraints:
  1261. msg.transTag != 0 ? BoxConstraints(minWidth: minWidth) : null,
  1262. child: Column(
  1263. crossAxisAlignment: CrossAxisAlignment.start, children: showMsg),
  1264. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  1265. decoration: BoxDecoration(
  1266. color: Colors.white,
  1267. border: Border.all(color: ReciveBorderColor, width: 0.5),
  1268. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  1269. );
  1270. }
  1271. void showFullImg(BuildContext context, MsgModel msg) {
  1272. print('显示图片');
  1273. Navigator.push(context,
  1274. MaterialPageRoute<void>(builder: (BuildContext context) {
  1275. return PhotoPage(msg: msg);
  1276. }));
  1277. }
  1278. _reveiveMsg(BuildContext context) {
  1279. Widget item;
  1280. switch (ChatType.valueOf(widget.msg.msgType)) {
  1281. case ChatType.TextChatType:
  1282. item = _receiveText(widget.msg);
  1283. break;
  1284. case ChatType.EmoticonType:
  1285. item = _receiveJIF(widget.msg);
  1286. break;
  1287. case ChatType.ImageChatType:
  1288. if (widget.msg.extraFile != null) {
  1289. item = _receiveImg(context, widget.msg.msgContent,
  1290. downloadData: widget.msg.extraFile);
  1291. } else {
  1292. item = _receiveImg(context, widget.msg.msgContent);
  1293. }
  1294. break;
  1295. case ChatType.ShortVideoChatType:
  1296. item = _receiveVideo(context, widget.msg.msgContent,
  1297. downloadData: widget.msg.extraFile);
  1298. break;
  1299. case ChatType.ShortVoiceChatType:
  1300. item = _receiveSound(context, widget.msg.msgContent);
  1301. break;
  1302. case ChatType.PlaceChatType:
  1303. item = PlaceItem(isMe: false, placeContent: widget.msg.msgContent);
  1304. break;
  1305. case ChatType.FileChatType:
  1306. item = _receiveFileMsgItem();
  1307. break;
  1308. default:
  1309. }
  1310. return wrapItemWithMenu(item);
  1311. }
  1312. Widget _receiveFileMsgItem() {
  1313. return DownloadItem(
  1314. isAutoDown: false,
  1315. msg: widget.msg,
  1316. onComplete: () {
  1317. if (mounted) {
  1318. setState(() {});
  1319. }
  1320. },
  1321. child: Container(
  1322. height: 100,
  1323. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  1324. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  1325. decoration: BoxDecoration(
  1326. color: Colors.white,
  1327. border: Border.all(color: ReciveBorderColor, width: 0.5),
  1328. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  1329. child: FileMsgItem(widget.msg)));
  1330. }
  1331. Widget _getReceivedMessageLayout(BuildContext context) {
  1332. bool hasHeadImg = true;
  1333. var memberModel = widget.memberModel;
  1334. if (memberModel == null) {
  1335. return Container();
  1336. }
  1337. if (memberModel.avtar == null || memberModel.avtar.length == 0) {
  1338. hasHeadImg = false;
  1339. }
  1340. GroupInfoModel infoModel = Provider.of<GroupInfoModel>(context);
  1341. var refName = Provider.of<RefNameProvider>(context)
  1342. .getGroupRefName(infoModel.sessionId, memberModel.memberId);
  1343. bool isShowSoundSate =
  1344. widget.msg.msgType == ChatType.ShortVoiceChatType.value &&
  1345. widget.msg.soundListened == 0;
  1346. return Row(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
  1347. Container(
  1348. margin: const EdgeInsets.only(right: 8.0),
  1349. child: GestureDetector(
  1350. child: ClipRRect(
  1351. borderRadius: BorderRadius.circular(8),
  1352. child: hasHeadImg
  1353. ? CachedNetworkImage(
  1354. imageUrl: memberModel.avtar,
  1355. placeholder: (context, url) => Image.asset(
  1356. Constants.DefaultHeadImgUrl,
  1357. width: 40,
  1358. height: 40,
  1359. ),
  1360. width: 40,
  1361. height: 40,
  1362. )
  1363. : SizedBox(
  1364. width: 40,
  1365. height: 40,
  1366. child: Image.asset(R.assetsImagesDefaultNorAvatar))),
  1367. onTap: () {
  1368. AppNavigator.pushProfileInfoPage(context, memberModel.memberId,
  1369. fromWhere: 2,
  1370. addMode: !FriendListMgr().isMyFriend(memberModel.memberId)
  1371. ? 1
  1372. : 0);
  1373. },
  1374. onLongPress: () {
  1375. print('long press user');
  1376. MessageMgr().emit('Alter User Message', memberModel);
  1377. },
  1378. )),
  1379. infoModel.isShowName > 0
  1380. ? Column(
  1381. crossAxisAlignment: CrossAxisAlignment.start,
  1382. children: <Widget>[
  1383. Container(
  1384. padding: EdgeInsets.only(left: 9),
  1385. child: fixedText(refName, fontSize: 12, color: Colors.grey),
  1386. ),
  1387. SizedBox(height: 2),
  1388. _reveiveMsg(context),
  1389. ],
  1390. )
  1391. : _reveiveMsg(context),
  1392. isShowSoundSate
  1393. ? Container(
  1394. margin: EdgeInsets.only(
  1395. left: 8, top: infoModel.isShowName > 0 ? 38 : 20),
  1396. child: CircleAvatar(
  1397. radius: 3.5,
  1398. backgroundColor: Colors.red,
  1399. ))
  1400. : Container()
  1401. ]);
  1402. }
  1403. }