Hibok
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 
 

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