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

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