Hibok
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

678 regels
24 KiB

  1. import 'package:cached_network_image/cached_network_image.dart';
  2. import 'package:chat/chat/group_chat_view.dart';
  3. import 'package:chat/data/UserData.dart';
  4. import 'package:chat/data/constants.dart';
  5. import 'package:chat/generated/i18n.dart';
  6. import 'package:chat/models/group_info_model.dart';
  7. import 'package:chat/models/group_list_provider.dart';
  8. import 'package:chat/utils/CustomUI.dart';
  9. import 'package:chat/utils/MessageMgr.dart';
  10. import 'package:chat/utils/conversation_table.dart';
  11. import 'package:chat/utils/friend_list_mgr.dart';
  12. import 'package:chat/utils/group_member_model.dart';
  13. import 'package:chat/utils/msgHandler.dart';
  14. import 'package:chat/utils/screen.dart';
  15. import 'package:flutter/material.dart';
  16. import 'package:flutter/services.dart';
  17. import 'package:oktoast/oktoast.dart';
  18. import 'package:provider/provider.dart';
  19. Widget _createAvatar(avatar) {
  20. return ClipRRect(
  21. borderRadius: BorderRadius.circular(6),
  22. child: CachedNetworkImage(
  23. imageUrl: avatar,
  24. width: Constants.ContactAvatarSize,
  25. height: Constants.ContactAvatarSize,
  26. ));
  27. }
  28. class FriendSelectItem extends StatefulWidget {
  29. final FriendModel friendModel;
  30. final String groupTitle;
  31. final bool isShowDivder;
  32. final bool isSingle;
  33. final bool isSelected;
  34. FriendSelectItem(
  35. {this.friendModel,
  36. this.groupTitle,
  37. this.isShowDivder = true,
  38. this.isSelected = false,
  39. this.isSingle = false});
  40. @override
  41. _FriendSelectItemState createState() => _FriendSelectItemState();
  42. }
  43. class _FriendSelectItemState extends State<FriendSelectItem> {
  44. @override
  45. void initState() {
  46. super.initState();
  47. }
  48. @override
  49. Widget build(BuildContext context) {
  50. var selectProvider = Provider.of<GroupSelectProvider>(context);
  51. bool isSelect = selectProvider.isSelect(widget.friendModel);
  52. // print('是否选择 ${widget.friendModel.name} $isSelect');
  53. var _avatarIcon = _createAvatar(widget.friendModel.avatar);
  54. Widget _button = Container(
  55. padding: const EdgeInsets.symmetric(
  56. vertical: MARGIN_VERTICAL, horizontal: 16.0),
  57. decoration: BoxDecoration(color: Colors.white),
  58. child: Row(
  59. children: <Widget>[
  60. widget.isSelected
  61. ? Container(
  62. width: 20,
  63. height: 20,
  64. decoration: BoxDecoration(
  65. color: Colors.grey[400],
  66. shape: BoxShape.circle,
  67. border: Border.all(
  68. color: const Color(0xFF707070), width: 0.2)),
  69. child: Icon(Icons.check, size: 15, color: Colors.white),
  70. )
  71. : (isSelect
  72. ? Container(
  73. width: 20,
  74. height: 20,
  75. decoration: BoxDecoration(
  76. color: const Color(0xFF2685FA),
  77. shape: BoxShape.circle,
  78. border: Border.all(
  79. color: const Color(0xFF707070), width: 0.2)),
  80. child: Icon(Icons.check, size: 15, color: Colors.white),
  81. )
  82. : Container(
  83. width: 20,
  84. height: 20,
  85. decoration: BoxDecoration(
  86. color: Colors.white,
  87. shape: BoxShape.circle,
  88. border: Border.all(color: const Color(0xFF707070))),
  89. )),
  90. SizedBox(width: 10),
  91. _avatarIcon,
  92. SizedBox(width: 10.0),
  93. Expanded(
  94. child: Container(
  95. child: Text(
  96. widget.friendModel.getTitle(),
  97. overflow: TextOverflow.ellipsis,
  98. )),
  99. )
  100. ],
  101. ),
  102. );
  103. //分组标签
  104. Widget _itemBody;
  105. if (widget.groupTitle != null) {
  106. _itemBody = Column(
  107. children: <Widget>[
  108. Container(
  109. height: GROUP_TITLE_HEIGHT,
  110. padding: EdgeInsets.only(left: 16.0, right: 16.0),
  111. color: const Color(AppColors.ContactGroupTitleBgColor),
  112. alignment: Alignment.centerLeft,
  113. child: Text(widget.groupTitle,
  114. style: AppStyles.GroupTitleItemTextStyle),
  115. ),
  116. _button,
  117. ],
  118. );
  119. } else {
  120. _itemBody = _button;
  121. }
  122. return InkWell(
  123. onTap: widget.isSelected
  124. ? null
  125. : () {
  126. if (widget.isSingle) {
  127. selectProvider.curSelectFriendList.clear();
  128. if (isSelect) {
  129. selectProvider.removeFriend(widget.friendModel);
  130. } else {
  131. selectProvider.addFriend(widget.friendModel);
  132. MessageMgr().emit('jump_top');
  133. }
  134. } else {
  135. if (isSelect) {
  136. selectProvider.removeFriend(widget.friendModel);
  137. } else {
  138. selectProvider.addFriend(widget.friendModel);
  139. MessageMgr().emit('jump_top');
  140. }
  141. }
  142. },
  143. child: Container(
  144. color: Colors.white,
  145. child: Column(
  146. children: <Widget>[
  147. _itemBody,
  148. widget.isShowDivder
  149. ? Container(
  150. height: 1,
  151. color: const Color(0xFFF3F3F3),
  152. margin: EdgeInsets.only(
  153. left: 26 + Constants.ContactAvatarSize),
  154. )
  155. : Container()
  156. ],
  157. )));
  158. }
  159. }
  160. class CreateGroupPage extends StatefulWidget {
  161. final int pageType;
  162. final List<GroupMemberModel> originalList;
  163. final int groupId;
  164. CreateGroupPage(this.pageType, this.originalList, this.groupId);
  165. @override
  166. _CreateGroupPageState createState() => _CreateGroupPageState();
  167. }
  168. class _CreateGroupPageState extends State<CreateGroupPage> {
  169. String _currentLetter = '';
  170. ScrollController _scrollController;
  171. ScrollController _headScrollCtrl = ScrollController();
  172. TextEditingController _txtCtrl = new TextEditingController();
  173. bool _hasdeleteIcon = false;
  174. bool isSingle = false;
  175. bool isCreating = false;
  176. List<FriendModel> friendList = [];
  177. List<FriendModel> searchList = [];
  178. Set originalUserIdSet = new Set();
  179. GroupSelectProvider _groupSelectProvider = GroupSelectProvider();
  180. final Map _letterPosMap = {INDEX_BAR_WORDS[0]: 0.0};
  181. double _groupHeight(bool hasGroupTitle) {
  182. final _buttonHeight = MARGIN_VERTICAL * 2 +
  183. Constants.ContactAvatarSize +
  184. Constants.DividerWidth;
  185. if (hasGroupTitle) {
  186. return _buttonHeight + GROUP_TITLE_HEIGHT;
  187. } else {
  188. return _buttonHeight;
  189. }
  190. }
  191. @override
  192. void initState() {
  193. super.initState();
  194. print('CreateGroupPage initState');
  195. getFriendList();
  196. isSingle = widget.pageType == GroupOperatingPageType.SelectGroupOwner;
  197. MessageMgr().on('jump_top', animateToTop);
  198. }
  199. @override
  200. void dispose() {
  201. MessageMgr().off('jump_top', animateToTop);
  202. super.dispose();
  203. }
  204. animateToTop(data) {
  205. // _scrollController.animateTo(
  206. // 0.0,
  207. // curve: Curves.easeOut,
  208. // duration: const Duration(milliseconds: 300),
  209. // );
  210. _headScrollCtrl.jumpTo(0);
  211. }
  212. getFriendList() async {
  213. for (var item in widget.originalList) {
  214. originalUserIdSet.add(item.memberId);
  215. }
  216. if (widget.pageType == GroupOperatingPageType.AddMember ||
  217. widget.pageType == GroupOperatingPageType.CreateGroup) {
  218. friendList = await FriendListMgr().getFriendList();
  219. setState(() {});
  220. }
  221. if (widget.pageType == GroupOperatingPageType.DeleteMember ||
  222. widget.pageType == GroupOperatingPageType.SelectGroupOwner) {
  223. for (var item in widget.originalList) {
  224. if (item.memberId != UserData().basicInfo.userId) {
  225. FriendModel friend = new FriendModel(
  226. friendId: item.memberId,
  227. avatar: item.avtar,
  228. name: item.nickName,
  229. refName: item.refName);
  230. friendList.add(friend);
  231. }
  232. }
  233. setState(() {});
  234. }
  235. }
  236. Widget _createSearch() {
  237. return Container(
  238. alignment: Alignment.center,
  239. height: 45,
  240. child: TextField(
  241. keyboardAppearance: Brightness.light,
  242. keyboardType: TextInputType.text,
  243. textInputAction: TextInputAction.search,
  244. controller: _txtCtrl,
  245. maxLines: 1,
  246. style:
  247. TextStyle(textBaseline: TextBaseline.alphabetic, fontSize: 14.5),
  248. autofocus: false,
  249. inputFormatters: [
  250. LengthLimitingTextInputFormatter(50),
  251. ],
  252. decoration: InputDecoration(
  253. contentPadding: EdgeInsets.symmetric(vertical: 14),
  254. hintText: I18n.of(context).search,
  255. hintStyle: TextStyle(fontSize: 14.5),
  256. prefixIcon: Consumer<GroupSelectProvider>(
  257. builder: (context, counter, child) => Icon(
  258. IconData(
  259. 0xe664,
  260. fontFamily: Constants.IconFontFamily,
  261. ),
  262. color: const Color(0xFFA0A0A0),
  263. size: 18,
  264. )),
  265. filled: true,
  266. fillColor: Colors.transparent,
  267. border: InputBorder.none,
  268. ),
  269. onChanged: (str) async {
  270. setState(() {
  271. if (str.isEmpty) {
  272. _hasdeleteIcon = false;
  273. } else {
  274. _hasdeleteIcon = true;
  275. searchList = CustomUI()
  276. .getSearchResult(str, friendList == null ? [] : friendList);
  277. }
  278. });
  279. },
  280. onEditingComplete: () {}),
  281. );
  282. }
  283. updateIndexPos(List<FriendModel> friendList) {
  284. //计算用于 IndexBar 进行定位的关键通讯录列表项的位置
  285. double _totalPos = 0.0;
  286. for (var i = 0; i < friendList.length; i++) {
  287. bool _hasGroupTitle = true;
  288. if (i > 0 &&
  289. friendList[i].nameTag.compareTo(friendList[i - 1].nameTag) == 0) {
  290. _hasGroupTitle = false;
  291. }
  292. if (_hasGroupTitle) {
  293. _letterPosMap[friendList[i].nameTag] = _totalPos;
  294. }
  295. _totalPos += _groupHeight(_hasGroupTitle);
  296. }
  297. }
  298. String getLetter(BuildContext context, double tileHeight, Offset globalPos) {
  299. RenderBox _box = context.findRenderObject();
  300. var local = _box.globalToLocal(globalPos);
  301. int index = (local.dy ~/ tileHeight).clamp(0, INDEX_BAR_WORDS.length - 1);
  302. return INDEX_BAR_WORDS[index];
  303. }
  304. void _jumpToIndex(String letter) {
  305. if (_letterPosMap.isNotEmpty) {
  306. final _pos = _letterPosMap[letter];
  307. if (_pos != null) {
  308. _scrollController.animateTo(_letterPosMap[letter],
  309. curve: Curves.easeInOut, duration: Duration(microseconds: 200));
  310. }
  311. }
  312. }
  313. Widget _buildIndexBar(BuildContext context, BoxConstraints constraints) {
  314. final List<Widget> _letters = INDEX_BAR_WORDS.map((String word) {
  315. return Expanded(
  316. child: Container(
  317. margin: EdgeInsets.only(right: 5),
  318. decoration: BoxDecoration(
  319. shape: BoxShape.circle,
  320. color:
  321. _currentLetter == word ? Colors.blue : Colors.transparent,
  322. ),
  323. alignment: Alignment.center,
  324. padding: EdgeInsets.all(2),
  325. width: 20,
  326. child: Text(
  327. word,
  328. textScaleFactor: 1.0,
  329. style: TextStyle(
  330. fontSize: 10,
  331. color:
  332. _currentLetter == word ? Colors.white : Colors.black),
  333. )));
  334. }).toList();
  335. final _totalHeight = constraints.biggest.height;
  336. final _tileHeight = _totalHeight / _letters.length;
  337. return GestureDetector(
  338. onVerticalDragDown: (DragDownDetails details) {
  339. setState(() {
  340. _currentLetter =
  341. getLetter(context, _tileHeight, details.globalPosition);
  342. _jumpToIndex(_currentLetter);
  343. });
  344. },
  345. onVerticalDragEnd: (DragEndDetails details) {
  346. setState(() {
  347. //_indexBarBgColor = Colors.transparent;
  348. _currentLetter = null;
  349. });
  350. },
  351. onVerticalDragCancel: () {
  352. setState(() {
  353. //_indexBarBgColor = Colors.transparent;
  354. _currentLetter = null;
  355. });
  356. },
  357. onVerticalDragUpdate: (DragUpdateDetails details) {
  358. setState(() {
  359. //var _letter = getLetter(context, _tileHeight, details.globalPosition);
  360. _currentLetter =
  361. getLetter(context, _tileHeight, details.globalPosition);
  362. _jumpToIndex(_currentLetter);
  363. });
  364. },
  365. child: Column(
  366. children: _letters,
  367. ),
  368. );
  369. }
  370. @override
  371. Widget build(BuildContext context) {
  372. if (friendList == null) {
  373. return Scaffold(
  374. appBar: AppBar(
  375. leading: CustomUI.buildCustomLeading(context),
  376. ),
  377. body: Center(child: CircularProgressIndicator()));
  378. }
  379. final List<Widget> _body = [];
  380. if (!_hasdeleteIcon) {
  381. friendList.sort((a, b) => a.nameTag.compareTo(b.nameTag));
  382. updateIndexPos(friendList);
  383. _body.addAll([
  384. ListView.builder(
  385. controller: _scrollController,
  386. itemBuilder: (BuildContext context, int index) {
  387. bool _isGroupTitle = true;
  388. FriendModel _contact = friendList[index];
  389. if (index >= 1 &&
  390. _contact.nameTag == friendList[index - 1].nameTag) {
  391. _isGroupTitle = false;
  392. }
  393. return FriendSelectItem(
  394. friendModel: _contact,
  395. isShowDivder: _isGroupTitle,
  396. isSingle: isSingle,
  397. isSelected: originalUserIdSet.contains(_contact.friendId) &&
  398. widget.pageType == GroupOperatingPageType.AddMember,
  399. groupTitle: _isGroupTitle ? _contact.nameTag : null);
  400. },
  401. itemCount: friendList.length,
  402. ),
  403. Positioned(
  404. width: Constants.IndexBarWidth,
  405. right: 0.0,
  406. top: 0.0,
  407. bottom: 0.0,
  408. child: Container(
  409. child: LayoutBuilder(
  410. builder: _buildIndexBar,
  411. ),
  412. ),
  413. )
  414. ]);
  415. } else {
  416. _body.add(ListView.builder(
  417. controller: _scrollController,
  418. itemBuilder: (BuildContext context, int index) {
  419. FriendModel _contact = searchList[index];
  420. return FriendSelectItem(
  421. isSingle: isSingle,
  422. friendModel: _contact,
  423. isShowDivder: true,
  424. isSelected: originalUserIdSet.contains(_contact.friendId) &&
  425. widget.pageType == GroupOperatingPageType.AddMember,
  426. groupTitle: null);
  427. },
  428. itemCount: searchList.length,
  429. ));
  430. }
  431. if (_currentLetter != null &&
  432. _currentLetter.isNotEmpty &&
  433. !_hasdeleteIcon) {
  434. _body.add(Center(
  435. child: Container(
  436. width: Constants.IndexLetterBoxSize,
  437. height: Constants.IndexLetterBoxSize,
  438. decoration: BoxDecoration(
  439. color: AppColors.IndexLetterBoxBgColor,
  440. borderRadius: BorderRadius.all(
  441. Radius.circular(Constants.IndexLetterBoxRadius)),
  442. ),
  443. child: Center(
  444. child:
  445. Text(_currentLetter, style: AppStyles.IndexLetterBoxTextStyle),
  446. ),
  447. ),
  448. ));
  449. }
  450. String title = '';
  451. switch (widget.pageType) {
  452. case GroupOperatingPageType.AddMember:
  453. case GroupOperatingPageType.CreateGroup:
  454. title = I18n.of(context).add_member;
  455. break;
  456. case GroupOperatingPageType.DeleteMember:
  457. title = I18n.of(context).delete_member;
  458. break;
  459. case GroupOperatingPageType.SelectGroupOwner:
  460. title = I18n.of(context).choose_group_owner;
  461. break;
  462. default:
  463. }
  464. return ChangeNotifierProvider(
  465. create: (_) => _groupSelectProvider,
  466. child: Scaffold(
  467. resizeToAvoidBottomPadding: false,
  468. appBar: AppBar(
  469. backgroundColor: AppColors.NewAppbarBgColor,
  470. title: Text(
  471. title,
  472. textScaleFactor: 1.0,
  473. style: TextStyle(color: AppColors.NewAppbarTextColor),
  474. ),
  475. centerTitle: true,
  476. leading: CustomUI.buildCustomLeading(context),
  477. elevation: 0,
  478. actions: <Widget>[
  479. InkWell(
  480. child: Padding(
  481. padding: EdgeInsets.only(right: 15, top: 14, bottom: 14),
  482. child: Consumer<GroupSelectProvider>(
  483. builder: (context, counter, child) => Container(
  484. decoration: BoxDecoration(
  485. borderRadius: BorderRadius.circular(4.5),
  486. color: _groupSelectProvider
  487. .curSelectFriendList.length ==
  488. 0
  489. ? Colors.grey[350]
  490. : (widget.pageType ==
  491. GroupOperatingPageType
  492. .DeleteMember
  493. ? Colors.red
  494. : const Color(0xFF3875E9)),
  495. ),
  496. padding: EdgeInsets.symmetric(horizontal: 18),
  497. alignment: Alignment.center,
  498. child: fixedText(
  499. (widget.pageType ==
  500. GroupOperatingPageType
  501. .DeleteMember
  502. ? I18n.of(context).delete
  503. : I18n.of(context).determine) +
  504. "(${_groupSelectProvider.curSelectFriendList.length})",
  505. color: Colors.white,
  506. fontSize: 12.67),
  507. ))),
  508. onTap: () async {
  509. if (_groupSelectProvider.curSelectFriendList.length == 0)
  510. return;
  511. switch (widget.pageType) {
  512. case GroupOperatingPageType.CreateGroup:
  513. createGroup();
  514. break;
  515. case GroupOperatingPageType.AddMember:
  516. addMember();
  517. break;
  518. case GroupOperatingPageType.DeleteMember:
  519. deleteMember();
  520. break;
  521. case GroupOperatingPageType.SelectGroupOwner:
  522. selectGroupOwner();
  523. break;
  524. default:
  525. }
  526. },
  527. )
  528. ],
  529. bottom: PreferredSize(
  530. preferredSize: Size.fromHeight(49),
  531. child: Container(
  532. color: Colors.white,
  533. child: Column(
  534. children: <Widget>[
  535. Container(
  536. height: 6,
  537. color: const Color(0xFFE9E9E9),
  538. ),
  539. Row(
  540. children: <Widget>[
  541. Consumer<GroupSelectProvider>(
  542. builder: (context, counter, child) {
  543. return Container(
  544. margin: EdgeInsets.only(left: 10),
  545. constraints: BoxConstraints(
  546. maxWidth: Screen.width - 100),
  547. height: 45,
  548. width: (Constants.ContactAvatarSize + 3) *
  549. counter.curSelectFriendList.length,
  550. child: ListView(
  551. reverse: true,
  552. controller: _headScrollCtrl,
  553. scrollDirection: Axis.horizontal,
  554. children: counter.curSelectFriendList.reversed
  555. .map((friend) => InkWell(
  556. onTap: () {
  557. var selectProvider = Provider.of<
  558. GroupSelectProvider>(context);
  559. bool isSelect =
  560. selectProvider.isSelect(friend);
  561. if (isSelect) {
  562. selectProvider
  563. .removeFriend(friend);
  564. } else {
  565. selectProvider.addFriend(friend);
  566. }
  567. },
  568. child: Container(
  569. margin: EdgeInsets.only(right: 3),
  570. child: _createAvatar(
  571. friend.avatar))))
  572. .toList(),
  573. ),
  574. );
  575. }),
  576. Expanded(
  577. child: _createSearch(),
  578. )
  579. ],
  580. )
  581. ],
  582. ),
  583. )),
  584. ),
  585. body: Stack(
  586. children: _body,
  587. )));
  588. }
  589. deleteMember() {
  590. List<int> members = [];
  591. for (var i = 0; i < _groupSelectProvider.curSelectFriendList.length; i++) {
  592. var fdInfo = _groupSelectProvider.curSelectFriendList[i];
  593. members.add(fdInfo.friendId);
  594. }
  595. MsgHandler.removeGroupMember(widget.groupId, members);
  596. Navigator.of(context).pop();
  597. }
  598. addMember() {
  599. List<int> members = [];
  600. for (var i = 0; i < _groupSelectProvider.curSelectFriendList.length; i++) {
  601. var fdInfo = _groupSelectProvider.curSelectFriendList[i];
  602. members.add(fdInfo.friendId);
  603. }
  604. MsgHandler.addGroupMember(widget.groupId, members);
  605. Navigator.of(context).pop();
  606. }
  607. selectGroupOwner() {
  608. FriendModel fdInfo = _groupSelectProvider.curSelectFriendList[0];
  609. MsgHandler.updateGroupHoster(widget.groupId, fdInfo.friendId);
  610. Navigator.of(context).pop('close');
  611. }
  612. createGroup() {
  613. //创建群
  614. print('创建群:成员数${_groupSelectProvider.curSelectFriendList.length}');
  615. var myId = UserData().basicInfo.userId;
  616. var members = [myId];
  617. for (var i = 0; i < _groupSelectProvider.curSelectFriendList.length; i++) {
  618. var fdInfo = _groupSelectProvider.curSelectFriendList[i];
  619. members.add(fdInfo.friendId);
  620. }
  621. if (isCreating) {
  622. showToast(I18n.of(context).creating_group);
  623. return;
  624. }
  625. isCreating = true;
  626. MsgHandler.createGroup(members, (GroupInfoModel groupInfoModel) {
  627. Navigator.of(context).pushReplacement(MaterialPageRoute(
  628. builder: (context) => GroupChatPage(
  629. key: Key('GroupChat'), groupInfoModel: groupInfoModel)));
  630. });
  631. }
  632. }