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.
 
 
 
 
 
 

641 line
21 KiB

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