Hibok
您不能選擇超過 %s 個話題 話題必須以字母或數字為開頭,可包含連接號 ('-') 且最長為 35 個字
 
 
 
 
 
 

617 行
20 KiB

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