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

443 行
14 KiB

  1. import 'package:cached_network_image/cached_network_image.dart';
  2. import 'package:chat/data/UserData.dart';
  3. import 'package:chat/data/constants.dart';
  4. import 'package:chat/generated/i18n.dart';
  5. import 'package:chat/models/group_info_model.dart';
  6. import 'package:chat/utils/CustomUI.dart';
  7. import 'package:chat/utils/group_member_model.dart';
  8. import 'package:flutter/material.dart';
  9. import 'package:flutter/services.dart';
  10. class _MemberItem extends StatelessWidget {
  11. _MemberItem({
  12. @required this.avatar,
  13. @required this.title,
  14. @required this.userId,
  15. this.groupTitle,
  16. this.onPressed,
  17. this.gradient,
  18. this.iconCode,
  19. this.isShowDivder: true,
  20. });
  21. final int userId;
  22. final int iconCode;
  23. final String avatar;
  24. final String title;
  25. final String groupTitle;
  26. final VoidCallback onPressed;
  27. final Gradient gradient;
  28. final bool isShowDivder;
  29. static double _height(bool hasGroupTitle) {
  30. final _buttonHeight = MARGIN_VERTICAL * 2 +
  31. Constants.ContactAvatarSize +
  32. Constants.DividerWidth;
  33. if (hasGroupTitle) {
  34. return _buttonHeight + GROUP_TITLE_HEIGHT;
  35. } else {
  36. return _buttonHeight;
  37. }
  38. }
  39. @override
  40. Widget build(BuildContext context) {
  41. Widget _avatarIcon;
  42. if (iconCode == null) {
  43. _avatarIcon = ClipRRect(
  44. borderRadius: BorderRadius.circular(6),
  45. child: CachedNetworkImage(
  46. imageUrl: this.avatar,
  47. width: Constants.ContactAvatarSize,
  48. height: Constants.ContactAvatarSize,
  49. ));
  50. } else {
  51. _avatarIcon = Container(
  52. width: Constants.ContactAvatarSize,
  53. height: Constants.ContactAvatarSize,
  54. decoration: BoxDecoration(
  55. gradient: gradient, borderRadius: BorderRadius.circular(6)),
  56. child: Icon(
  57. IconData(this.iconCode, fontFamily: Constants.IconFontFamily),
  58. color: Colors.white,
  59. ),
  60. );
  61. }
  62. Widget _button = Container(
  63. padding: const EdgeInsets.symmetric(
  64. vertical: MARGIN_VERTICAL, horizontal: 16.0),
  65. decoration: BoxDecoration(color: Colors.white),
  66. child: Row(
  67. children: <Widget>[
  68. _avatarIcon,
  69. SizedBox(width: 10.0),
  70. Text(title),
  71. ],
  72. ),
  73. );
  74. //分组标签
  75. Widget _itemBody;
  76. if (this.groupTitle != null) {
  77. _itemBody = Column(
  78. children: <Widget>[
  79. Container(
  80. height: GROUP_TITLE_HEIGHT,
  81. padding: EdgeInsets.only(left: 16.0, right: 16.0),
  82. color: const Color(AppColors.ContactGroupTitleBgColor),
  83. alignment: Alignment.centerLeft,
  84. child:
  85. Text(this.groupTitle, style: AppStyles.GroupTitleItemTextStyle),
  86. ),
  87. _button,
  88. ],
  89. );
  90. } else {
  91. _itemBody = _button;
  92. }
  93. return InkWell(
  94. onTap: onPressed,
  95. child: Container(
  96. color: Colors.white,
  97. child: Column(
  98. children: <Widget>[
  99. _itemBody,
  100. isShowDivder
  101. ? Container(
  102. height: 1,
  103. color: const Color(0xFFF3F3F3),
  104. margin: EdgeInsets.only(
  105. left: 26 + Constants.ContactAvatarSize),
  106. )
  107. : Container()
  108. ],
  109. )));
  110. }
  111. }
  112. class AlterSelectPage extends StatefulWidget {
  113. final GroupInfoModel groupInfoModel;
  114. AlterSelectPage(this.groupInfoModel);
  115. @override
  116. _AlterSelectPageState createState() => _AlterSelectPageState();
  117. static Future<GroupMemberModel> pickAlterUser(
  118. BuildContext context, GroupInfoModel groupInfoModel) async {
  119. var results = await Navigator.of(context).push(
  120. MaterialPageRoute<dynamic>(
  121. builder: (BuildContext context) {
  122. return AlterSelectPage(groupInfoModel);
  123. },
  124. ),
  125. );
  126. return results;
  127. }
  128. }
  129. class _AlterSelectPageState extends State<AlterSelectPage> {
  130. List<GroupMemberModel> members = []; //除自己外的其他成员
  131. bool _hasdeleteIcon = false; //是否在搜索状态
  132. TextEditingController _txtCtrl = TextEditingController();
  133. ScrollController _scrollController = ScrollController();
  134. List<GroupMemberModel> searchList = [];
  135. final Map _letterPosMap = {INDEX_BAR_WORDS[0]: 0.0};
  136. FocusNode focusNode = FocusNode();
  137. String _currentLetter = '';
  138. @override
  139. void initState() {
  140. super.initState();
  141. print('AlterSelectPage initState');
  142. var myId = UserData().basicInfo.userId;
  143. for (var member in widget.groupInfoModel.members) {
  144. if (member.memberId != myId && member.inGroup > 0) {
  145. member.getNameTag();
  146. members.add(member);
  147. }
  148. }
  149. }
  150. @override
  151. void dispose() {
  152. focusNode.unfocus();
  153. _scrollController.dispose();
  154. _txtCtrl.dispose();
  155. super.dispose();
  156. }
  157. updateIndexPos(List<GroupMemberModel> friendList) {
  158. //计算用于 IndexBar 进行定位的关键通讯录列表项的位置
  159. double _totalPos = 0.0;
  160. for (var i = 0; i < friendList.length; i++) {
  161. bool _hasGroupTitle = true;
  162. if (i > 0 &&
  163. friendList[i].nameTag.compareTo(friendList[i - 1].nameTag) == 0) {
  164. _hasGroupTitle = false;
  165. }
  166. if (_hasGroupTitle) {
  167. _letterPosMap[friendList[i].nameTag] = _totalPos;
  168. }
  169. _totalPos += _MemberItem._height(_hasGroupTitle);
  170. }
  171. }
  172. Widget _buildAvatar(url) {
  173. return ClipRRect(
  174. borderRadius: BorderRadius.circular(6),
  175. child: CachedNetworkImage(
  176. imageUrl: url,
  177. width: Constants.ContactAvatarSize,
  178. height: Constants.ContactAvatarSize,
  179. ));
  180. }
  181. String getLetter(BuildContext context, double tileHeight, Offset globalPos) {
  182. RenderBox _box = context.findRenderObject();
  183. var local = _box.globalToLocal(globalPos);
  184. int index = (local.dy ~/ tileHeight).clamp(0, INDEX_BAR_WORDS.length - 1);
  185. return INDEX_BAR_WORDS[index];
  186. }
  187. void _jumpToIndex(String letter) {
  188. if (_letterPosMap.isNotEmpty) {
  189. final _pos = _letterPosMap[letter];
  190. if (_pos != null) {
  191. _scrollController.animateTo(_letterPosMap[letter],
  192. curve: Curves.easeInOut, duration: Duration(microseconds: 200));
  193. }
  194. }
  195. }
  196. Widget _buildIndexBar(BuildContext context, BoxConstraints constraints) {
  197. final List<Widget> _letters = INDEX_BAR_WORDS.map((String word) {
  198. return Expanded(
  199. child: Container(
  200. margin: EdgeInsets.only(right: 5),
  201. decoration: BoxDecoration(
  202. shape: BoxShape.circle,
  203. color:
  204. _currentLetter == word ? Colors.blue : Colors.transparent,
  205. ),
  206. alignment: Alignment.center,
  207. padding: EdgeInsets.all(2),
  208. width: 20,
  209. child: Text(
  210. word,
  211. style: TextStyle(
  212. fontSize: 10,
  213. color:
  214. _currentLetter == word ? Colors.white : Colors.black),
  215. )));
  216. }).toList();
  217. final _totalHeight = constraints.biggest.height;
  218. final _tileHeight = _totalHeight / _letters.length;
  219. return GestureDetector(
  220. onVerticalDragDown: (DragDownDetails details) {
  221. setState(() {
  222. _currentLetter =
  223. getLetter(context, _tileHeight, details.globalPosition);
  224. _jumpToIndex(_currentLetter);
  225. });
  226. },
  227. onVerticalDragEnd: (DragEndDetails details) {
  228. setState(() {
  229. //_indexBarBgColor = Colors.transparent;
  230. _currentLetter = null;
  231. });
  232. },
  233. onVerticalDragCancel: () {
  234. setState(() {
  235. //_indexBarBgColor = Colors.transparent;
  236. _currentLetter = null;
  237. });
  238. },
  239. onVerticalDragUpdate: (DragUpdateDetails details) {
  240. setState(() {
  241. //var _letter = getLetter(context, _tileHeight, details.globalPosition);
  242. _currentLetter =
  243. getLetter(context, _tileHeight, details.globalPosition);
  244. _jumpToIndex(_currentLetter);
  245. });
  246. },
  247. child: Column(
  248. children: _letters,
  249. ),
  250. );
  251. }
  252. @override
  253. Widget build(BuildContext context) {
  254. final List<Widget> _body = [];
  255. if (!_hasdeleteIcon) {
  256. members.sort((a, b) => a.nameTag.compareTo(b.nameTag));
  257. updateIndexPos(members);
  258. _body.addAll([
  259. ListView.builder(
  260. controller: _scrollController,
  261. itemBuilder: (BuildContext context, int index) {
  262. bool _isGroupTitle = true;
  263. GroupMemberModel _contact = members[index];
  264. if (index >= 1 &&
  265. _contact.nameTag == members[index - 1].nameTag) {
  266. _isGroupTitle = false;
  267. }
  268. return _MemberItem(
  269. userId: _contact.memberId,
  270. avatar: _contact.avtar,
  271. title: _contact.refName,
  272. isShowDivder: _isGroupTitle,
  273. onPressed: () {
  274. Navigator.of(context).pop(_contact);
  275. },
  276. groupTitle: _isGroupTitle ? _contact.nameTag : null);
  277. },
  278. itemCount: members.length),
  279. Positioned(
  280. width: Constants.IndexBarWidth,
  281. right: 0.0,
  282. top: 0.0,
  283. bottom: 0.0,
  284. child: Container(
  285. child: LayoutBuilder(
  286. builder: _buildIndexBar,
  287. ),
  288. ),
  289. )
  290. ]);
  291. } else {
  292. _body.add(ListView.builder(
  293. controller: _scrollController,
  294. itemBuilder: (BuildContext context, int index) {
  295. GroupMemberModel _contact = searchList[index];
  296. return _MemberItem(
  297. userId: _contact.memberId,
  298. avatar: _contact.avtar,
  299. title: _contact.refName,
  300. isShowDivder: true,
  301. onPressed: () {
  302. Navigator.of(context).pop(_contact);
  303. },
  304. groupTitle: null);
  305. },
  306. itemCount: searchList.length,
  307. ));
  308. }
  309. if (_currentLetter != null &&
  310. _currentLetter.isNotEmpty &&
  311. !_hasdeleteIcon) {
  312. _body.add(Center(
  313. child: Container(
  314. width: Constants.IndexLetterBoxSize,
  315. height: Constants.IndexLetterBoxSize,
  316. decoration: BoxDecoration(
  317. color: AppColors.IndexLetterBoxBgColor,
  318. borderRadius: BorderRadius.all(
  319. Radius.circular(Constants.IndexLetterBoxRadius)),
  320. ),
  321. child: Center(
  322. child:
  323. Text(_currentLetter, style: AppStyles.IndexLetterBoxTextStyle),
  324. ),
  325. ),
  326. ));
  327. }
  328. return Scaffold(
  329. resizeToAvoidBottomPadding: false,
  330. backgroundColor: AppColors.NewAppbarBgColor,
  331. appBar: AppBar(
  332. title: Text(I18n.of(context).select_notice_people),
  333. centerTitle: true,
  334. elevation: 1,
  335. leading: CustomUI.buildCustomLeading(context),
  336. bottom: PreferredSize(
  337. preferredSize: Size.fromHeight(49),
  338. child: Container(
  339. alignment: Alignment.center,
  340. margin: EdgeInsets.only(bottom: 14, left: 12.5, right: 12.5),
  341. height: 35,
  342. decoration: BoxDecoration(
  343. color: const Color(0xFFEEEEEE),
  344. borderRadius: BorderRadius.all(Radius.circular(8))),
  345. child: TextField(
  346. keyboardAppearance: Brightness.light,
  347. keyboardType: TextInputType.text,
  348. textInputAction: TextInputAction.search,
  349. controller: _txtCtrl,
  350. cursorColor: Constants.BlueTextColor,
  351. maxLines: 1,
  352. style: TextStyle(
  353. textBaseline: TextBaseline.alphabetic, fontSize: 14.5),
  354. autofocus: false,
  355. inputFormatters: [
  356. LengthLimitingTextInputFormatter(50),
  357. ],
  358. focusNode: focusNode,
  359. decoration: InputDecoration(
  360. hintText: I18n.of(context).search,
  361. hintStyle: TextStyle(fontSize: 14.5),
  362. prefixIcon: Icon(
  363. IconData(
  364. 0xe664,
  365. fontFamily: Constants.IconFontFamily,
  366. ),
  367. color: const Color(0xFFA0A0A0),
  368. size: 18,
  369. ),
  370. suffixIcon: Padding(
  371. padding: EdgeInsetsDirectional.only(
  372. start: 2.0, end: _hasdeleteIcon ? 20.0 : 0),
  373. child: _hasdeleteIcon
  374. ? new InkWell(
  375. onTap: (() {
  376. setState(() {
  377. WidgetsBinding.instance
  378. .addPostFrameCallback(
  379. (_) => _txtCtrl.clear());
  380. _hasdeleteIcon = false;
  381. });
  382. }),
  383. child: Icon(
  384. Icons.clear,
  385. size: 18.0,
  386. color: Constants.BlackTextColor,
  387. ))
  388. : new Text('')),
  389. filled: true,
  390. fillColor: Colors.transparent,
  391. border: InputBorder.none,
  392. ),
  393. onChanged: (str) async {
  394. setState(() {
  395. if (str.isEmpty) {
  396. _hasdeleteIcon = false;
  397. } else {
  398. _hasdeleteIcon = true;
  399. searchList = CustomUI().getSearchResult(
  400. str, members == null ? [] : members);
  401. }
  402. });
  403. },
  404. onEditingComplete: () {}),
  405. )),
  406. ),
  407. body: Stack(
  408. children: _body,
  409. ));
  410. }
  411. }