Hibok
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

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