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.
 
 
 
 
 
 

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