Hibok
您不能選擇超過 %s 個話題 話題必須以字母或數字為開頭,可包含連接號 ('-') 且最長為 35 個字
 
 
 
 
 
 

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