Hibok
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

435 рядки
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, textScaleFactor: 1.0,),
  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, textScaleFactor: 1.0,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. String getLetter(BuildContext context, double tileHeight, Offset globalPos) {
  173. RenderBox _box = context.findRenderObject();
  174. var local = _box.globalToLocal(globalPos);
  175. int index = (local.dy ~/ tileHeight).clamp(0, INDEX_BAR_WORDS.length - 1);
  176. return INDEX_BAR_WORDS[index];
  177. }
  178. void _jumpToIndex(String letter) {
  179. if (_letterPosMap.isNotEmpty) {
  180. final _pos = _letterPosMap[letter];
  181. if (_pos != null) {
  182. _scrollController.animateTo(_letterPosMap[letter],
  183. curve: Curves.easeInOut, duration: Duration(microseconds: 200));
  184. }
  185. }
  186. }
  187. Widget _buildIndexBar(BuildContext context, BoxConstraints constraints) {
  188. final List<Widget> _letters = INDEX_BAR_WORDS.map((String word) {
  189. return Expanded(
  190. child: Container(
  191. margin: EdgeInsets.only(right: 5),
  192. decoration: BoxDecoration(
  193. shape: BoxShape.circle,
  194. color:
  195. _currentLetter == word ? Colors.blue : Colors.transparent,
  196. ),
  197. alignment: Alignment.center,
  198. padding: EdgeInsets.all(2),
  199. width: 20,
  200. child: Text(
  201. word,
  202. textScaleFactor: 1.0,
  203. style: TextStyle(
  204. fontSize: 10,
  205. color:
  206. _currentLetter == word ? Colors.white : Colors.black),
  207. )));
  208. }).toList();
  209. final _totalHeight = constraints.biggest.height;
  210. final _tileHeight = _totalHeight / _letters.length;
  211. return GestureDetector(
  212. onVerticalDragDown: (DragDownDetails details) {
  213. setState(() {
  214. _currentLetter =
  215. getLetter(context, _tileHeight, details.globalPosition);
  216. _jumpToIndex(_currentLetter);
  217. });
  218. },
  219. onVerticalDragEnd: (DragEndDetails details) {
  220. setState(() {
  221. //_indexBarBgColor = Colors.transparent;
  222. _currentLetter = null;
  223. });
  224. },
  225. onVerticalDragCancel: () {
  226. setState(() {
  227. //_indexBarBgColor = Colors.transparent;
  228. _currentLetter = null;
  229. });
  230. },
  231. onVerticalDragUpdate: (DragUpdateDetails details) {
  232. setState(() {
  233. //var _letter = getLetter(context, _tileHeight, details.globalPosition);
  234. _currentLetter =
  235. getLetter(context, _tileHeight, details.globalPosition);
  236. _jumpToIndex(_currentLetter);
  237. });
  238. },
  239. child: Column(
  240. children: _letters,
  241. ),
  242. );
  243. }
  244. @override
  245. Widget build(BuildContext context) {
  246. final List<Widget> _body = [];
  247. if (!_hasdeleteIcon) {
  248. members.sort((a, b) => a.nameTag.compareTo(b.nameTag));
  249. updateIndexPos(members);
  250. _body.addAll([
  251. ListView.builder(
  252. controller: _scrollController,
  253. itemBuilder: (BuildContext context, int index) {
  254. bool _isGroupTitle = true;
  255. GroupMemberModel _contact = members[index];
  256. if (index >= 1 &&
  257. _contact.nameTag == members[index - 1].nameTag) {
  258. _isGroupTitle = false;
  259. }
  260. return _MemberItem(
  261. userId: _contact.memberId,
  262. avatar: _contact.avtar,
  263. title: _contact.refName,
  264. isShowDivder: _isGroupTitle,
  265. onPressed: () {
  266. Navigator.of(context).pop(_contact);
  267. },
  268. groupTitle: _isGroupTitle ? _contact.nameTag : null);
  269. },
  270. itemCount: members.length),
  271. Positioned(
  272. width: Constants.IndexBarWidth,
  273. right: 0.0,
  274. top: 0.0,
  275. bottom: 0.0,
  276. child: Container(
  277. child: LayoutBuilder(
  278. builder: _buildIndexBar,
  279. ),
  280. ),
  281. )
  282. ]);
  283. } else {
  284. _body.add(ListView.builder(
  285. controller: _scrollController,
  286. itemBuilder: (BuildContext context, int index) {
  287. GroupMemberModel _contact = searchList[index];
  288. return _MemberItem(
  289. userId: _contact.memberId,
  290. avatar: _contact.avtar,
  291. title: _contact.refName,
  292. isShowDivder: true,
  293. onPressed: () {
  294. Navigator.of(context).pop(_contact);
  295. },
  296. groupTitle: null);
  297. },
  298. itemCount: searchList.length,
  299. ));
  300. }
  301. if (_currentLetter != null &&
  302. _currentLetter.isNotEmpty &&
  303. !_hasdeleteIcon) {
  304. _body.add(Center(
  305. child: Container(
  306. width: Constants.IndexLetterBoxSize,
  307. height: Constants.IndexLetterBoxSize,
  308. decoration: BoxDecoration(
  309. color: AppColors.IndexLetterBoxBgColor,
  310. borderRadius: BorderRadius.all(
  311. Radius.circular(Constants.IndexLetterBoxRadius)),
  312. ),
  313. child: Center(
  314. child:
  315. Text(_currentLetter, textScaleFactor: 1.0, style: AppStyles.IndexLetterBoxTextStyle),
  316. ),
  317. ),
  318. ));
  319. }
  320. return Scaffold(
  321. resizeToAvoidBottomPadding: false,
  322. backgroundColor: AppColors.NewAppbarBgColor,
  323. appBar: AppBar(
  324. title: Text(I18n.of(context).select_notice_people),
  325. centerTitle: true,
  326. elevation: 1,
  327. leading: CustomUI.buildCustomLeading(context),
  328. bottom: PreferredSize(
  329. preferredSize: Size.fromHeight(49),
  330. child: Container(
  331. alignment: Alignment.center,
  332. margin: EdgeInsets.only(bottom: 14, left: 12.5, right: 12.5),
  333. height: 35,
  334. decoration: BoxDecoration(
  335. color: const Color(0xFFEEEEEE),
  336. borderRadius: BorderRadius.all(Radius.circular(8))),
  337. child: TextField(
  338. keyboardAppearance: Brightness.light,
  339. keyboardType: TextInputType.text,
  340. textInputAction: TextInputAction.search,
  341. controller: _txtCtrl,
  342. cursorColor: Constants.BlueTextColor,
  343. maxLines: 1,
  344. style: TextStyle(
  345. textBaseline: TextBaseline.alphabetic, fontSize: 14.5),
  346. autofocus: false,
  347. inputFormatters: [
  348. LengthLimitingTextInputFormatter(50),
  349. ],
  350. focusNode: focusNode,
  351. decoration: InputDecoration(
  352. hintText: I18n.of(context).search,
  353. hintStyle: TextStyle(fontSize: 14.5),
  354. prefixIcon: Icon(
  355. IconData(
  356. 0xe664,
  357. fontFamily: Constants.IconFontFamily,
  358. ),
  359. color: const Color(0xFFA0A0A0),
  360. size: 18,
  361. ),
  362. suffixIcon: Padding(
  363. padding: EdgeInsetsDirectional.only(
  364. start: 2.0, end: _hasdeleteIcon ? 20.0 : 0),
  365. child: _hasdeleteIcon
  366. ? new InkWell(
  367. onTap: (() {
  368. setState(() {
  369. WidgetsBinding.instance
  370. .addPostFrameCallback(
  371. (_) => _txtCtrl.clear());
  372. _hasdeleteIcon = false;
  373. });
  374. }),
  375. child: Icon(
  376. Icons.clear,
  377. size: 18.0,
  378. color: Constants.BlackTextColor,
  379. ))
  380. : new Text('')),
  381. filled: true,
  382. fillColor: Colors.transparent,
  383. border: InputBorder.none,
  384. ),
  385. onChanged: (str) async {
  386. setState(() {
  387. if (str.isEmpty) {
  388. _hasdeleteIcon = false;
  389. } else {
  390. _hasdeleteIcon = true;
  391. searchList = CustomUI().getSearchResult(
  392. str, members == null ? [] : members);
  393. }
  394. });
  395. },
  396. onEditingComplete: () {}),
  397. )),
  398. ),
  399. body: Stack(
  400. children: _body,
  401. ));
  402. }
  403. }