Hibok
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1322 lines
42 KiB

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