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ů.
 
 
 
 
 
 

1333 řádky
42 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. ///todo 翻译管家系统通知消息
  215. if (type == ChatType.GroupChatNoticeType.value) {
  216. var res = GroupChatNotice.fromBuffer(widget.msg.msgContent);
  217. var groupInfoModel = Provider.of<GroupInfoModel>(context);
  218. var showStr = MsgHandler.getGroupNoticeMsg(res, groupInfoModel);
  219. return Container(
  220. alignment: Alignment.center,
  221. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  222. child: Text(
  223. showStr,
  224. textScaleFactor: 1.0,
  225. textAlign: TextAlign.center,
  226. style: TextStyle(color: Constants.GreyTextColor, fontSize: 12),
  227. ),
  228. );
  229. }
  230. }
  231. return Container();
  232. }
  233. _textGif(List<int> msgContent) {
  234. var msg = utf8.decode(msgContent);
  235. return Container(
  236. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  237. child: extendedText(
  238. msg,
  239. hideKeyboard: widget.hideKeyboard,
  240. fontSize: FontSize,
  241. color: Colors.black,
  242. ),
  243. );
  244. }
  245. _textMsg(List<int> msgContent) {
  246. var msg = utf8.decode(msgContent);
  247. Widget text = Container(
  248. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  249. child: extendedText(
  250. msg,
  251. hideKeyboard: widget.hideKeyboard,
  252. fontSize: FontSize,
  253. color: Colors.black,
  254. ),
  255. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  256. decoration: BoxDecoration(
  257. color: isLongPressed ? Colors.grey[300] : SendMsgBg,
  258. border: Border.all(color: Color(0xFFB9CBD7), width: 0.6),
  259. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  260. );
  261. if (widget.msg.refMsgContent != null &&
  262. widget.msg.refMsgContent.length > 0) {
  263. QuoteMsg quoteMsg = QuoteMsg.fromBuffer(widget.msg.refMsgContent);
  264. return Column(
  265. mainAxisSize: MainAxisSize.min,
  266. crossAxisAlignment: CrossAxisAlignment.end,
  267. children: <Widget>[
  268. text,
  269. SizedBox(height: 2),
  270. Container(
  271. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  272. padding: EdgeInsets.symmetric(vertical: 1, horizontal: 3),
  273. child: Text(
  274. quoteMsg.content,
  275. maxLines: 1,
  276. textScaleFactor: 1.0,
  277. overflow: TextOverflow.ellipsis,
  278. style: TextStyle(fontSize: 12),
  279. ),
  280. decoration: BoxDecoration(
  281. color: Colors.grey[300],
  282. borderRadius: BorderRadius.circular(5)),
  283. )
  284. ],
  285. );
  286. } else {
  287. return text;
  288. }
  289. }
  290. _soundMsg() {
  291. double time = widget.msg.extraInfo / 1000;
  292. if (time > 60) {
  293. time = 60.0;
  294. }
  295. bool isPlaying = false;
  296. var soundPath = widget.msg.localFile;
  297. isPlaying = SoundUtils().isPlaying(soundPath);
  298. var soundWidget = GestureDetector(
  299. child: Container(
  300. width: 120,
  301. child: Row(
  302. mainAxisAlignment: MainAxisAlignment.end,
  303. children: <Widget>[
  304. Container(
  305. alignment: Alignment.center,
  306. padding: EdgeInsets.only(bottom: 2),
  307. margin: EdgeInsets.only(right: 10),
  308. width: 25.5,
  309. height: 25.5,
  310. decoration: BoxDecoration(
  311. border:
  312. Border.all(color: const Color(0xFF1B92C7), width: 0.5),
  313. color: const Color(0xFF04A4FE),
  314. shape: BoxShape.circle),
  315. child: Icon(
  316. IconData(isPlaying ? 0xe652 : 0xe653,
  317. fontFamily: Constants.IconFontFamily),
  318. size: isPlaying ? 15 : 18,
  319. color: Colors.white,
  320. ),
  321. ),
  322. isPlaying
  323. ? Stack(
  324. children: <Widget>[
  325. Container(
  326. height: 18,
  327. width: 19,
  328. ),
  329. Positioned(
  330. bottom: 0,
  331. child: VideoAnim(
  332. begin: 18,
  333. start: 0.444,
  334. end: 4.5,
  335. )),
  336. Positioned(
  337. left: 7,
  338. bottom: 0,
  339. child: VideoAnim(
  340. begin: 4.5,
  341. end: 18,
  342. )),
  343. Positioned(
  344. left: 14,
  345. bottom: 0,
  346. child: VideoAnim(
  347. begin: 18,
  348. end: 4.5,
  349. ))
  350. ],
  351. )
  352. : Stack(
  353. children: <Widget>[
  354. Container(
  355. height: 18,
  356. width: 19,
  357. ),
  358. Positioned(
  359. bottom: 0, child: CustomUI.buildAudioContaniner(12)),
  360. Positioned(
  361. left: 7,
  362. bottom: 0,
  363. child: CustomUI.buildAudioContaniner(4.5)),
  364. Positioned(
  365. left: 14,
  366. bottom: 0,
  367. child: CustomUI.buildAudioContaniner(18))
  368. ],
  369. ),
  370. Expanded(child: SizedBox()),
  371. fixedText(time.toStringAsFixed(0),
  372. color: Constants.BlackTextColor, fontSize: 16)
  373. ],
  374. ),
  375. padding: EdgeInsets.symmetric(horizontal: 15, vertical: 12),
  376. decoration: BoxDecoration(
  377. color: SendMsgBg,
  378. border: Border.all(color: Color(0xFFB9CBD7), width: 0.6),
  379. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  380. ),
  381. onTap: () async {
  382. print('播放状态 : $isPlaying');
  383. print('当前文件$soundPath ');
  384. if (isPlaying) {
  385. SoundUtils().pause();
  386. } else {
  387. SoundUtils().play(soundPath, onPlayed: () {
  388. if (mounted) {
  389. setState(() {});
  390. }
  391. }, complete: () {
  392. if (mounted) {
  393. setState(() {});
  394. }
  395. });
  396. }
  397. },
  398. );
  399. return UploadImgItem(
  400. msg: widget.msg,
  401. child: soundWidget,
  402. isShowProgress: false,
  403. );
  404. }
  405. Size _getImgSize() {
  406. double aspectRatio = widget.msg.extraInfo / 100;
  407. var maxWidth = Screen.width * 0.65;
  408. var maxHeight = Screen.height / 4;
  409. var width, height;
  410. if (maxWidth / maxHeight > aspectRatio) {
  411. height = maxHeight;
  412. width = maxHeight * aspectRatio;
  413. } else {
  414. width = maxWidth;
  415. height = maxWidth / aspectRatio;
  416. }
  417. return Size(width, height);
  418. }
  419. _imgMsg(List<int> imgData) {
  420. var imgSize = _getImgSize();
  421. return GestureDetector(
  422. child: ClipRRect(
  423. child: UploadImgItem(
  424. msg: widget.msg,
  425. cancelToken: _cancelToken,
  426. child: Container(
  427. height: imgSize.height,
  428. width: imgSize.width,
  429. child: Image(
  430. fit: BoxFit.contain,
  431. image: MemoryImage(Uint8List.fromList(imgData)),
  432. ),
  433. )),
  434. borderRadius: BorderRadius.circular(5),
  435. ),
  436. onTap: () async {
  437. showFullImg(context, widget.msg);
  438. });
  439. }
  440. _videoMsg(BuildContext context, List<int> thumbnail) {
  441. var imgSize = _getImgSize();
  442. return InkWell(
  443. child: ClipRRect(
  444. child: Stack(
  445. alignment: Alignment.center,
  446. children: <Widget>[
  447. UploadImgItem(
  448. msg: widget.msg,
  449. cancelToken: _cancelToken,
  450. child: Container(
  451. width: imgSize.width,
  452. height: imgSize.height,
  453. child: Image(
  454. fit: BoxFit.contain,
  455. image: MemoryImage(Uint8List.fromList(thumbnail)),
  456. ),
  457. ))
  458. ],
  459. ),
  460. borderRadius: BorderRadius.circular(5),
  461. ),
  462. onTap: () {
  463. showVideoPage(context, widget.msg.localFile);
  464. },
  465. );
  466. }
  467. _msgLayout(BuildContext context, MsgModel msg) {
  468. Widget item;
  469. switch (ChatType.valueOf(msg.msgType)) {
  470. case ChatType.TextChatType:
  471. item = _textMsg(msg.msgContent);
  472. break;
  473. case ChatType.EmoticonType:
  474. item = _textGif(msg.msgContent);
  475. break;
  476. case ChatType.ImageChatType:
  477. item = _imgMsg(msg.msgContent);
  478. break;
  479. case ChatType.ShortVideoChatType:
  480. item = _videoMsg(context, msg.msgContent);
  481. break;
  482. case ChatType.ShortVoiceChatType:
  483. item = _soundMsg();
  484. break;
  485. case ChatType.RedWalletChatType:
  486. item = RedBagItem(
  487. Key(msg.time.toString()), UserData().basicInfo.userId, msg);
  488. break;
  489. case ChatType.PlaceChatType:
  490. item = PlaceItem(isMe: true, placeContent: msg.msgContent);
  491. break;
  492. case ChatType.GiftChatType:
  493. item = GiftMsgItem(msg.msgContent, true);
  494. break;
  495. case ChatType.FileChatType:
  496. item = _fileMsgItem();
  497. break;
  498. default:
  499. }
  500. return wrapItemWithMenu(item);
  501. }
  502. Widget _fileMsgItem() {
  503. return UploadImgItem(
  504. msg: widget.msg,
  505. cancelToken: _cancelToken,
  506. child: Container(
  507. height: 100,
  508. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  509. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  510. decoration: BoxDecoration(
  511. color: isLongPressed ? Colors.grey[300] : SendMsgBg,
  512. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  513. child: FileMsgItem(widget.msg)));
  514. }
  515. Widget _getSentMessageLayout(BuildContext context) {
  516. bool hasHeadImg = true;
  517. if (UserData().basicInfo.headimgurl == null ||
  518. UserData().basicInfo.headimgurl.length == 0) {
  519. hasHeadImg = false;
  520. }
  521. return Row(
  522. crossAxisAlignment: CrossAxisAlignment.start,
  523. mainAxisAlignment: MainAxisAlignment.end,
  524. children: <Widget>[
  525. MsgStateWidget(widget.msg),
  526. SizedBox(width: 3),
  527. _msgLayout(context, widget.msg),
  528. SizedBox(width: 10),
  529. Column(
  530. crossAxisAlignment: CrossAxisAlignment.end,
  531. children: <Widget>[
  532. ClipRRect(
  533. borderRadius: BorderRadius.circular(8),
  534. child: hasHeadImg
  535. ? CachedNetworkImage(
  536. imageUrl: UserData().basicInfo.headimgurl,
  537. placeholder: (context, url) => Image.asset(
  538. Constants.DefaultHeadImgUrl,
  539. width: 40,
  540. height: 40,
  541. ),
  542. width: 40,
  543. height: 40,
  544. )
  545. : SizedBox(
  546. width: 40,
  547. height: 40,
  548. child: Image.asset(R.assetsImagesDefaultNorAvatar))),
  549. ],
  550. )
  551. ]);
  552. }
  553. Widget wrapItemWithMenu(item) {
  554. List<Function> actionsFunc = [];
  555. List<String> actions = [
  556. I18n.of(context).forward,
  557. I18n.of(context).reply,
  558. I18n.of(context).delete,
  559. ];
  560. actionsFunc.add(() {
  561. print('转发消息');
  562. AppNavigator.pushForwardPage(context, widget.msg);
  563. });
  564. actionsFunc.add(() {
  565. print('发送引用的消息');
  566. MessageMgr().emit('Reply Select Message', widget.msg);
  567. });
  568. actionsFunc.add(() {
  569. MessageMgr().emit('Delete Select Message', widget.msg);
  570. });
  571. if (widget.msg.msgType == ChatType.TextChatType.value) {
  572. actions.insert(0, I18n.of(context).copy);
  573. actionsFunc.insert(0, () {
  574. //复制当前的文字
  575. print('复制文字 ${textList[curTextType]}');
  576. ClipboardData clipboardData =
  577. ClipboardData(text: textList[curTextType]);
  578. Clipboard.setData(clipboardData);
  579. });
  580. }
  581. //如果是文件增加复制下载地址
  582. if (widget.msg.msgType == ChatType.FileChatType.value) {
  583. actions.insert(0, I18n.of(context).copy_download_url);
  584. actionsFunc.insert(0, () {
  585. UploadUtil().copyFileUrl(widget.msg, context);
  586. });
  587. }
  588. if (widget.msg.msgType == ChatType.ShortVoiceChatType.value) {
  589. var soundPlayMode =
  590. Provider.of<KeyboardIndexProvider>(context).soundPlayMode;
  591. actions.add(soundPlayMode
  592. ? I18n.of(context).handset_playback
  593. : I18n.of(context).speaker_play);
  594. actionsFunc.add(() {
  595. Provider.of<KeyboardIndexProvider>(context)
  596. .changeSoundPlayMode(!soundPlayMode);
  597. SoundUtils.instance.savePlayModeConfig(soundPlayMode);
  598. });
  599. }
  600. // String date2 = DateTime.fromMillisecondsSinceEpoch(widget.msg.time).toString();
  601. return WPopupMenu(
  602. child: item,
  603. actions: actions,
  604. onLongPressStart: () {
  605. isLongPressed = true;
  606. setState(() {});
  607. },
  608. onLongPressEnd: () {
  609. isLongPressed = false;
  610. setState(() {});
  611. },
  612. onValueChanged: (int value) {
  613. print('选择的是$value个菜单');
  614. if (value >= 0 && value < actionsFunc.length) {
  615. actionsFunc[value]();
  616. }
  617. },
  618. );
  619. }
  620. //用户评价人工翻译,差评
  621. rateTranslateResult() async {
  622. return await HttpUtil().rateTranslateResult(widget.msg, () {
  623. if (mounted) {
  624. setState(() {});
  625. }
  626. });
  627. }
  628. _receiveJIF(MsgModel msg) {
  629. var text = utf8.decode(msg.msgContent);
  630. return extendedText(text, hideKeyboard: widget.hideKeyboard);
  631. }
  632. double _getTextWidth(String text) {
  633. var tp = TextPainter(
  634. text: TextSpan(style: TextStyle(fontSize: FontSize), text: text),
  635. textAlign: TextAlign.left,
  636. textDirection: TextDirection.ltr,
  637. textScaleFactor: 1,
  638. );
  639. tp.layout(maxWidth: Screen.width - 140);
  640. RegExp alterStr = RegExp(r'\[([0-9]+)\]');
  641. Iterable<Match> matches = alterStr.allMatches(text);
  642. // print('~~~~~~~~~~~~~~${matches.length}~~~~~~~~~~~~~~~');
  643. double delta = 0;
  644. for (Match m in matches) {
  645. // print('~~~~~~~~~~~~~~${m.group(1)}~~~~~~~~~~~~~~~');
  646. if (int.parse(m.group(1)) > 10) {
  647. delta += 20 + 4 - 25;
  648. } else {
  649. delta += 20 + 4 - 16.8;
  650. }
  651. }
  652. return tp.width + delta;
  653. }
  654. _receiveText(MsgModel msg) {
  655. List<Widget> showMsg = [];
  656. if (textList.length > 0) {
  657. showMsg.add(Container(
  658. constraints:
  659. BoxConstraints(maxWidth: Screen.width - 140, minHeight: 24),
  660. alignment: Alignment.centerLeft,
  661. child: extendedText(
  662. textList[curTextType],
  663. color: Constants.BlackTextColor,
  664. hideKeyboard: widget.hideKeyboard,
  665. fontSize: FontSize,
  666. )));
  667. }
  668. var width = _getTextWidth(textList[curTextType]);
  669. var minWidth = width;
  670. if (msg.transTag != 0) {
  671. minWidth = 200;
  672. showMsg.add(Padding(
  673. padding: EdgeInsets.symmetric(vertical: 5),
  674. child: Divider(color: Color(0xFFECECEC), height: 1)));
  675. Widget tranWidget = _transProcessWidget(msg.transTag);
  676. showMsg.add(tranWidget);
  677. }
  678. ///todo
  679. Widget text = Container(
  680. width: width + 20,
  681. constraints:
  682. BoxConstraints(maxWidth: Screen.width - 120, minWidth: minWidth),
  683. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  684. child: Column(
  685. crossAxisAlignment: CrossAxisAlignment.start, children: showMsg),
  686. decoration: BoxDecoration(
  687. border: Border.all(color: ReciveBorderColor, width: 0.5),
  688. color: isLongPressed ? Colors.grey[300] : Colors.white,
  689. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  690. );
  691. if (msg.refMsgContent != null && msg.refMsgContent.length > 0) {
  692. QuoteMsg quoteMsg = QuoteMsg.fromBuffer(msg.refMsgContent);
  693. return Column(
  694. mainAxisSize: MainAxisSize.min,
  695. crossAxisAlignment: CrossAxisAlignment.start,
  696. children: <Widget>[
  697. text,
  698. SizedBox(height: 2),
  699. Container(
  700. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  701. padding: EdgeInsets.symmetric(vertical: 1, horizontal: 3),
  702. child: Text(
  703. quoteMsg.content,
  704. maxLines: 1,
  705. textScaleFactor: 1.0,
  706. overflow: TextOverflow.ellipsis,
  707. style: TextStyle(fontSize: 12),
  708. ),
  709. decoration: BoxDecoration(
  710. color: Colors.grey[300],
  711. borderRadius: BorderRadius.circular(5)),
  712. )
  713. ],
  714. );
  715. } else {
  716. return text;
  717. }
  718. }
  719. _translateItemWidget(int code, String title, Function onTap) {
  720. Color color = onTap == null ? Constants.GreyTextColor : Color(0xFF087FF3);
  721. return InkWell(
  722. onTap: onTap,
  723. splashColor: Colors.red,
  724. child: Container(
  725. width: 80,
  726. child: Row(
  727. children: <Widget>[
  728. Icon(IconData(code, fontFamily: Constants.IconFontFamily),
  729. color: color, size: 20),
  730. SizedBox(width: 5),
  731. Expanded(
  732. child: SizedBox(
  733. child: Text(
  734. title,
  735. style: TextStyle(color: color, fontSize: 10),
  736. textScaleFactor: 1.0,
  737. maxLines: 1,
  738. overflow: TextOverflow.ellipsis,
  739. ),
  740. ))
  741. ],
  742. )));
  743. }
  744. bool isTranslating = false;
  745. _transProcessWidget(int transTag) {
  746. double width = 160;
  747. Widget userTranslateWidget;
  748. Widget machineTranslateWidget;
  749. if (transTag == 1) {
  750. //机器翻译中
  751. userTranslateWidget =
  752. _translateItemWidget(0xe670, I18n.of(context).man_retranslate, null);
  753. machineTranslateWidget =
  754. _translateItemWidget(0xe671, I18n.of(context).robotTranslate, null);
  755. } else if (transTag == 2) {
  756. //人工翻译中
  757. userTranslateWidget =
  758. _translateItemWidget(0xe670, I18n.of(context).ManTranslate, null);
  759. machineTranslateWidget =
  760. _translateItemWidget(0xe671, I18n.of(context).robot_retranslate, () {
  761. setState(() {
  762. curTextType += 1;
  763. curTextType %= textList.length;
  764. });
  765. });
  766. } else if (transTag == 3) {
  767. //机器翻译完成
  768. userTranslateWidget = _translateItemWidget(
  769. 0xe670,
  770. I18n.of(context).man_retranslate,
  771. isTranslating
  772. ? null
  773. : () async {
  774. isTranslating = true;
  775. int money =
  776. Provider.of<MoneyChangeProvider>(context, listen: false)
  777. .money;
  778. int voucher =
  779. Provider.of<VoucherChangeProvider>(context, listen: false)
  780. .voucher;
  781. int needMoney = widget.msg.getNeedMoney();
  782. if (needMoney > voucher + money) {
  783. showToast('翻译券和H币不足');
  784. return;
  785. }
  786. var res = await HttpUtil().getPersonalTranslate(widget.msg);
  787. if (res) {
  788. print('请求人工翻译成功,进行扣费');
  789. setState(() {
  790. widget.msg.transTag = 2;
  791. //优先扣券
  792. if (voucher > 0) {
  793. int costQuan = min(voucher, needMoney);
  794. Provider.of<VoucherChangeProvider>(context,
  795. listen: false)
  796. .subVoucher(costQuan);
  797. }
  798. //不足的话再扣H币
  799. if (needMoney > voucher) {
  800. Provider.of<MoneyChangeProvider>(context, listen: false)
  801. .subMoney(needMoney - voucher);
  802. }
  803. });
  804. }
  805. });
  806. machineTranslateWidget =
  807. _translateItemWidget(0xe671, I18n.of(context).robot_retranslate, () {
  808. setState(() {
  809. curTextType += 1;
  810. curTextType %= textList.length;
  811. });
  812. });
  813. } else if (transTag == 4 || transTag == 10) {
  814. //4人工翻译完成,未评论 10人工翻译完成已评论
  815. userTranslateWidget = InkWell(
  816. onTap: () {
  817. setState(() {
  818. curTextType = 0;
  819. });
  820. },
  821. child: Container(
  822. width: width / 2,
  823. child: Row(
  824. children: <Widget>[
  825. InkWell(
  826. child: Icon(
  827. IconData(0xe641, fontFamily: Constants.IconFontFamily),
  828. size: 18,
  829. color:
  830. transTag == 10 ? Colors.grey : Color(0xFF087FF3)),
  831. onTap: transTag == 10
  832. ? null
  833. : () {
  834. CustomUI.showIosDialog(
  835. context, I18n.of(context).bad_ev, () async {
  836. bool isSuccess = await rateTranslateResult();
  837. if (isSuccess) {
  838. Navigator.of(context).pop(true);
  839. showToast(I18n.of(context).success);
  840. } else {
  841. showToast(I18n.of(context).fail);
  842. Navigator.of(context).pop();
  843. }
  844. }, () {
  845. Navigator.of(context).pop();
  846. });
  847. },
  848. ),
  849. SizedBox(width: 5),
  850. Expanded(
  851. child: SizedBox(
  852. child: Text(
  853. I18n.of(context).over,
  854. style: TextStyle(color: Color(0xFF087FF3), fontSize: 10),
  855. textScaleFactor: 1.0,
  856. maxLines: 1,
  857. overflow: TextOverflow.ellipsis,
  858. ),
  859. ))
  860. ],
  861. )));
  862. machineTranslateWidget =
  863. _translateItemWidget(0xe675, I18n.of(context).see_original, () {
  864. setState(() {
  865. curTextType = textList.length - 1;
  866. });
  867. });
  868. }
  869. return Container(
  870. height: 26,
  871. alignment: Alignment.center,
  872. child: Row(
  873. children: <Widget>[
  874. userTranslateWidget,
  875. Expanded(
  876. child: SizedBox(child: VerticalDivider(), height: 20, width: 2)),
  877. machineTranslateWidget
  878. ],
  879. ),
  880. );
  881. }
  882. _receiveImg(BuildContext context, List<int> imgData, {String downloadData}) {
  883. ImageProvider provider = MemoryImage(Uint8List.fromList(imgData));
  884. var imgSize = _getImgSize();
  885. return GestureDetector(
  886. child: Container(
  887. width: imgSize.width,
  888. height: imgSize.height,
  889. child: ClipRRect(
  890. child: Image(
  891. image: provider ?? AssetImage(R.assetsImagesIcAlbum),
  892. ),
  893. borderRadius: BorderRadius.circular(5),
  894. )),
  895. onTap: () async {
  896. showFullImg(context, widget.msg);
  897. });
  898. }
  899. _receiveVideo(BuildContext context, List<int> imgData,
  900. {String downloadData}) {
  901. ImageProvider provider = MemoryImage(Uint8List.fromList(imgData));
  902. var imgSize = _getImgSize();
  903. return InkWell(
  904. onTap: () {
  905. if (widget.msg.localFile != null) {
  906. showVideoPage(context, widget.msg.localFile);
  907. }
  908. },
  909. child: DownloadItem(
  910. isAutoDown: false,
  911. msg: widget.msg,
  912. child: Container(
  913. width: imgSize.width,
  914. height: imgSize.height,
  915. child: ClipRRect(
  916. child: Image(
  917. image: provider ?? AssetImage(R.assetsImagesIcAlbum),
  918. ),
  919. borderRadius: BorderRadius.circular(5),
  920. ),
  921. ),
  922. ));
  923. }
  924. showVideoPage(BuildContext context, String filePath) {
  925. widget.hideKeyboard();
  926. Navigator.push(context,
  927. MaterialPageRoute<void>(builder: (BuildContext context) {
  928. return VideoPage(videoPath: filePath);
  929. }));
  930. }
  931. Widget _receiveFileMsgItem() {
  932. return DownloadItem(
  933. isAutoDown: false,
  934. msg: widget.msg,
  935. onComplete: () {
  936. if (mounted) {
  937. setState(() {});
  938. }
  939. },
  940. child: Container(
  941. height: 100,
  942. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  943. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  944. decoration: BoxDecoration(
  945. color: Colors.white,
  946. border: Border.all(color: ReciveBorderColor, width: 0.5),
  947. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  948. child: FileMsgItem(widget.msg)));
  949. }
  950. _receiveSound(BuildContext context, List<int> soundData) {
  951. print('收到语音消息');
  952. MsgModel msg = widget.msg;
  953. var time = widget.msg.extraInfo / 1000;
  954. if (time > 60) {
  955. time = 60.0;
  956. }
  957. bool isPlaying = false;
  958. if (curSoundUrl != null) {
  959. isPlaying = SoundUtils().isPlaying(curSoundUrl);
  960. }
  961. var soundWidget = InkWell(
  962. onTap: () async {
  963. bool isLocal = true;
  964. if (msg.localFile != null) {
  965. isLocal = true;
  966. curSoundUrl = msg.localFile;
  967. } else {
  968. isLocal = false;
  969. var sessionId = msg.sessionId;
  970. curSoundUrl = UploadUtil()
  971. .getFullUrl(msg.extraFile, sessionId, msg.channelType);
  972. }
  973. print('当前文件$curSoundUrl 本地?$isLocal');
  974. if (isPlaying) {
  975. await SoundUtils().pause();
  976. } else {
  977. print('开始播放');
  978. if (widget.msg.soundListened == 0) {
  979. widget.msg.updateSoundListened();
  980. } //设置为已经播放
  981. await SoundUtils().play(curSoundUrl, isLocal: isLocal, onPlayed: () {
  982. if (mounted) {
  983. this.setState(() {});
  984. }
  985. }, complete: () {
  986. if (mounted) {
  987. this.setState(() {});
  988. }
  989. });
  990. }
  991. },
  992. child: Container(
  993. width: 130,
  994. child: Row(children: <Widget>[
  995. Container(
  996. alignment: Alignment.center,
  997. padding: EdgeInsets.only(bottom: 2),
  998. margin: EdgeInsets.only(right: 10),
  999. width: 25.5,
  1000. height: 25.5,
  1001. decoration: BoxDecoration(
  1002. border:
  1003. Border.all(color: const Color(0xFF1B92C7), width: 0.5),
  1004. color: const Color(0xFF04A4FE),
  1005. shape: BoxShape.circle),
  1006. child: Icon(
  1007. IconData(isPlaying ? 0xe652 : 0xe653,
  1008. fontFamily: Constants.IconFontFamily),
  1009. size: isPlaying ? 15 : 18,
  1010. color: Colors.white,
  1011. ),
  1012. ),
  1013. isPlaying
  1014. ? Stack(
  1015. children: <Widget>[
  1016. Container(
  1017. height: 18,
  1018. width: 19,
  1019. ),
  1020. Positioned(
  1021. bottom: 0,
  1022. child: VideoAnim(
  1023. begin: 18,
  1024. start: 0.444,
  1025. end: 4.5,
  1026. )),
  1027. Positioned(
  1028. left: 7,
  1029. bottom: 0,
  1030. child: VideoAnim(
  1031. begin: 4.5,
  1032. end: 18,
  1033. )),
  1034. Positioned(
  1035. left: 14,
  1036. bottom: 0,
  1037. child: VideoAnim(
  1038. begin: 18,
  1039. end: 4.5,
  1040. ))
  1041. ],
  1042. )
  1043. : Stack(
  1044. children: <Widget>[
  1045. Container(
  1046. height: 18,
  1047. width: 19,
  1048. ),
  1049. Positioned(
  1050. bottom: 0, child: CustomUI.buildAudioContaniner(12)),
  1051. Positioned(
  1052. left: 7,
  1053. bottom: 0,
  1054. child: CustomUI.buildAudioContaniner(4.5)),
  1055. Positioned(
  1056. left: 14,
  1057. bottom: 0,
  1058. child: CustomUI.buildAudioContaniner(18))
  1059. ],
  1060. ),
  1061. Expanded(child: SizedBox()),
  1062. fixedText(time.toStringAsFixed(0),
  1063. color: Constants.BlackTextColor, fontSize: 16)
  1064. ])),
  1065. );
  1066. List<Widget> showMsg = [];
  1067. showMsg.add(DownloadItem(
  1068. msg: widget.msg,
  1069. child: soundWidget,
  1070. isShowProgress: false,
  1071. ));
  1072. double width = 130;
  1073. double minWidth = 0;
  1074. if (textList.length > 0) {
  1075. width = _getTextWidth(textList[curTextType]) + 20;
  1076. width = min(width, Screen.width - 120);
  1077. showMsg.add(Padding(
  1078. padding: EdgeInsets.symmetric(vertical: 5),
  1079. child: Divider(
  1080. height: 1,
  1081. )));
  1082. showMsg.add(Container(
  1083. child: extendedText(
  1084. textList[curTextType],
  1085. color: Constants.BlackTextColor,
  1086. hideKeyboard: widget.hideKeyboard,
  1087. fontSize: FontSize,
  1088. ),
  1089. alignment: Alignment.centerLeft,
  1090. constraints:
  1091. BoxConstraints(maxWidth: Screen.width - 120, minHeight: 22),
  1092. ));
  1093. }
  1094. if (msg.transTag != 0) {
  1095. minWidth = 200;
  1096. showMsg.add(Divider(color: Color(0xFFECECEC), height: 3));
  1097. Widget tranWidget = _transProcessWidget(msg.transTag);
  1098. showMsg.add(tranWidget);
  1099. }
  1100. return Container(
  1101. width: width + 20,
  1102. constraints:
  1103. BoxConstraints(maxWidth: Screen.width - 120, minWidth: minWidth),
  1104. child: Column(
  1105. crossAxisAlignment: CrossAxisAlignment.start, children: showMsg),
  1106. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  1107. decoration: BoxDecoration(
  1108. color: Colors.white,
  1109. border: Border.all(color: ReciveBorderColor, width: 0.5),
  1110. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  1111. );
  1112. }
  1113. void showFullImg(BuildContext context, MsgModel msg) {
  1114. print('显示图片');
  1115. Navigator.push(context,
  1116. MaterialPageRoute<void>(builder: (BuildContext context) {
  1117. return PhotoPage(msg: msg);
  1118. }));
  1119. }
  1120. _reveiveMsg(BuildContext context) {
  1121. Widget item;
  1122. switch (ChatType.valueOf(widget.msg.msgType)) {
  1123. case ChatType.TextChatType:
  1124. item = _receiveText(widget.msg);
  1125. break;
  1126. case ChatType.EmoticonType:
  1127. item = _receiveJIF(widget.msg);
  1128. break;
  1129. case ChatType.ImageChatType:
  1130. if (widget.msg.extraFile != null) {
  1131. item = _receiveImg(context, widget.msg.msgContent,
  1132. downloadData: widget.msg.extraFile);
  1133. } else {
  1134. item = _receiveImg(context, widget.msg.msgContent);
  1135. }
  1136. break;
  1137. case ChatType.ShortVideoChatType:
  1138. item = _receiveVideo(context, widget.msg.msgContent,
  1139. downloadData: widget.msg.extraFile);
  1140. break;
  1141. case ChatType.ShortVoiceChatType:
  1142. item = _receiveSound(context, widget.msg.msgContent);
  1143. break;
  1144. case ChatType.RedWalletChatType:
  1145. item = RedBagItem(
  1146. Key(widget.msg.time.toString()), friendInfo.userId, widget.msg);
  1147. break;
  1148. case ChatType.PlaceChatType:
  1149. item = PlaceItem(isMe: false, placeContent: widget.msg.msgContent);
  1150. break;
  1151. case ChatType.GiftChatType:
  1152. item = GiftMsgItem(widget.msg.msgContent, false);
  1153. break;
  1154. case ChatType.FileChatType:
  1155. item = _receiveFileMsgItem();
  1156. break;
  1157. default:
  1158. }
  1159. return wrapItemWithMenu(item);
  1160. }
  1161. Widget _getReceivedMessageLayout(BuildContext context) {
  1162. bool hasHeadImg = true;
  1163. if (friendInfo.headimgurl == null || friendInfo.headimgurl.length == 0) {
  1164. hasHeadImg = false;
  1165. }
  1166. bool isShowSoundSate =
  1167. widget.msg.msgType == ChatType.ShortVoiceChatType.value &&
  1168. widget.msg.soundListened == 0;
  1169. return Row(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
  1170. Container(
  1171. margin: const EdgeInsets.only(right: 8.0),
  1172. child: InkWell(
  1173. child: ClipRRect(
  1174. borderRadius: BorderRadius.circular(8),
  1175. child: hasHeadImg
  1176. ? CachedNetworkImage(
  1177. imageUrl: friendInfo.headimgurl,
  1178. placeholder: (context, url) => Image.asset(
  1179. Constants.DefaultHeadImgUrl,
  1180. width: 40,
  1181. height: 40,
  1182. ),
  1183. width: 40,
  1184. height: 40,
  1185. )
  1186. : SizedBox(
  1187. width: 40,
  1188. height: 40,
  1189. child: Image.asset(R.assetsImagesDefaultNorAvatar))),
  1190. onTap: () {
  1191. AppNavigator.pushProfileInfoPage(context, friendInfo.userId,
  1192. fromWhere: 0);
  1193. },
  1194. )),
  1195. _reveiveMsg(context),
  1196. isShowSoundSate
  1197. ? Container(
  1198. margin: EdgeInsets.only(left: 8),
  1199. width: 40,
  1200. height: 40,
  1201. alignment: Alignment.centerLeft,
  1202. child: CircleAvatar(
  1203. radius: 3.5,
  1204. backgroundColor: Colors.red,
  1205. ))
  1206. : Container()
  1207. ]);
  1208. }
  1209. }