Hibok
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

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