Hibok
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

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