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.
 
 
 
 
 
 

1318 linhas
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. 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. placeholder: (context, url) => Image.asset(
  537. Constants.DefaultHeadImgUrl,
  538. width: 40,
  539. height: 40,
  540. ),
  541. width: 40,
  542. height: 40,
  543. )
  544. : SizedBox(
  545. width: 40,
  546. height: 40,
  547. child: Image.asset(R.assetsImagesDefaultNorAvatar))),
  548. ],
  549. )
  550. ]);
  551. }
  552. Widget wrapItemWithMenu(item) {
  553. List<Function> actionsFunc = [];
  554. List<String> actions = [
  555. I18n.of(context).delete,
  556. I18n.of(context).reply,
  557. '转发'
  558. ];
  559. actionsFunc.add(() {
  560. MessageMgr().emit('Delete Select Message', widget.msg);
  561. });
  562. actionsFunc.add(() {
  563. print('发送引用的消息');
  564. MessageMgr().emit('Reply Select Message', widget.msg);
  565. });
  566. actionsFunc.add(() {
  567. print('转发消息');
  568. AppNavigator.pushForwardPage(context, widget.msg);
  569. });
  570. if (widget.msg.msgType == ChatType.TextChatType.value) {
  571. actions.insert(0, I18n.of(context).copy);
  572. actionsFunc.insert(0, () {
  573. //复制当前的文字
  574. print('复制文字 ${textList[curTextType]}');
  575. ClipboardData clipboardData =
  576. ClipboardData(text: textList[curTextType]);
  577. Clipboard.setData(clipboardData);
  578. });
  579. }
  580. if (widget.msg.msgType == ChatType.ShortVoiceChatType.value) {
  581. var soundPlayMode =
  582. Provider.of<KeyboardIndexProvider>(context).soundPlayMode;
  583. actions.add(soundPlayMode
  584. ? I18n.of(context).handset_playback
  585. : I18n.of(context).speaker_play);
  586. actionsFunc.add(() {
  587. Provider.of<KeyboardIndexProvider>(context)
  588. .changeSoundPlayMode(!soundPlayMode);
  589. SoundUtils.instance.savePlayModeConfig(soundPlayMode);
  590. });
  591. }
  592. return WPopupMenu(
  593. child: item,
  594. actions: actions,
  595. onLongPressStart: () {
  596. isLongPressed = true;
  597. setState(() {});
  598. },
  599. onLongPressEnd: () {
  600. isLongPressed = false;
  601. setState(() {});
  602. },
  603. onValueChanged: (int value) {
  604. print('选择的是$value个菜单');
  605. if (value >= 0 && value < actionsFunc.length) {
  606. actionsFunc[value]();
  607. }
  608. },
  609. );
  610. }
  611. //用户评价人工翻译,差评
  612. rateTranslateResult() async {
  613. return await HttpUtil().rateTranslateResult(widget.msg, () {
  614. if (mounted) {
  615. setState(() {});
  616. }
  617. });
  618. }
  619. _receiveJIF(MsgModel msg) {
  620. var text = utf8.decode(msg.msgContent);
  621. return extendedText(text, hideKeyboard: widget.hideKeyboard);
  622. }
  623. double _getTextWidth(String text) {
  624. var tp = TextPainter(
  625. text: TextSpan(style: TextStyle(fontSize: FontSize), text: text),
  626. textAlign: TextAlign.left,
  627. textDirection: TextDirection.ltr,
  628. textScaleFactor: 1,
  629. );
  630. tp.layout(maxWidth: Screen.width - 140);
  631. RegExp alterStr = RegExp(r'\[([0-9]+)\]');
  632. Iterable<Match> matches = alterStr.allMatches(text);
  633. print('~~~~~~~~~~~~~~${matches.length}~~~~~~~~~~~~~~~');
  634. double delta = 0;
  635. for (Match m in matches) {
  636. print('~~~~~~~~~~~~~~${m.group(1)}~~~~~~~~~~~~~~~');
  637. if (int.parse(m.group(1)) > 10) {
  638. delta += 20 + 4 - 25;
  639. } else {
  640. delta += 20 + 4 - 16.8;
  641. }
  642. }
  643. return tp.width + delta;
  644. }
  645. _receiveText(MsgModel msg) {
  646. List<Widget> showMsg = [];
  647. if (textList.length > 0) {
  648. showMsg.add(Container(
  649. constraints:
  650. BoxConstraints(maxWidth: Screen.width - 140, minHeight: 24),
  651. alignment: Alignment.centerLeft,
  652. child: extendedText(
  653. textList[curTextType],
  654. color: Constants.BlackTextColor,
  655. hideKeyboard: widget.hideKeyboard,
  656. fontSize: FontSize,
  657. )));
  658. }
  659. var width = _getTextWidth(textList[curTextType]);
  660. var minWidth = width;
  661. if (msg.transTag != 0) {
  662. minWidth = 200;
  663. showMsg.add(Padding(
  664. padding: EdgeInsets.symmetric(vertical: 5),
  665. child: Divider(color: Color(0xFFECECEC), height: 1)));
  666. Widget tranWidget = _transProcessWidget(msg.transTag);
  667. showMsg.add(tranWidget);
  668. }
  669. ///todo
  670. Widget text = Container(
  671. width: width + 20,
  672. constraints:
  673. BoxConstraints(maxWidth: Screen.width - 120, minWidth: minWidth),
  674. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  675. child: Column(
  676. crossAxisAlignment: CrossAxisAlignment.start, children: showMsg),
  677. decoration: BoxDecoration(
  678. border: Border.all(color: ReciveBorderColor, width: 0.5),
  679. color: isLongPressed ? Colors.grey[300] : Colors.white,
  680. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  681. );
  682. if (msg.refMsgContent != null && msg.refMsgContent.length > 0) {
  683. QuoteMsg quoteMsg = QuoteMsg.fromBuffer(msg.refMsgContent);
  684. return Column(
  685. mainAxisSize: MainAxisSize.min,
  686. crossAxisAlignment: CrossAxisAlignment.start,
  687. children: <Widget>[
  688. text,
  689. SizedBox(height: 2),
  690. Container(
  691. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  692. padding: EdgeInsets.symmetric(vertical: 1, horizontal: 3),
  693. child: Text(
  694. quoteMsg.content,
  695. maxLines: 1,
  696. textScaleFactor: 1.0,
  697. overflow: TextOverflow.ellipsis,
  698. style: TextStyle(fontSize: 12),
  699. ),
  700. decoration: BoxDecoration(
  701. color: Colors.grey[300],
  702. borderRadius: BorderRadius.circular(5)),
  703. )
  704. ],
  705. );
  706. } else {
  707. return text;
  708. }
  709. }
  710. _translateItemWidget(int code, String title, Function onTap) {
  711. Color color = onTap == null ? Constants.GreyTextColor : Color(0xFF087FF3);
  712. return InkWell(
  713. onTap: onTap,
  714. splashColor: Colors.red,
  715. child: Container(
  716. width: 80,
  717. child: Row(
  718. children: <Widget>[
  719. Icon(IconData(code, fontFamily: Constants.IconFontFamily),
  720. color: color, size: 20),
  721. SizedBox(width: 5),
  722. Expanded(
  723. child: SizedBox(
  724. child: Text(
  725. title,
  726. style: TextStyle(color: color, fontSize: 10),
  727. textScaleFactor: 1.0,
  728. maxLines: 1,
  729. overflow: TextOverflow.ellipsis,
  730. ),
  731. ))
  732. ],
  733. )));
  734. }
  735. bool isTranslating = false;
  736. _transProcessWidget(int transTag) {
  737. double width = 160;
  738. Widget userTranslateWidget;
  739. Widget machineTranslateWidget;
  740. if (transTag == 1) {
  741. //机器翻译中
  742. userTranslateWidget = _translateItemWidget(0xe670, '人工重译', null);
  743. machineTranslateWidget =
  744. _translateItemWidget(0xe671, I18n.of(context).robotTranslate, null);
  745. } else if (transTag == 2) {
  746. //人工翻译中
  747. userTranslateWidget = _translateItemWidget(0xe670, '人工翻译中', null);
  748. machineTranslateWidget = _translateItemWidget(0xe671, '机器重译', () {
  749. setState(() {
  750. curTextType += 1;
  751. curTextType %= textList.length;
  752. });
  753. });
  754. } else if (transTag == 3) {
  755. //机器翻译完成
  756. userTranslateWidget = _translateItemWidget(
  757. 0xe670,
  758. '人工重译',
  759. isTranslating
  760. ? null
  761. : () async {
  762. isTranslating = true;
  763. int money =
  764. Provider.of<MoneyChangeProvider>(context, listen: false)
  765. .money;
  766. int voucher =
  767. Provider.of<VoucherChangeProvider>(context, listen: false)
  768. .voucher;
  769. int needMoney = widget.msg.getNeedMoney();
  770. if (needMoney > voucher + money) {
  771. showToast('翻译券和H币不足');
  772. return;
  773. }
  774. var res = await HttpUtil().getPersonalTranslate(widget.msg);
  775. if (res) {
  776. print('请求人工翻译成功,进行扣费');
  777. setState(() {
  778. widget.msg.transTag = 2;
  779. //优先扣券
  780. if (voucher > 0) {
  781. int costQuan = min(voucher, needMoney);
  782. Provider.of<VoucherChangeProvider>(context,
  783. listen: false)
  784. .subVoucher(costQuan);
  785. }
  786. //不足的话再扣H币
  787. if (needMoney > voucher) {
  788. Provider.of<MoneyChangeProvider>(context, listen: false)
  789. .subMoney(needMoney - voucher);
  790. }
  791. });
  792. }
  793. });
  794. machineTranslateWidget = _translateItemWidget(0xe671, '机器重译', () {
  795. setState(() {
  796. curTextType += 1;
  797. curTextType %= textList.length;
  798. });
  799. });
  800. } else if (transTag == 4 || transTag == 10) {
  801. //4人工翻译完成,未评论 10人工翻译完成已评论
  802. userTranslateWidget = InkWell(
  803. onTap: () {
  804. setState(() {
  805. curTextType = 0;
  806. });
  807. },
  808. child: Container(
  809. width: width / 2,
  810. child: Row(
  811. children: <Widget>[
  812. InkWell(
  813. child: Icon(
  814. IconData(0xe641, fontFamily: Constants.IconFontFamily),
  815. size: 18,
  816. color:
  817. transTag == 10 ? Colors.grey : Color(0xFF087FF3)),
  818. onTap: transTag == 10
  819. ? null
  820. : () {
  821. CustomUI.showIosDialog(
  822. context, I18n.of(context).bad_ev, () async {
  823. bool isSuccess = await rateTranslateResult();
  824. if (isSuccess) {
  825. Navigator.of(context).pop(true);
  826. showToast(I18n.of(context).success);
  827. } else {
  828. showToast(I18n.of(context).fail);
  829. Navigator.of(context).pop();
  830. }
  831. }, () {
  832. Navigator.of(context).pop();
  833. });
  834. },
  835. ),
  836. SizedBox(width: 5),
  837. Expanded(
  838. child: SizedBox(
  839. child: Text(
  840. I18n.of(context).over,
  841. style: TextStyle(color: Color(0xFF087FF3), fontSize: 10),
  842. textScaleFactor: 1.0,
  843. maxLines: 1,
  844. overflow: TextOverflow.ellipsis,
  845. ),
  846. ))
  847. ],
  848. )));
  849. machineTranslateWidget = _translateItemWidget(0xe675, '查看原文', () {
  850. setState(() {
  851. curTextType = textList.length - 1;
  852. });
  853. });
  854. }
  855. return Container(
  856. height: 26,
  857. alignment: Alignment.center,
  858. child: Row(
  859. children: <Widget>[
  860. userTranslateWidget,
  861. Expanded(
  862. child: SizedBox(child: VerticalDivider(), height: 20, width: 2)),
  863. machineTranslateWidget
  864. ],
  865. ),
  866. );
  867. }
  868. _receiveImg(BuildContext context, List<int> imgData, {String downloadData}) {
  869. ImageProvider provider = MemoryImage(Uint8List.fromList(imgData));
  870. var imgSize = _getImgSize();
  871. return GestureDetector(
  872. child: Container(
  873. width: imgSize.width,
  874. height: imgSize.height,
  875. child: ClipRRect(
  876. child: Image(
  877. image: provider ?? AssetImage(R.assetsImagesIcAlbum),
  878. ),
  879. borderRadius: BorderRadius.circular(5),
  880. )),
  881. onTap: () async {
  882. showFullImg(context, widget.msg);
  883. });
  884. }
  885. _receiveVideo(BuildContext context, List<int> imgData,
  886. {String downloadData}) {
  887. ImageProvider provider = MemoryImage(Uint8List.fromList(imgData));
  888. var imgSize = _getImgSize();
  889. return InkWell(
  890. onTap: () {
  891. if (widget.msg.localFile != null) {
  892. showVideoPage(context, widget.msg.localFile);
  893. }
  894. },
  895. child: DownloadItem(
  896. isAutoDown: false,
  897. msg: widget.msg,
  898. child: Container(
  899. width: imgSize.width,
  900. height: imgSize.height,
  901. child: ClipRRect(
  902. child: Image(
  903. image: provider ?? AssetImage(R.assetsImagesIcAlbum),
  904. ),
  905. borderRadius: BorderRadius.circular(5),
  906. ),
  907. ),
  908. ));
  909. }
  910. showVideoPage(BuildContext context, String filePath) {
  911. widget.hideKeyboard();
  912. Navigator.push(context,
  913. MaterialPageRoute<void>(builder: (BuildContext context) {
  914. return VideoPage(videoPath: filePath);
  915. }));
  916. }
  917. Widget _receiveFileMsgItem() {
  918. return DownloadItem(
  919. isAutoDown: false,
  920. msg: widget.msg,
  921. onComplete: () {
  922. if (mounted) {
  923. setState(() {});
  924. }
  925. },
  926. child: Container(
  927. height: 100,
  928. constraints: BoxConstraints(maxWidth: Screen.width - 120),
  929. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  930. decoration: BoxDecoration(
  931. color: Colors.white,
  932. border: Border.all(color: ReciveBorderColor, width: 0.5),
  933. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  934. child: FileMsgItem(widget.msg)));
  935. }
  936. _receiveSound(BuildContext context, List<int> soundData) {
  937. print('收到语音消息');
  938. MsgModel msg = widget.msg;
  939. var time = widget.msg.extraInfo / 1000;
  940. if (time > 60) {
  941. time = 60.0;
  942. }
  943. bool isPlaying = false;
  944. if (curSoundUrl != null) {
  945. isPlaying = SoundUtils().isPlaying(curSoundUrl);
  946. }
  947. var soundWidget = InkWell(
  948. onTap: () async {
  949. bool isLocal = true;
  950. if (msg.localFile != null) {
  951. isLocal = true;
  952. curSoundUrl = msg.localFile;
  953. } else {
  954. isLocal = false;
  955. var sessionId = msg.sessionId;
  956. curSoundUrl = UploadUtil()
  957. .getFullUrl(msg.extraFile, sessionId, msg.channelType);
  958. }
  959. print('当前文件$curSoundUrl 本地?$isLocal');
  960. if (isPlaying) {
  961. await SoundUtils().pause();
  962. } else {
  963. print('开始播放');
  964. if (widget.msg.soundListened == 0) {
  965. widget.msg.updateSoundListened();
  966. } //设置为已经播放
  967. await SoundUtils().play(curSoundUrl, isLocal: isLocal, onPlayed: () {
  968. if (mounted) {
  969. this.setState(() {});
  970. }
  971. }, complete: () {
  972. if (mounted) {
  973. this.setState(() {});
  974. }
  975. });
  976. }
  977. },
  978. child: Container(
  979. width: 130,
  980. child: Row(children: <Widget>[
  981. Container(
  982. alignment: Alignment.center,
  983. padding: EdgeInsets.only(bottom: 2),
  984. margin: EdgeInsets.only(right: 10),
  985. width: 25.5,
  986. height: 25.5,
  987. decoration: BoxDecoration(
  988. border:
  989. Border.all(color: const Color(0xFF1B92C7), width: 0.5),
  990. color: const Color(0xFF04A4FE),
  991. shape: BoxShape.circle),
  992. child: Icon(
  993. IconData(isPlaying ? 0xe652 : 0xe653,
  994. fontFamily: Constants.IconFontFamily),
  995. size: isPlaying ? 15 : 18,
  996. color: Colors.white,
  997. ),
  998. ),
  999. isPlaying
  1000. ? Stack(
  1001. children: <Widget>[
  1002. Container(
  1003. height: 18,
  1004. width: 19,
  1005. ),
  1006. Positioned(
  1007. bottom: 0,
  1008. child: VideoAnim(
  1009. begin: 18,
  1010. start: 0.444,
  1011. end: 4.5,
  1012. )),
  1013. Positioned(
  1014. left: 7,
  1015. bottom: 0,
  1016. child: VideoAnim(
  1017. begin: 4.5,
  1018. end: 18,
  1019. )),
  1020. Positioned(
  1021. left: 14,
  1022. bottom: 0,
  1023. child: VideoAnim(
  1024. begin: 18,
  1025. end: 4.5,
  1026. ))
  1027. ],
  1028. )
  1029. : Stack(
  1030. children: <Widget>[
  1031. Container(
  1032. height: 18,
  1033. width: 19,
  1034. ),
  1035. Positioned(
  1036. bottom: 0, child: CustomUI.buildAudioContaniner(12)),
  1037. Positioned(
  1038. left: 7,
  1039. bottom: 0,
  1040. child: CustomUI.buildAudioContaniner(4.5)),
  1041. Positioned(
  1042. left: 14,
  1043. bottom: 0,
  1044. child: CustomUI.buildAudioContaniner(18))
  1045. ],
  1046. ),
  1047. Expanded(child: SizedBox()),
  1048. fixedText(time.toStringAsFixed(0),
  1049. color: Constants.BlackTextColor, fontSize: 16)
  1050. ])),
  1051. );
  1052. List<Widget> showMsg = [];
  1053. showMsg.add(DownloadItem(
  1054. msg: widget.msg,
  1055. child: soundWidget,
  1056. isShowProgress: false,
  1057. ));
  1058. double width = 130;
  1059. double minWidth = 0;
  1060. if (textList.length > 0) {
  1061. width = _getTextWidth(textList[curTextType]) + 20;
  1062. width = min(width, Screen.width - 120);
  1063. showMsg.add(Padding(
  1064. padding: EdgeInsets.symmetric(vertical: 5),
  1065. child: Divider(
  1066. height: 1,
  1067. )));
  1068. showMsg.add(Container(
  1069. child: extendedText(
  1070. textList[curTextType],
  1071. color: Constants.BlackTextColor,
  1072. hideKeyboard: widget.hideKeyboard,
  1073. fontSize: FontSize,
  1074. ),
  1075. alignment: Alignment.centerLeft,
  1076. constraints:
  1077. BoxConstraints(maxWidth: Screen.width - 120, minHeight: 22),
  1078. ));
  1079. }
  1080. if (msg.transTag != 0) {
  1081. minWidth = 200;
  1082. showMsg.add(Divider(color: Color(0xFFECECEC), height: 3));
  1083. Widget tranWidget = _transProcessWidget(msg.transTag);
  1084. showMsg.add(tranWidget);
  1085. }
  1086. return Container(
  1087. width: width + 20,
  1088. constraints:
  1089. BoxConstraints(maxWidth: Screen.width - 120, minWidth: minWidth),
  1090. child: Column(
  1091. crossAxisAlignment: CrossAxisAlignment.start, children: showMsg),
  1092. padding: EdgeInsets.symmetric(horizontal: 9, vertical: 10.5),
  1093. decoration: BoxDecoration(
  1094. color: Colors.white,
  1095. border: Border.all(color: ReciveBorderColor, width: 0.5),
  1096. borderRadius: BorderRadius.all(Radius.circular(ChatRadius))),
  1097. );
  1098. }
  1099. void showFullImg(BuildContext context, MsgModel msg) {
  1100. print('显示图片');
  1101. Navigator.push(context,
  1102. MaterialPageRoute<void>(builder: (BuildContext context) {
  1103. return PhotoPage(msg: msg);
  1104. }));
  1105. }
  1106. _reveiveMsg(BuildContext context) {
  1107. Widget item;
  1108. switch (ChatType.valueOf(widget.msg.msgType)) {
  1109. case ChatType.TextChatType:
  1110. item = _receiveText(widget.msg);
  1111. break;
  1112. case ChatType.EmoticonType:
  1113. item = _receiveJIF(widget.msg);
  1114. break;
  1115. case ChatType.ImageChatType:
  1116. if (widget.msg.extraFile != null) {
  1117. item = _receiveImg(context, widget.msg.msgContent,
  1118. downloadData: widget.msg.extraFile);
  1119. } else {
  1120. item = _receiveImg(context, widget.msg.msgContent);
  1121. }
  1122. break;
  1123. case ChatType.ShortVideoChatType:
  1124. item = _receiveVideo(context, widget.msg.msgContent,
  1125. downloadData: widget.msg.extraFile);
  1126. break;
  1127. case ChatType.ShortVoiceChatType:
  1128. item = _receiveSound(context, widget.msg.msgContent);
  1129. break;
  1130. case ChatType.RedWalletChatType:
  1131. item = RedBagItem(
  1132. Key(widget.msg.time.toString()), friendInfo.userId, widget.msg);
  1133. break;
  1134. case ChatType.PlaceChatType:
  1135. item = PlaceItem(isMe: false, placeContent: widget.msg.msgContent);
  1136. break;
  1137. case ChatType.GiftChatType:
  1138. item = GiftMsgItem(widget.msg.msgContent, false);
  1139. break;
  1140. case ChatType.FileChatType:
  1141. item = _receiveFileMsgItem();
  1142. break;
  1143. default:
  1144. }
  1145. return wrapItemWithMenu(item);
  1146. }
  1147. Widget _getReceivedMessageLayout(BuildContext context) {
  1148. bool hasHeadImg = true;
  1149. if (friendInfo.headimgurl == null || friendInfo.headimgurl.length == 0) {
  1150. hasHeadImg = false;
  1151. }
  1152. bool isShowSoundSate =
  1153. widget.msg.msgType == ChatType.ShortVoiceChatType.value &&
  1154. widget.msg.soundListened == 0;
  1155. return Row(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
  1156. Container(
  1157. margin: const EdgeInsets.only(right: 8.0),
  1158. child: InkWell(
  1159. child: ClipRRect(
  1160. borderRadius: BorderRadius.circular(8),
  1161. child: hasHeadImg
  1162. ? CachedNetworkImage(
  1163. imageUrl: friendInfo.headimgurl,
  1164. placeholder: (context, url) => Image.asset(
  1165. Constants.DefaultHeadImgUrl,
  1166. width: 40,
  1167. height: 40,
  1168. ),
  1169. width: 40,
  1170. height: 40,
  1171. )
  1172. : SizedBox(
  1173. width: 40,
  1174. height: 40,
  1175. child: Image.asset(R.assetsImagesDefaultNorAvatar))),
  1176. onTap: () {
  1177. AppNavigator.pushProfileInfoPage(context, friendInfo.userId,
  1178. fromWhere: 0);
  1179. },
  1180. )),
  1181. _reveiveMsg(context),
  1182. isShowSoundSate
  1183. ? Container(
  1184. margin: EdgeInsets.only(left: 8),
  1185. width: 40,
  1186. height: 40,
  1187. alignment: Alignment.centerLeft,
  1188. child: CircleAvatar(
  1189. radius: 3.5,
  1190. backgroundColor: Colors.red,
  1191. ))
  1192. : Container()
  1193. ]);
  1194. }
  1195. }