Hibok
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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