Hibok
您不能選擇超過 %s 個話題 話題必須以字母或數字為開頭,可包含連接號 ('-') 且最長為 35 個字
 
 
 
 
 
 

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