Hibok
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

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