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.
 
 
 
 
 
 

1369 line
43 KiB

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