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

1309 lines
41 KiB

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