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.
 
 
 
 
 
 

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