Hibok
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

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