Hibok
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 
 

620 行
21 KiB

  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:chat/chat/group_chat_item.dart';
  4. import 'package:chat/data/chat_data_mgr.dart';
  5. import 'package:chat/data/constants.dart';
  6. import 'package:chat/data/group_data_mgr.dart';
  7. import 'package:chat/generated/i18n.dart';
  8. import 'package:chat/home/group_setting.dart';
  9. import 'package:chat/models/ChatMsg.dart';
  10. import 'package:chat/models/group_info_model.dart';
  11. import 'package:chat/models/keyboard_provider.dart';
  12. import 'package:chat/models/money_change.dart';
  13. import 'package:chat/models/voucher_change.dart';
  14. import 'package:chat/proto/all.pbserver.dart';
  15. import 'package:chat/utils/CustomUI.dart';
  16. import 'package:chat/utils/MessageMgr.dart';
  17. import 'package:chat/utils/analyze_utils.dart';
  18. import 'package:chat/utils/msgHandler.dart';
  19. import 'package:chat/utils/net_state_widget.dart';
  20. import 'package:chat/utils/screen.dart';
  21. import 'package:chat/utils/sound_util.dart';
  22. import 'package:chat/utils/sp_utils.dart';
  23. import 'package:chat/utils/sql_util.dart';
  24. import 'package:chat/utils/upload_util.dart';
  25. import 'package:extended_text/extended_text.dart';
  26. import 'package:flutter/cupertino.dart';
  27. import 'package:flutter/material.dart';
  28. import 'package:oktoast/oktoast.dart';
  29. import 'package:provider/provider.dart';
  30. import 'package:scroll_to_index/scroll_to_index.dart';
  31. import '../r.dart';
  32. import 'input_bar.dart';
  33. import 'package:chat/models/ref_name_provider.dart';
  34. import 'package:fixnum/fixnum.dart';
  35. class GroupChatPage extends StatefulWidget {
  36. final GroupInfoModel groupInfoModel;
  37. final int enterType; // 0默认 1图片
  38. final dynamic enterContent;
  39. GroupChatPage(
  40. {Key key, this.groupInfoModel, this.enterType = 0, this.enterContent})
  41. : super(key: key);
  42. _GroupChatPageState createState() => _GroupChatPageState();
  43. }
  44. class _GroupChatPageState extends State<GroupChatPage> {
  45. AutoScrollController controller;
  46. MessageMgr msgMgr = MessageMgr();
  47. List<MsgModel> msgList;
  48. KeyboardIndexProvider _keyboardIndexProvider = KeyboardIndexProvider();
  49. TextEditingController nickNameController = new TextEditingController();
  50. //统计聊天时长
  51. int startTime;
  52. //子元素的对应偏移量
  53. Map itemOffsetMap = {};
  54. //未读消息数目
  55. int unreadNums = 0;
  56. //最上一条未读消息id
  57. int unreadTime;
  58. //@消息索引
  59. int alterTime;
  60. String alterUserName = '';
  61. int jumpTime;
  62. @override
  63. void dispose() {
  64. var endTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
  65. AnalyzeUtils.commitChatDuration(startTime, endTime);
  66. msgMgr.off('New Chat Message', receiveMsg);
  67. msgMgr.off('Keyboard Hide', dealWithKeyboardHide);
  68. msgMgr.off('Update Group Info', updateGroupInfo);
  69. msgMgr.off('Delete Select Message', _deleteItem);
  70. msgMgr.off('Jump to Msg', jumpToMsg);
  71. MsgHandler.curActiveSession = 0;
  72. SoundUtils().stop();
  73. super.dispose();
  74. }
  75. @override
  76. void initState() {
  77. super.initState();
  78. print('init group chat page ${widget.groupInfoModel.sessionId}');
  79. getDefaultSetting();
  80. controller = AutoScrollController(
  81. viewportBoundaryGetter: () =>
  82. Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
  83. axis: Axis.vertical);
  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 && unreadNums <= msgList.length) {
  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. controller.addListener(() {
  136. if (alterTime != null || unreadTime != null) {
  137. for (int i = 0; i < msgList.length; i++) {
  138. if ((msgList[i].time == alterTime || msgList[i].time == unreadTime) &&
  139. controller.isIndexStateInLayoutRange(i)) {
  140. clearUnreadNews();
  141. }
  142. }
  143. }
  144. });
  145. }
  146. jumpToMsg(time) async {
  147. hideKeyBoard();
  148. int jumIndex = 0;
  149. jumpTime = time;
  150. for (int i = 0; i < msgList.length; i++) {
  151. if (time == msgList[i].time) {
  152. jumIndex = i;
  153. break;
  154. }
  155. }
  156. if (jumIndex < 0) {
  157. jumIndex = 0;
  158. }
  159. if (jumIndex > msgList.length - 1) {
  160. jumIndex = msgList.length - 1;
  161. }
  162. await controller.scrollToIndex(jumIndex,
  163. preferPosition: AutoScrollPosition.middle);
  164. controller.highlight(jumIndex,
  165. highlightDuration: new Duration(milliseconds: 100));
  166. }
  167. void _sendFile(File file) async {
  168. // File file = await FilePicker.getFile();
  169. int fileSize = file.lengthSync();
  170. print('选择的文件 ${file.path} 大小 $fileSize');
  171. if (fileSize > 33 * 1024 * 1024) {
  172. showToast('文件大于33M');
  173. return;
  174. }
  175. var fileName = file.path.split('/').last;
  176. print('fileName $fileName');
  177. var ext = '';
  178. var extList = fileName.split('.');
  179. if (extList.length > 1) {
  180. ext = extList.last;
  181. }
  182. print('ext $ext');
  183. var fileMsg = FileChat.create();
  184. fileMsg.type = ext;
  185. fileMsg.size = fileSize;
  186. fileMsg.name = fileName;
  187. var msg = MsgHandler.createSendMsg(
  188. ChatType.FileChatType, fileMsg.writeToBuffer(),
  189. friendId: 0, localFile: file.path, channelType: ChatChannelType.Group);
  190. sendMsg(msg);
  191. }
  192. updateGroupInfo(args) {
  193. print('更新群信息');
  194. if (mounted) {
  195. setState(() {});
  196. }
  197. }
  198. void getDefaultSetting() async {
  199. bool soundPlayMode =
  200. (await SPUtils.getBool(Constants.SOUND_PLAY_MODE)) ?? false;
  201. _keyboardIndexProvider.init(soundPlayMode);
  202. }
  203. dealWithKeyboardHide(args) {
  204. if (_keyboardIndexProvider.curKeyboardIndex == 0) {
  205. readOnly();
  206. }
  207. }
  208. clearUnreadNews() {
  209. unreadTime = null;
  210. setState(() {});
  211. }
  212. @override
  213. Widget build(BuildContext context) {
  214. List<Widget> actions = [];
  215. actions.add(Row(
  216. children: <Widget>[
  217. CustomUI.buildImageLabel("assets/images/voucher.png",
  218. Provider.of<VoucherChangeProvider>(context).voucher,
  219. imgOpc: 0.5, imgHeight: 13),
  220. CustomUI.buildImageLabel(
  221. R.assetsImagesCoin, Provider.of<MoneyChangeProvider>(context).money,
  222. isLeft: false)
  223. ],
  224. ));
  225. actions.add(widget.groupInfoModel.isInGroup
  226. ? IconButton(
  227. icon: Icon(Icons.more_horiz),
  228. iconSize: 22,
  229. onPressed: () {
  230. //进入群管理界面
  231. hideKeyBoard();
  232. Navigator.of(context).push(
  233. new MaterialPageRoute(
  234. builder: (context) {
  235. return GroupSetting(
  236. groupInfoModel: widget.groupInfoModel,
  237. );
  238. },
  239. ),
  240. );
  241. },
  242. )
  243. : IconButton(
  244. icon: Icon(Icons.delete),
  245. iconSize: 22,
  246. color: Colors.redAccent,
  247. onPressed: () {
  248. //进入群管理界面
  249. quiteGroup();
  250. },
  251. ));
  252. Map refMap = Provider.of<RefNameProvider>(context).refMap;
  253. bool isHaveUnreadNews = unreadTime != null;
  254. bool isAlterYou = alterTime != null;
  255. return MultiProvider(
  256. providers: [
  257. ChangeNotifierProvider(create: (_) => _keyboardIndexProvider),
  258. Provider<bool>.value(value: true),
  259. Provider<GroupInfoModel>.value(value: widget.groupInfoModel),
  260. ],
  261. child: GestureDetector(
  262. onTap: hideKeyBoard,
  263. child: ExtendedTextSelectionPointerHandler(
  264. ///选择文字,消除弹窗
  265. builder: (states) {
  266. return Listener(
  267. child: Scaffold(
  268. resizeToAvoidBottomInset: false,
  269. backgroundColor: const Color(0xFFE2E9F1),
  270. appBar: AppBar(
  271. backgroundColor: AppColors.NewAppbarBgColor,
  272. title: Text(
  273. widget.groupInfoModel.getGroupName(refMap),
  274. textScaleFactor: 1.0,
  275. style: TextStyle(
  276. color: Constants.BlackTextColor,
  277. fontSize: 16.47),
  278. ),
  279. leading: CustomUI.buildCustomLeading(context),
  280. titleSpacing: -10,
  281. elevation: 1,
  282. centerTitle: false,
  283. actions: actions),
  284. body: SafeArea(
  285. child: Stack(
  286. children: <Widget>[
  287. Column(
  288. children: <Widget>[
  289. NetStateWidget(),
  290. Expanded(child: _buildMessageList()),
  291. InputBar(sendMsg: sendMsg),
  292. ],
  293. ),
  294. isHaveUnreadNews
  295. ? Positioned(
  296. top: 32.5,
  297. right: 0,
  298. child: InkWell(
  299. onTap: () {
  300. jumpToMsg(isAlterYou
  301. ? alterTime
  302. : unreadTime);
  303. clearUnreadNews();
  304. },
  305. child: Container(
  306. alignment: Alignment.center,
  307. decoration: BoxDecoration(
  308. boxShadow: [
  309. BoxShadow(
  310. color: Color(0x33000000), //阴影颜色
  311. blurRadius: 7, //阴影大小
  312. )
  313. ],
  314. borderRadius: BorderRadius.only(
  315. topLeft: Radius.circular(80),
  316. bottomLeft: Radius.circular(80)),
  317. color: Colors.white,
  318. ),
  319. constraints:
  320. BoxConstraints(minWidth: 120),
  321. height: 39,
  322. child: Row(
  323. children: <Widget>[
  324. SizedBox(width: 16.5),
  325. Image.asset(
  326. 'assets/images/up.png',
  327. width: 12,
  328. ),
  329. SizedBox(width: 10),
  330. Container(
  331. child: Text(
  332. isAlterYou
  333. ? '$alterUserName'
  334. : I18n.of(context)
  335. .new_msg
  336. .replaceFirst(
  337. '/s1',
  338. unreadNums
  339. .toString()),
  340. style: TextStyle(
  341. color: Color(0xFF3875E9)),
  342. overflow:
  343. TextOverflow.ellipsis,
  344. ),
  345. constraints: BoxConstraints(
  346. maxWidth: isAlterYou
  347. ? 60
  348. : Screen.width)),
  349. Text(
  350. isAlterYou
  351. ? '@${I18n.of(context).you}'
  352. : '',
  353. style: TextStyle(
  354. color: Color(0xFF3875E9)),
  355. ),
  356. SizedBox(width: 14),
  357. ],
  358. ),
  359. )))
  360. : Container()
  361. ],
  362. ))),
  363. onPointerDown: (value) {
  364. for (var state in states) {
  365. if (!state.containsPosition(value.position)) {
  366. //clear other selection
  367. state.clearSelection();
  368. }
  369. }
  370. },
  371. onPointerMove: (value) {
  372. //clear other selection
  373. for (var state in states) {
  374. if (!state.containsPosition(value.position)) {
  375. //clear other selection
  376. state.clearSelection();
  377. }
  378. }
  379. },
  380. );
  381. },
  382. )));
  383. }
  384. Widget _buildMessageList() {
  385. return Container(
  386. alignment: Alignment.topCenter,
  387. child: msgList.length == 0
  388. ? Padding(
  389. padding: EdgeInsets.all(10),
  390. child: Text(
  391. I18n.of(context).chat_tips,
  392. textAlign: TextAlign.center,
  393. textScaleFactor: 1.0,
  394. style: TextStyle(color: Colors.grey),
  395. ))
  396. : Scrollbar(
  397. child: ListView.builder(
  398. controller: controller,
  399. physics: new ClampingScrollPhysics(),
  400. itemCount: msgList.length,
  401. itemBuilder: _buildItem,
  402. padding: EdgeInsets.symmetric(vertical: 8),
  403. reverse: true,
  404. shrinkWrap: true,
  405. )),
  406. );
  407. }
  408. Widget _wrapScrollTag({int index, Widget child}) => AutoScrollTag(
  409. key: ValueKey(index),
  410. controller: controller,
  411. index: index,
  412. child: child,
  413. highlightColor: Colors.white.withOpacity(1),
  414. );
  415. hideKeyBoard() {
  416. _keyboardIndexProvider.changeSelectIndex(-1);
  417. }
  418. readOnly() {
  419. _keyboardIndexProvider.changeReadOnlyKey(true);
  420. }
  421. sendMsg(MsgModel msg) async {
  422. if (!widget.groupInfoModel.isInGroup) {
  423. //如果不在该群
  424. showToast(I18n.of(context).not_in_group);
  425. return;
  426. }
  427. MsgHandler.insertMsgToDB(msg);
  428. MsgHandler.sendChatMsg(msg);
  429. if (mounted) {
  430. setState(() {});
  431. await controller.scrollToIndex(0,
  432. preferPosition: AutoScrollPosition.begin);
  433. }
  434. }
  435. MsgModel msg;
  436. int count = 0;
  437. testBig(MsgModel msg) async {
  438. for (int k = 0; k < 100; k++) {
  439. msg.msgContent = utf8.encode('测试$count');
  440. Int64 time = Int64((DateTime.now()).millisecondsSinceEpoch);
  441. msg.time = time.toInt();
  442. MsgHandler.insertMsgToDB(msg);
  443. MsgHandler.sendChatMsg(msg);
  444. await Future.delayed(Duration(milliseconds: 300), () {});
  445. count++;
  446. }
  447. count = 0;
  448. print('攻击完毕');
  449. showToast('攻击完毕');
  450. }
  451. void receiveMsg(args) {
  452. print("msgList.length: ${msgList.length}");
  453. if (args != widget.groupInfoModel.sessionId) {
  454. return;
  455. }
  456. if (mounted) {
  457. setState(() {});
  458. }
  459. }
  460. _deleteItem(msg) {
  461. if (msg.state == MsgState.Uploading) {
  462. print('取消上传');
  463. UploadUtil().cancelSendMsg(msg);
  464. }
  465. print('#### 开始删除--');
  466. msgList.remove(msg);
  467. setState(() {});
  468. SqlUtil().deleteSigleRecordWith(msg.sessionId, msg.time);
  469. }
  470. Widget _buildItem(BuildContext context, int index) {
  471. var lastMsgTime;
  472. if (index < msgList.length - 1) {
  473. lastMsgTime = msgList[index].time;
  474. }
  475. MsgModel msg = msgList[index];
  476. var result;
  477. if (msg.from == 0) {
  478. result = GroupChatPageItem(
  479. key: Key(msg.time.toString()), msg: msg, lastMsgTime: lastMsgTime);
  480. } else {
  481. result = GroupChatPageItem(
  482. key: Key(msg.time.toString()),
  483. msg: msg,
  484. memberModel: widget.groupInfoModel.getMember(msg.from),
  485. hideKeyboard: readOnly,
  486. lastMsgTime: lastMsgTime);
  487. }
  488. return _wrapScrollTag(index: index, child: result);
  489. }
  490. quiteGroup() {
  491. var function = () {
  492. GroupInfoMgr().deleteGroup(widget.groupInfoModel.sessionId);
  493. MessageMgr().emit('Quit Group', widget.groupInfoModel.sessionId);
  494. Navigator.of(context).popUntil(ModalRoute.withName('/main'));
  495. MsgHandler.quitGroup(widget.groupInfoModel.sessionId);
  496. };
  497. showModalBottomSheet(
  498. context: context,
  499. backgroundColor: Colors.transparent,
  500. builder: (BuildContext context) {
  501. return Container(
  502. decoration: BoxDecoration(
  503. color: Colors.white,
  504. borderRadius: BorderRadius.only(
  505. topLeft: Radius.circular(13), topRight: Radius.circular(13))),
  506. height: 225,
  507. child: Column(
  508. crossAxisAlignment: CrossAxisAlignment.center,
  509. mainAxisAlignment: MainAxisAlignment.center,
  510. children: <Widget>[
  511. Padding(
  512. padding: EdgeInsets.fromLTRB(15, 15, 15, 13),
  513. child: Text(
  514. I18n.of(context).quit_group_tips,
  515. textScaleFactor: 1.0,
  516. style: TextStyle(fontSize: 12, color: Color(0xff777777)),
  517. ),
  518. ),
  519. Divider(
  520. color: Color(0xffE5E5E5),
  521. ),
  522. InkWell(
  523. onTap: function,
  524. child: Container(
  525. height: 60,
  526. alignment: Alignment.center,
  527. child: Text(I18n.of(context).determine,
  528. textScaleFactor: 1.0,
  529. style: TextStyle(
  530. fontSize: 18, color: Constants.ConfrimButtonColor)),
  531. ),
  532. ),
  533. Container(
  534. color: Color(0xffF2F2F2),
  535. height: 4,
  536. ),
  537. InkWell(
  538. onTap: () {
  539. Navigator.of(context).pop();
  540. },
  541. child: Container(
  542. height: 60,
  543. alignment: Alignment.center,
  544. child: Text(I18n.of(context).cancel,
  545. textScaleFactor: 1.0,
  546. style: TextStyle(fontSize: 18, color: Color(0xff4B4B4B))),
  547. ),
  548. )
  549. ],
  550. ),
  551. );
  552. },
  553. );
  554. }
  555. }