Hibok
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

541 lines
19 KiB

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