Hibok
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 
 

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