Hibok
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

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