Hibok
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

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