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.
 
 
 
 
 
 

653 lines
21 KiB

  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:chat/utils/upload_util.dart';
  4. import 'package:fixnum/fixnum.dart';
  5. import 'package:cached_network_image/cached_network_image.dart';
  6. import 'package:chat/chat/translate_state.dart';
  7. import 'package:chat/data/UserData.dart';
  8. import 'package:chat/data/chat_data_mgr.dart';
  9. import 'package:chat/data/constants.dart';
  10. import 'package:chat/generated/i18n.dart';
  11. import 'package:chat/home/add_friend.dart';
  12. import 'package:chat/models/ChatMsg.dart';
  13. import 'package:chat/models/UserInfo.dart';
  14. import 'package:chat/models/keyboard_provider.dart';
  15. import 'package:chat/models/ref_name_provider.dart';
  16. import 'package:chat/models/voucher_change.dart';
  17. import 'package:chat/proto/all.pbserver.dart';
  18. import 'package:chat/utils/CustomUI.dart';
  19. import 'package:chat/utils/HttpUtil.dart';
  20. import 'package:chat/utils/MessageMgr.dart';
  21. import 'package:chat/utils/TokenMgr.dart';
  22. import 'package:chat/utils/analyze_utils.dart';
  23. import 'package:chat/utils/app_navigator.dart';
  24. import 'package:chat/utils/blacklist_mgr.dart';
  25. import 'package:chat/utils/count_down_button.dart';
  26. import 'package:chat/utils/friend_list_mgr.dart';
  27. import 'package:chat/utils/msgHandler.dart';
  28. import 'package:chat/utils/net_state_widget.dart';
  29. import 'package:chat/utils/sound_util.dart';
  30. import 'package:chat/utils/sp_utils.dart';
  31. import 'package:chat/utils/sql_util.dart';
  32. import 'package:dio/dio.dart';
  33. import 'package:extended_text/extended_text.dart';
  34. import 'package:flutter/cupertino.dart';
  35. import 'package:flutter/material.dart';
  36. import 'package:flutter/services.dart';
  37. import 'package:oktoast/oktoast.dart';
  38. import 'package:provider/provider.dart';
  39. import '../r.dart';
  40. import 'ChatPageItem.dart';
  41. import 'input_bar.dart';
  42. import 'package:chat/utils/PopUpMenu.dart' as myPop;
  43. import 'package:chat/models/money_change.dart';
  44. class ChatPage extends StatefulWidget {
  45. final int friendId;
  46. final int enterType; // 0默认 1图片
  47. final dynamic enterContent;
  48. final bool isTranslateButler;
  49. ChatPage(
  50. {Key key,
  51. this.friendId,
  52. this.enterType = 0,
  53. this.enterContent,
  54. this.isTranslateButler = false})
  55. : super(key: key);
  56. _ChatPageState createState() => _ChatPageState();
  57. }
  58. class _ChatPageState extends State<ChatPage> {
  59. ScrollController _scrollCtrl = ScrollController();
  60. MessageMgr msgMgr = MessageMgr();
  61. UserInfo friendInfo;
  62. List<MsgModel> msgList;
  63. KeyboardIndexProvider _keyboardIndexProvider = KeyboardIndexProvider();
  64. TextEditingController nickNameController = new TextEditingController();
  65. //统计聊天时长
  66. int startTime;
  67. @override
  68. void dispose() {
  69. var endTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
  70. AnalyzeUtils.commitChatDuration(startTime, endTime);
  71. MessageMgr().off('Send CoinBag', _sendCoin);
  72. msgMgr.off('New Chat Message', receiveMsg);
  73. msgMgr.off('Keyboard Hide', dealWithKeyboardHide);
  74. msgMgr.off('Delete Select Message', _deleteItem);
  75. MsgHandler.curActiveSession = 0;
  76. SoundUtils().stop();
  77. nickNameController.dispose();
  78. _scrollCtrl.dispose();
  79. super.dispose();
  80. }
  81. @override
  82. void initState() {
  83. super.initState();
  84. print('init chatpage');
  85. getDefaultSetting();
  86. getUserInfo();
  87. startTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
  88. msgList = ChatDataMgr().getRecord();
  89. msgMgr.on('New Chat Message', receiveMsg);
  90. msgMgr.on('Keyboard Hide', dealWithKeyboardHide);
  91. msgMgr.on('Send CoinBag', _sendCoin);
  92. msgMgr.on('Delete Select Message', _deleteItem);
  93. }
  94. void _sendFile(File file) async {
  95. // File file = await FilePicker.getFile();
  96. int fileSize = file.lengthSync();
  97. print('选择的文件 ${file.path} 大小 $fileSize');
  98. if (fileSize > 33 * 1024 * 1024) {
  99. showToast(I18n.of(context).max_file.replaceFirst('/s1', 33.toString()));
  100. return;
  101. }
  102. var fileName = file.path.split('/').last;
  103. print('fileName $fileName');
  104. var ext = '';
  105. var extList = fileName.split('.');
  106. if (extList.length > 1) {
  107. ext = extList.last;
  108. }
  109. print('ext $ext');
  110. var fileMsg = FileChat.create();
  111. fileMsg.type = ext;
  112. fileMsg.size = fileSize;
  113. fileMsg.name = fileName;
  114. var msg = MsgHandler.createSendMsg(
  115. ChatType.FileChatType, fileMsg.writeToBuffer(),
  116. friendId: widget.friendId,
  117. localFile: file.path,
  118. channelType: ChatChannelType.Session);
  119. sendMsg(msg);
  120. }
  121. _sendCoin(args) async {
  122. var res = await _checkCoinBeforeSend(args['amount']);
  123. if (res != null) {
  124. args['redNo'] = res['redNo'];
  125. args['friendId'] = widget.friendId;
  126. var msg = MsgHandler.createCoinBagMsg(args);
  127. sendMsg(msg);
  128. }
  129. }
  130. //校验
  131. _checkCoinBeforeSend(int amount) async {
  132. Map data = {
  133. "userId": UserData().basicInfo.userId,
  134. "rUserId": friendInfo.userId,
  135. "price": amount,
  136. };
  137. data['sign'] = TokenMgr().getSign(data);
  138. Response res = await HttpUtil().post('red/packet/send', data: data);
  139. if (res == null) {
  140. return null;
  141. }
  142. Map resData = res.data;
  143. if (resData['code'] == 0) {
  144. print(resData['data']);
  145. return resData['data'];
  146. }
  147. return null;
  148. }
  149. void getDefaultSetting() async {
  150. bool soundPlayMode =
  151. (await SPUtils.getBool(Constants.SOUND_PLAY_MODE)) ?? false;
  152. _keyboardIndexProvider.init(soundPlayMode);
  153. }
  154. dealWithKeyboardHide(args) {
  155. if (_keyboardIndexProvider.curKeyboardIndex == 0) {
  156. hideKeyBoard();
  157. }
  158. }
  159. getUserInfo() async {
  160. //先从本地获取用户信息
  161. friendInfo = await HttpUtil().getFriendInfo(widget.friendId, true);
  162. //如果是新的聊天,向服务器发送创建会话消息
  163. if (msgList.length == 0) {
  164. MsgHandler.getActiveSesstion(
  165. [friendInfo.userId, UserData().basicInfo.userId]);
  166. }
  167. if (mounted) {
  168. setState(() {});
  169. }
  170. WidgetsBinding.instance.addPostFrameCallback((_) {
  171. if (widget.enterType == 1) {
  172. print('接收到的:${widget.enterContent}');
  173. _sendFile(File(widget.enterContent));
  174. } else if (widget.enterType == 2) {
  175. //转发消息
  176. MsgModel originMsg = widget.enterContent;
  177. MsgModel msg = MsgHandler.createSendMsg(
  178. ChatType.valueOf(originMsg.msgType), originMsg.msgContent);
  179. msg.extraInfo = originMsg.extraInfo;
  180. if(originMsg.extraFile.contains('http')){
  181. msg.extraFile = originMsg.extraFile;
  182. }else{
  183. msg.extraFile = UploadUtil().getFullUrl(originMsg.extraFile, originMsg.sessionId, originMsg.channelType);
  184. }
  185. msg.localFile = originMsg.localFile;
  186. msg.friendId = widget.friendId;
  187. if (msg.localFile != null) {
  188. msg.state = MsgState.Uploaded;
  189. }
  190. sendMsg(msg);
  191. }
  192. });
  193. }
  194. @override
  195. Widget build(BuildContext context) {
  196. print('build chatpage');
  197. if (friendInfo == null) {
  198. return Scaffold(
  199. backgroundColor: const Color(0xFFE2E9F1),
  200. body: SafeArea(
  201. child: Center(
  202. child: CircularProgressIndicator(),
  203. ),
  204. ),
  205. );
  206. }
  207. List<Widget> actions = [];
  208. int voucher = Provider.of<VoucherChangeProvider>(context).voucher;
  209. actions.add(Row(
  210. children: <Widget>[
  211. CustomUI.buildImageLabel("assets/images/voucher.png", voucher,
  212. imgOpc: 0.5, imgHeight: 13),
  213. CustomUI.buildImageLabel(
  214. R.assetsImagesCoin, Provider.of<MoneyChangeProvider>(context).money,
  215. isLeft: false)
  216. ],
  217. ));
  218. actions.add(TranslateSateWidget(friendId: friendInfo.userId));
  219. actions.add(Container(
  220. margin: EdgeInsets.only(top: 1),
  221. child: myPop.PopupMenuButton(
  222. icon: Icon(
  223. Icons.more_horiz,
  224. size: 24,
  225. ),
  226. offset: Offset(0, 100),
  227. onSelected: (int index) {
  228. if (index == 2) {
  229. AppNavigator.pushInformUserPage(
  230. context, friendInfo.sex == 1, friendInfo.userId);
  231. } else if (index == 1) {
  232. Navigator.of(context).push(
  233. new MaterialPageRoute(
  234. builder: (context) {
  235. return AddFriendPage(
  236. userId: friendInfo.userId,
  237. pageType: SendMessagePageType.Remark,
  238. originalName: Provider.of<RefNameProvider>(context)
  239. .getRefName(friendInfo.userId, friendInfo.nickName));
  240. },
  241. ),
  242. );
  243. }
  244. },
  245. itemBuilder: (BuildContext context) {
  246. return <myPop.PopupMenuItem<int>>[
  247. myPop.PopupMenuItem(
  248. child: Container(
  249. alignment: Alignment.center,
  250. color: Colors.white,
  251. padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
  252. child: Text(I18n.of(context).anonymous_report,
  253. textScaleFactor: 1.0,
  254. maxLines: 1,
  255. style: TextStyle(
  256. color: Color(AppColors.AppBarColor), fontSize: 12)),
  257. ),
  258. value: 2,
  259. ),
  260. myPop.PopupMenuItem(
  261. child: Container(
  262. decoration: BoxDecoration(
  263. border: Border(
  264. top: BorderSide(width: 1, color: Colors.grey[300])),
  265. color: Colors.white,
  266. ),
  267. alignment: Alignment.center,
  268. padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
  269. child: Text(I18n.of(context).Remark,
  270. textScaleFactor: 1.0,
  271. maxLines: 1,
  272. style: TextStyle(
  273. color: Color(AppColors.AppBarColor), fontSize: 12)),
  274. ),
  275. value: 1,
  276. ),
  277. ];
  278. })));
  279. return MultiProvider(
  280. providers: [
  281. ChangeNotifierProvider(create: (_) => _keyboardIndexProvider),
  282. Provider<bool>.value(value: false),
  283. Provider<int>.value(value: widget.friendId),
  284. ],
  285. child: GestureDetector(
  286. onTap: hideKeyBoard,
  287. child: ExtendedTextSelectionPointerHandler(
  288. ///选择文字,消除弹窗
  289. builder: (states) {
  290. return Listener(
  291. child: Scaffold(
  292. resizeToAvoidBottomInset: false,
  293. backgroundColor: const Color(0xFFE2E9F1),
  294. appBar: AppBar(
  295. title: Text(
  296. '${Provider.of<RefNameProvider>(context).getRefName(friendInfo.userId, friendInfo.nickName)}',
  297. textScaleFactor: 1.0,
  298. style: TextStyle(
  299. color: Constants.BlackTextColor,
  300. fontSize: 16.47),
  301. ),
  302. leading: CustomUI.buildCustomLeading(context),
  303. titleSpacing: -10,
  304. centerTitle: false,
  305. elevation: 1,
  306. actions: actions),
  307. body: SafeArea(
  308. child: Column(
  309. children: <Widget>[
  310. NetStateWidget(),
  311. widget.isTranslateButler
  312. ? _buildTranslationButler()
  313. : Container(),
  314. widget.isTranslateButler
  315. ? _buildServiceCard(true, () {})
  316. : Container(),
  317. widget.isTranslateButler
  318. ? _buildServiceCard(false, () {})
  319. : Container(),
  320. Expanded(child: _buildMessageList()),
  321. InputBar(sendMsg: sendMsg),
  322. ],
  323. ))),
  324. behavior: HitTestBehavior.translucent,
  325. onPointerDown: (value) {
  326. for (var state in states) {
  327. if (!state.containsPosition(value.position)) {
  328. //clear other selection
  329. state.clearSelection();
  330. }
  331. }
  332. },
  333. onPointerMove: (value) {
  334. //clear other selection
  335. for (var state in states) {
  336. if (!state.containsPosition(value.position)) {
  337. //clear other selection
  338. state.clearSelection();
  339. }
  340. }
  341. },
  342. );
  343. },
  344. )));
  345. }
  346. Widget _buildTranslationButler() {
  347. bool hasHeadImg = true;
  348. if (friendInfo.headimgurl == null || friendInfo.headimgurl.length == 0) {
  349. hasHeadImg = false;
  350. }
  351. /// 5H币/1分钟
  352. String coinTIme = I18n.of(context).translation_butler_coin_time;
  353. coinTIme = coinTIme.replaceAll('/s1', '5');
  354. coinTIme = coinTIme.replaceAll('/s2', '1');
  355. return Container(
  356. padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
  357. color: Colors.white,
  358. child: Row(
  359. children: <Widget>[
  360. ClipRRect(
  361. borderRadius: BorderRadius.circular(8),
  362. child: hasHeadImg
  363. ? CachedNetworkImage(
  364. imageUrl: friendInfo.headimgurl,
  365. placeholder: (context, url) => Image.asset(
  366. Constants.DefaultHeadImgUrl,
  367. width: 54,
  368. height: 54,
  369. ),
  370. width: 54,
  371. height: 54,
  372. )
  373. : SizedBox(
  374. width: 54,
  375. height: 54,
  376. child: Image.asset(R.assetsImagesDefaultNorAvatar))),
  377. Padding(
  378. padding: EdgeInsets.only(left: 10),
  379. child: Container(child: Column(
  380. crossAxisAlignment: CrossAxisAlignment.start,
  381. children: <Widget>[
  382. Text(
  383. I18n.of(context).translation_butler ,
  384. textScaleFactor: 1.0,
  385. overflow: TextOverflow.ellipsis,
  386. maxLines: 1,
  387. style: TextStyle(
  388. color: AppColors.NewAppbarTextColor, fontSize: 15),
  389. ),
  390. SizedBox(height: 5,),
  391. Text(
  392. coinTIme,
  393. textScaleFactor: 1.0,
  394. maxLines: 1,
  395. style: TextStyle(
  396. color: Color(0xFF797979), fontSize: 13),
  397. )
  398. ],
  399. ),constraints:BoxConstraints(maxWidth: 135),),
  400. ),
  401. Expanded(
  402. child: Container(
  403. constraints:BoxConstraints(maxWidth: 130),
  404. width: double.maxFinite,
  405. child: CountDownButton(
  406. I18n.of(context).translation_butler_end_service,
  407. () {
  408. Navigator.of(context).pop();
  409. },
  410. countDownTime: 360,
  411. align: Alignment.centerRight,
  412. onPress: () {},
  413. ),
  414. // alignment: Alignment(1,0),
  415. )),
  416. ],
  417. ),
  418. );
  419. }
  420. Widget _buildServiceCard(bool isStart, Function callBack) {
  421. return Container(
  422. margin: EdgeInsets.all(10),
  423. child: Card(
  424. elevation: 2,
  425. // 阴影
  426. shape: RoundedRectangleBorder(
  427. borderRadius: BorderRadius.circular(10),
  428. // side: BorderSide(color: Colors.green,width: 25),
  429. ),
  430. child: Padding(
  431. padding: EdgeInsets.only(left: 10, right: 10, top: 15, bottom: 15),
  432. child: Row(
  433. children: <Widget>[
  434. Column(
  435. crossAxisAlignment: CrossAxisAlignment.start,
  436. children: <Widget>[
  437. Text(
  438. isStart
  439. ? I18n.of(context).translation_butler_start_service
  440. : I18n.of(context).translation_butler_service_end,
  441. textScaleFactor: 1.0,
  442. style: TextStyle(
  443. color: AppColors.NewAppbarTextColor, fontSize: 15),
  444. ),
  445. Text(
  446. isStart
  447. ? I18n.of(context).translation_butler_start_tips
  448. : I18n.of(context).translation_butler_evaluation_tips,
  449. textScaleFactor: 1.0,
  450. style: TextStyle(
  451. color: AppColors.NewAppbarTextColor, fontSize: 13),
  452. )
  453. ],
  454. ),
  455. isStart
  456. ? Container()
  457. : Expanded(
  458. child: Container(
  459. margin: EdgeInsets.only(left: 15),
  460. height: 30,
  461. child: RaisedButton(
  462. color: Color(0xff3875E9),
  463. shape: RoundedRectangleBorder(
  464. borderRadius:
  465. BorderRadius.all(Radius.circular(10))),
  466. child: Text(
  467. I18n.of(context).translation_butler_evaluation,
  468. textScaleFactor: 1.0,
  469. style: TextStyle(color: Colors.white, fontSize: 15),
  470. ),
  471. onPressed: () {
  472. CustomUI.buildTranslationEvaluationDialog(context);
  473. }),
  474. )),
  475. ],
  476. ),
  477. ),
  478. ),
  479. );
  480. }
  481. Widget _buildMessageList() {
  482. return Container(
  483. alignment: Alignment.topCenter,
  484. child: msgList.length == 0
  485. ? Padding(
  486. padding: EdgeInsets.all(8),
  487. child: Text(
  488. I18n.of(context).chat_tips,
  489. textAlign: TextAlign.center,
  490. textScaleFactor: 1.0,
  491. style: TextStyle(color: Colors.grey, fontSize: 12),
  492. ))
  493. : NotificationListener(
  494. child: Scrollbar(
  495. child: ListView.builder(
  496. reverse: true,
  497. shrinkWrap: true,
  498. itemCount: msgList.length,
  499. controller: _scrollCtrl,
  500. padding: EdgeInsets.all(8.0),
  501. itemBuilder: _buildItem,
  502. )),
  503. onNotification: (notification) {
  504. if (notification is ScrollNotification) {
  505. // var offset = notification.metrics.pixels;
  506. // print('滚动事件 offset $offset');
  507. }
  508. return true;
  509. },
  510. ),
  511. );
  512. }
  513. hideKeyBoard() {
  514. _keyboardIndexProvider.changeSelectIndex(-1);
  515. }
  516. readOnly() {
  517. _keyboardIndexProvider.changeReadOnlyKey(true);
  518. }
  519. sendMsg(MsgModel msg) {
  520. print('对方是否拉黑你 ${friendInfo.isBlackened}');
  521. if (BlacklistMgr.isBlack(friendInfo.userId)) {
  522. return;
  523. }
  524. if (!friendInfo.isCanStrangerNews &&
  525. !FriendListMgr().isMyFriend(friendInfo.userId)) {
  526. showToast(I18n.of(context).stranger_close_tips);
  527. return;
  528. }
  529. MsgHandler.insertMsgToDB(msg);
  530. MsgHandler.sendChatMsg(msg);
  531. if (mounted) {
  532. setState(() {});
  533. if (_scrollCtrl.hasClients) {
  534. _scrollCtrl.animateTo(0,
  535. duration: new Duration(milliseconds: 500), curve: Curves.ease);
  536. }
  537. }
  538. // testBig(msg);
  539. }
  540. MsgModel msg;
  541. int count=0;
  542. testBig(MsgModel msg)async{
  543. for(int k=0;k<100;k++){
  544. msg.msgContent = utf8.encode('测试$count');
  545. Int64 time = Int64((DateTime.now()).millisecondsSinceEpoch);
  546. msg.time = time.toInt();
  547. MsgHandler.insertMsgToDB(msg);
  548. MsgHandler.sendChatMsg(msg);
  549. await Future.delayed(Duration(milliseconds: 500),(){});
  550. count++;
  551. }
  552. count=0;
  553. print('发送完毕');
  554. showToast('发送完毕');
  555. }
  556. void receiveMsg(args) {
  557. if (mounted) {
  558. setState(() {});
  559. if (_scrollCtrl.hasClients) {
  560. _scrollCtrl.animateTo(0,
  561. duration: new Duration(milliseconds: 500), curve: Curves.ease);
  562. }
  563. }
  564. }
  565. _deleteItem(msg) {
  566. MessageMgr().emit('Cancel Request', msg);
  567. print('#### 开始删除--');
  568. msgList.remove(msg);
  569. setState(() {});
  570. SqlUtil().deleteSigleRecordWith(msg.sessionId, msg.time);
  571. }
  572. Widget _buildItem(BuildContext context, int index) {
  573. var lastMsgTime;
  574. if (index < msgList.length - 1) {
  575. lastMsgTime = msgList[index + 1].time;
  576. }
  577. MsgModel msg = msgList[index];
  578. return ChatPageItem(
  579. key: Key(msg.time.toString()),
  580. msg: msg,
  581. hideKeyboard: readOnly,
  582. friendInfo: friendInfo,
  583. lastMsgTime: lastMsgTime);
  584. }
  585. }