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.
 
 
 
 
 
 

650 line
22 KiB

  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:chat/chat/group_chat_item.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/data/group_data_mgr.dart';
  8. import 'package:chat/generated/i18n.dart';
  9. import 'package:chat/home/group_setting.dart';
  10. import 'package:chat/models/ChatMsg.dart';
  11. import 'package:chat/models/group_info_model.dart';
  12. import 'package:chat/models/keyboard_provider.dart';
  13. import 'package:chat/models/money_change.dart';
  14. import 'package:chat/models/voucher_change.dart';
  15. import 'package:chat/proto/all.pbserver.dart';
  16. import 'package:chat/utils/CustomUI.dart';
  17. import 'package:chat/utils/MessageMgr.dart';
  18. import 'package:chat/utils/analyze_utils.dart';
  19. import 'package:chat/utils/msgHandler.dart';
  20. import 'package:chat/utils/net_state_widget.dart';
  21. import 'package:chat/utils/screen.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:chat/utils/upload_util.dart';
  26. import 'package:extended_text/extended_text.dart';
  27. import 'package:flutter/cupertino.dart';
  28. import 'package:flutter/material.dart';
  29. import 'package:oktoast/oktoast.dart';
  30. import 'package:provider/provider.dart';
  31. import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
  32. import '../r.dart';
  33. import 'input_bar.dart';
  34. import 'package:chat/models/ref_name_provider.dart';
  35. import 'package:fixnum/fixnum.dart';
  36. class GroupChatPage extends StatefulWidget {
  37. final GroupInfoModel groupInfoModel;
  38. final int enterType; // 0默认 1图片
  39. final dynamic enterContent;
  40. GroupChatPage(
  41. {Key key, this.groupInfoModel, this.enterType = 0, this.enterContent})
  42. : super(key: key);
  43. _GroupChatPageState createState() => _GroupChatPageState();
  44. }
  45. class _GroupChatPageState extends State<GroupChatPage> {
  46. final ItemScrollController itemScrollController = ItemScrollController();
  47. final ItemPositionsListener itemPositionListener =
  48. ItemPositionsListener.create();
  49. MessageMgr msgMgr = MessageMgr();
  50. List<MsgModel> msgList;
  51. KeyboardIndexProvider _keyboardIndexProvider = KeyboardIndexProvider();
  52. TextEditingController nickNameController = new TextEditingController();
  53. //统计聊天时长
  54. int startTime;
  55. //子元素的对应偏移量
  56. Map itemOffsetMap = {};
  57. //未读消息数目
  58. int unreadNums = 0;
  59. //最上一条未读消息id
  60. int unreadTime;
  61. //@消息索引
  62. int alterTime;
  63. String alterUserName = '';
  64. @override
  65. void dispose() {
  66. var endTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
  67. AnalyzeUtils.commitChatDuration(startTime, endTime);
  68. msgMgr.off('New Chat Message', receiveMsg);
  69. msgMgr.off('Keyboard Hide', dealWithKeyboardHide);
  70. msgMgr.off('Update Group Info', updateGroupInfo);
  71. msgMgr.off('Delete Select Message', _deleteItem);
  72. MsgHandler.curActiveSession = 0;
  73. SoundUtils().stop();
  74. super.dispose();
  75. }
  76. @override
  77. void initState() {
  78. super.initState();
  79. print('init group chat page ${widget.groupInfoModel.sessionId}');
  80. getDefaultSetting();
  81. startTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
  82. unreadNums = ChatDataMgr()
  83. .groupUnreadProvider
  84. .getUnreadCount(widget.groupInfoModel.sessionId);
  85. alterTime = ChatDataMgr()
  86. .groupUnreadProvider
  87. .getHavaAltertime(widget.groupInfoModel.sessionId);
  88. MsgHandler.updateActiveSesstion(widget.groupInfoModel.sessionId,
  89. isGroup: true);
  90. msgList = ChatDataMgr().getGroupRecord();
  91. if (unreadNums >= 10) {
  92. unreadTime = msgList[unreadNums - 1].time;
  93. }
  94. for (int i = 0; i < msgList.length; i++) {
  95. if (msgList[i].time == alterTime) {
  96. alterUserName =
  97. widget.groupInfoModel.getMember(msgList[i].friendId).nickName;
  98. break;
  99. }
  100. }
  101. msgMgr.on('New Chat Message', receiveMsg);
  102. msgMgr.on('Keyboard Hide', dealWithKeyboardHide);
  103. msgMgr.on('Update Group Info', updateGroupInfo);
  104. msgMgr.on('Delete Select Message', _deleteItem);
  105. WidgetsBinding.instance.addPostFrameCallback((_) {
  106. if (widget.enterType == 1) {
  107. print('接收到的:${widget.enterContent}');
  108. _sendFile(File(widget.enterContent));
  109. } else if (widget.enterType == 2) {
  110. //转发消息
  111. MsgModel originMsg = widget.enterContent;
  112. MsgModel msg = MsgHandler.createSendMsg(
  113. ChatType.valueOf(originMsg.msgType), originMsg.msgContent,
  114. channelType: ChatChannelType.Group);
  115. msg.extraInfo = originMsg.extraInfo;
  116. // msg.extraFile = originMsg.extraFile;
  117. if (originMsg.extraFile == null ||
  118. originMsg.extraFile.contains('http')) {
  119. msg.extraFile = originMsg.extraFile;
  120. } else {
  121. msg.extraFile = UploadUtil().getFullUrl(
  122. originMsg.extraFile, originMsg.sessionId, originMsg.channelType);
  123. }
  124. msg.localFile = originMsg.localFile;
  125. if (msg.localFile != null) {
  126. msg.state = MsgState.Uploaded;
  127. }
  128. sendMsg(msg);
  129. }
  130. });
  131. }
  132. void _sendFile(File file) async {
  133. // File file = await FilePicker.getFile();
  134. int fileSize = file.lengthSync();
  135. print('选择的文件 ${file.path} 大小 $fileSize');
  136. if (fileSize > 33 * 1024 * 1024) {
  137. showToast('文件大于33M');
  138. return;
  139. }
  140. var fileName = file.path.split('/').last;
  141. print('fileName $fileName');
  142. var ext = '';
  143. var extList = fileName.split('.');
  144. if (extList.length > 1) {
  145. ext = extList.last;
  146. }
  147. print('ext $ext');
  148. var fileMsg = FileChat.create();
  149. fileMsg.type = ext;
  150. fileMsg.size = fileSize;
  151. fileMsg.name = fileName;
  152. var msg = MsgHandler.createSendMsg(
  153. ChatType.FileChatType, fileMsg.writeToBuffer(),
  154. friendId: 0, localFile: file.path, channelType: ChatChannelType.Group);
  155. sendMsg(msg);
  156. }
  157. updateGroupInfo(args) {
  158. print('更新群信息');
  159. if (mounted) {
  160. setState(() {});
  161. }
  162. }
  163. void getDefaultSetting() async {
  164. bool soundPlayMode =
  165. (await SPUtils.getBool(Constants.SOUND_PLAY_MODE)) ?? false;
  166. _keyboardIndexProvider.init(soundPlayMode);
  167. }
  168. dealWithKeyboardHide(args) {
  169. if (_keyboardIndexProvider.curKeyboardIndex == 0) {
  170. readOnly();
  171. }
  172. }
  173. @override
  174. Widget build(BuildContext context) {
  175. List<Widget> actions = [];
  176. actions.add(Row(
  177. children: <Widget>[
  178. CustomUI.buildImageLabel("assets/images/voucher.png",
  179. Provider.of<VoucherChangeProvider>(context).voucher,
  180. imgOpc: 0.5, imgHeight: 13),
  181. CustomUI.buildImageLabel(
  182. R.assetsImagesCoin, Provider.of<MoneyChangeProvider>(context).money,
  183. isLeft: false)
  184. ],
  185. ));
  186. actions.add(widget.groupInfoModel.isInGroup
  187. ? IconButton(
  188. icon: Icon(Icons.more_horiz),
  189. iconSize: 22,
  190. onPressed: () {
  191. //进入群管理界面
  192. hideKeyBoard();
  193. Navigator.of(context).push(
  194. new MaterialPageRoute(
  195. builder: (context) {
  196. return GroupSetting(
  197. groupInfoModel: widget.groupInfoModel,
  198. );
  199. },
  200. ),
  201. );
  202. },
  203. )
  204. : IconButton(
  205. icon: Icon(Icons.delete),
  206. iconSize: 22,
  207. color: Colors.redAccent,
  208. onPressed: () {
  209. //进入群管理界面
  210. quiteGroup();
  211. },
  212. ));
  213. Map refMap = Provider.of<RefNameProvider>(context).refMap;
  214. bool isHaveUnreadNews = unreadTime !=null;
  215. bool isAlterYou = alterTime != null;
  216. return MultiProvider(
  217. providers: [
  218. ChangeNotifierProvider(create: (_) => _keyboardIndexProvider),
  219. Provider<bool>.value(value: true),
  220. Provider<GroupInfoModel>.value(value: widget.groupInfoModel),
  221. ],
  222. child: GestureDetector(
  223. onTap: hideKeyBoard,
  224. child: ExtendedTextSelectionPointerHandler(
  225. ///选择文字,消除弹窗
  226. builder: (states) {
  227. return Listener(
  228. child: Scaffold(
  229. resizeToAvoidBottomInset: false,
  230. backgroundColor: const Color(0xFFE2E9F1),
  231. appBar: AppBar(
  232. backgroundColor: AppColors.NewAppbarBgColor,
  233. title: Text(
  234. widget.groupInfoModel.getGroupName(refMap),
  235. textScaleFactor: 1.0,
  236. style: TextStyle(
  237. color: Constants.BlackTextColor,
  238. fontSize: 16.47),
  239. ),
  240. leading: CustomUI.buildCustomLeading(context),
  241. titleSpacing: -10,
  242. elevation: 1,
  243. centerTitle: false,
  244. actions: actions),
  245. body: SafeArea(
  246. child: Stack(
  247. children: <Widget>[
  248. Column(
  249. children: <Widget>[
  250. NetStateWidget(),
  251. Expanded(child: _buildMessageList()),
  252. InputBar(sendMsg: sendMsg),
  253. ],
  254. ),
  255. isHaveUnreadNews
  256. ? Positioned(
  257. top: 32.5,
  258. right: 0,
  259. child: InkWell(
  260. onTap: () {
  261. var screenItemNums =
  262. itemPositionListener
  263. .itemPositions.value.length;
  264. int jumIndex = 0;
  265. if (isAlterYou) {
  266. for (int i = 0;
  267. i < msgList.length;
  268. i++) {
  269. if (alterTime == msgList[i].time) {
  270. jumIndex = i - screenItemNums + 2;
  271. break;
  272. }
  273. }
  274. } else {
  275. for (int i = 0;
  276. i < msgList.length;
  277. i++) {
  278. if (unreadTime == msgList[i].time) {
  279. jumIndex = i - screenItemNums + 2;
  280. break;
  281. }
  282. }
  283. }
  284. itemScrollController.jumpTo(
  285. index: jumIndex);
  286. unreadTime = null;
  287. setState(() {});
  288. },
  289. child: Container(
  290. alignment: Alignment.center,
  291. decoration: BoxDecoration(
  292. boxShadow: [
  293. BoxShadow(
  294. color: Color(0x33000000), //阴影颜色
  295. blurRadius: 10.0, //阴影大小
  296. )
  297. ],
  298. borderRadius: BorderRadius.only(
  299. topLeft: Radius.circular(80),
  300. bottomLeft: Radius.circular(80)),
  301. color: Colors.white,
  302. ),
  303. constraints:
  304. BoxConstraints(minWidth: 120),
  305. height: 39,
  306. child: Row(
  307. children: <Widget>[
  308. Icon(
  309. Icons.file_upload,
  310. color: Color(0xFF3875E9),
  311. size: 20,
  312. ),
  313. Text(
  314. isAlterYou
  315. ? '$alterUserName @你'
  316. : '$unreadNums条新消息',
  317. style: TextStyle(
  318. color: Color(0xFF3875E9)),
  319. )
  320. ],
  321. ),
  322. )))
  323. : Container()
  324. ],
  325. ))),
  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. }
  347. //更新各个子元素的偏移位置
  348. _updateMsgItemOffset() {
  349. if (msgList.length == 0) {
  350. return;
  351. }
  352. var myId = UserData().basicInfo.userId;
  353. double offset = 0;
  354. for (var i = 0; i < msgList.length; i++) {
  355. MsgModel msg = msgList[i];
  356. double itemHeight = 70;
  357. switch (ChatType.valueOf(msg.msgType)) {
  358. case ChatType.TextChatType:
  359. //if (msg.from == myId) {
  360. var text = utf8.decode(msg.msgContent);
  361. itemHeight = 21 + _getTextHeight(text);
  362. //} else {}
  363. break;
  364. case ChatType.ShortVoiceChatType:
  365. if (msg.from == myId) {
  366. itemHeight = 22.5 + 24;
  367. } else {}
  368. break;
  369. case ChatType.ImageChatType:
  370. itemHeight = _getImgHeight(msg);
  371. break;
  372. case ChatType.ShortVideoChatType:
  373. itemHeight = _getImgHeight(msg);
  374. break;
  375. case ChatType.EmoticonType:
  376. itemHeight = 40;
  377. break;
  378. case ChatType.RedWalletChatType:
  379. print('红包消息');
  380. itemHeight = 70;
  381. break;
  382. case ChatType.PlaceChatType:
  383. itemHeight = 100 + 40.0;
  384. break;
  385. case ChatType.GroupChatNoticeType:
  386. itemHeight = 40;
  387. break;
  388. case ChatType.GiftChatType:
  389. itemHeight = 40;
  390. break;
  391. case ChatType.FileChatType:
  392. itemHeight = 80;
  393. break;
  394. default:
  395. }
  396. itemOffsetMap[i] = offset;
  397. offset += itemHeight;
  398. }
  399. }
  400. double _getTextHeight(String text) {
  401. var tp = TextPainter(
  402. text: TextSpan(style: TextStyle(fontSize: 15), text: text),
  403. textAlign: TextAlign.left,
  404. textDirection: TextDirection.ltr,
  405. textScaleFactor: 1,
  406. );
  407. tp.layout(maxWidth: Screen.width - 140);
  408. return tp.height;
  409. }
  410. double _getImgHeight(MsgModel msg) {
  411. double aspectRatio = msg.extraInfo / 100;
  412. var maxWidth = Screen.width * 0.65;
  413. var maxHeight = Screen.height / 4;
  414. double height;
  415. if (maxWidth / maxHeight > aspectRatio) {
  416. height = maxHeight;
  417. } else {
  418. height = maxWidth / aspectRatio;
  419. }
  420. return height;
  421. }
  422. Widget _buildMessageList() {
  423. return Container(
  424. alignment: Alignment.topCenter,
  425. child: msgList.length == 0
  426. ? Padding(
  427. padding: EdgeInsets.all(10),
  428. child: Text(
  429. I18n.of(context).chat_tips,
  430. textAlign: TextAlign.center,
  431. textScaleFactor: 1.0,
  432. style: TextStyle(color: Colors.grey),
  433. ))
  434. : Scrollbar(
  435. child: ScrollablePositionedList.builder(
  436. physics: new ClampingScrollPhysics(),
  437. itemCount: msgList.length,
  438. itemBuilder: _buildItem,
  439. itemScrollController: itemScrollController,
  440. itemPositionsListener: itemPositionListener,
  441. padding: EdgeInsets.all(8.0),
  442. reverse: true,
  443. hitCallback: hideKeyBoard,
  444. )),
  445. );
  446. }
  447. hideKeyBoard() {
  448. _keyboardIndexProvider.changeSelectIndex(-1);
  449. }
  450. readOnly() {
  451. _keyboardIndexProvider.changeReadOnlyKey(true);
  452. }
  453. sendMsg(MsgModel msg) {
  454. if (!widget.groupInfoModel.isInGroup) {
  455. //如果不在该群
  456. showToast(I18n.of(context).not_in_group);
  457. return;
  458. }
  459. MsgHandler.insertMsgToDB(msg);
  460. MsgHandler.sendChatMsg(msg);
  461. if (mounted) {
  462. setState(() {});
  463. }
  464. itemScrollController.jumpTo(index: 0);
  465. }
  466. MsgModel msg;
  467. int count = 0;
  468. testBig(MsgModel msg) async {
  469. for (int k = 0; k < 100; k++) {
  470. msg.msgContent = utf8.encode('测试$count');
  471. Int64 time = Int64((DateTime.now()).millisecondsSinceEpoch);
  472. msg.time = time.toInt();
  473. MsgHandler.insertMsgToDB(msg);
  474. MsgHandler.sendChatMsg(msg);
  475. await Future.delayed(Duration(milliseconds: 300), () {});
  476. count++;
  477. }
  478. count = 0;
  479. print('攻击完毕');
  480. showToast('攻击完毕');
  481. }
  482. void receiveMsg(args) {
  483. print("msgList.length: ${msgList.length}");
  484. if (args != widget.groupInfoModel.sessionId) {
  485. return;
  486. }
  487. if (mounted) {
  488. setState(() {});
  489. }
  490. }
  491. _deleteItem(msg) {
  492. MessageMgr().emit('Cancel Request', msg);
  493. print('#### 开始删除--');
  494. msgList.remove(msg);
  495. setState(() {});
  496. SqlUtil().deleteSigleRecordWith(msg.sessionId, msg.time);
  497. }
  498. Widget _buildItem(BuildContext context, int index) {
  499. var lastMsgTime;
  500. if (index < msgList.length - 1) {
  501. lastMsgTime = msgList[index].time;
  502. }
  503. MsgModel msg = msgList[index];
  504. if (msg.from == 0) {
  505. return GroupChatPageItem(
  506. key: Key(msg.time.toString()), msg: msg, lastMsgTime: lastMsgTime);
  507. } else {
  508. return GroupChatPageItem(
  509. key: Key(msg.time.toString()),
  510. msg: msg,
  511. memberModel: widget.groupInfoModel.getMember(msg.from),
  512. hideKeyboard: readOnly,
  513. lastMsgTime: lastMsgTime);
  514. }
  515. }
  516. quiteGroup() {
  517. var function = () {
  518. GroupInfoMgr().deleteGroup(widget.groupInfoModel.sessionId);
  519. MessageMgr().emit('Quit Group', widget.groupInfoModel.sessionId);
  520. Navigator.of(context).popUntil(ModalRoute.withName('/main'));
  521. MsgHandler.quitGroup(widget.groupInfoModel.sessionId);
  522. };
  523. showModalBottomSheet(
  524. context: context,
  525. backgroundColor: Colors.transparent,
  526. builder: (BuildContext context) {
  527. return Container(
  528. decoration: BoxDecoration(
  529. color: Colors.white,
  530. borderRadius: BorderRadius.only(
  531. topLeft: Radius.circular(13), topRight: Radius.circular(13))),
  532. height: 225,
  533. child: Column(
  534. crossAxisAlignment: CrossAxisAlignment.center,
  535. mainAxisAlignment: MainAxisAlignment.center,
  536. children: <Widget>[
  537. Padding(
  538. padding: EdgeInsets.fromLTRB(15, 15, 15, 13),
  539. child: Text(
  540. I18n.of(context).quit_group_tips,
  541. textScaleFactor: 1.0,
  542. style: TextStyle(fontSize: 12, color: Color(0xff777777)),
  543. ),
  544. ),
  545. Divider(
  546. color: Color(0xffE5E5E5),
  547. ),
  548. InkWell(
  549. onTap: function,
  550. child: Container(
  551. height: 60,
  552. alignment: Alignment.center,
  553. child: Text(I18n.of(context).determine,
  554. textScaleFactor: 1.0,
  555. style: TextStyle(
  556. fontSize: 18, color: Constants.ConfrimButtonColor)),
  557. ),
  558. ),
  559. Container(
  560. color: Color(0xffF2F2F2),
  561. height: 4,
  562. ),
  563. InkWell(
  564. onTap: () {
  565. Navigator.of(context).pop();
  566. },
  567. child: Container(
  568. height: 60,
  569. alignment: Alignment.center,
  570. child: Text(I18n.of(context).cancel,
  571. textScaleFactor: 1.0,
  572. style: TextStyle(fontSize: 18, color: Color(0xff4B4B4B))),
  573. ),
  574. )
  575. ],
  576. ),
  577. );
  578. },
  579. );
  580. }
  581. }