Hibok
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 
 

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