Hibok
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 
 

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