Hibok
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

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