Hibok
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

710 linhas
22 KiB

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