Hibok
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

1397 行
44 KiB

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