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.
 
 
 
 
 
 

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