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.
 
 
 
 
 
 

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