Hibok
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

1439 wiersze
46 KiB

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