user_friend_page.dart 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. import 'package:azlistview/azlistview.dart';
  2. import 'package:cached_network_image/cached_network_image.dart';
  3. import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
  4. import 'package:flutter/material.dart' hide NestedScrollView;
  5. import 'package:flutter_easyrefresh/easy_refresh.dart';
  6. import 'package:sport/bean/login.dart';
  7. import 'package:sport/bean/post_user.dart';
  8. import 'package:sport/bean/user_friend.dart';
  9. import 'package:sport/pages/social/chat_page.dart';
  10. import 'package:sport/pages/social/user_detail_page.dart';
  11. import 'package:sport/pages/social/user_friend_add_page.dart';
  12. import 'package:sport/provider/lib/provider_widget.dart';
  13. import 'package:sport/provider/lib/view_state_lifecycle.dart';
  14. import 'package:sport/provider/user_friend_model.dart';
  15. import 'package:sport/router/navigator_util.dart';
  16. import 'package:sport/utils/DateFormat.dart';
  17. import 'package:sport/utils/toast.dart';
  18. import 'package:sport/widgets/appbar.dart';
  19. import 'package:sport/widgets/dialog/alert_dialog.dart';
  20. import 'package:sport/widgets/dialog/request_dialog.dart';
  21. import 'package:sport/widgets/error.dart';
  22. import 'package:sport/widgets/image.dart';
  23. import 'package:sport/widgets/loading.dart';
  24. import 'package:sport/widgets/misc.dart';
  25. import 'package:sport/widgets/persistent_header.dart';
  26. import 'package:sport/widgets/space.dart';
  27. class UserFriendPage extends StatefulWidget {
  28. @override
  29. State<StatefulWidget> createState() => _PageState();
  30. }
  31. class _PageState extends State<UserFriendPage> {
  32. PageController _controller;
  33. bool _refresh = false;
  34. int _page = 0;
  35. @override
  36. void initState() {
  37. super.initState();
  38. }
  39. @override
  40. void dispose() {
  41. super.dispose();
  42. _controller?.dispose();
  43. }
  44. @override
  45. Widget build(BuildContext context) {
  46. final double tabHeader = 60;
  47. final double statusBarHeight = MediaQuery.of(context).padding.top;
  48. final double pinnedHeaderHeight =
  49. //statusBar height
  50. statusBarHeight +
  51. //pinned SliverAppBar height in header
  52. kToolbarHeight +
  53. tabHeader;
  54. return DefaultTabController(
  55. length: 3,
  56. child: Scaffold(
  57. backgroundColor: Colors.white,
  58. body: NestedScrollView(
  59. pinnedHeaderSliverHeightBuilder: () {
  60. return pinnedHeaderHeight;
  61. },
  62. innerScrollPositionKeyBuilder: () {
  63. TabController tabController = DefaultTabController.of(context);
  64. String index = 'Tab${tabController.index}';
  65. return Key(index);
  66. },
  67. headerSliverBuilder: (context, innerBoxIsScrolled) {
  68. return <Widget>[
  69. buildSliverAppBar(context, "好友列表",
  70. innerBoxIsScrolled: innerBoxIsScrolled,
  71. actions: <Widget>[
  72. IconButton(
  73. icon: Image.asset(
  74. "lib/assets/img/bbs_icon_addmore.png",
  75. width: 22.0,
  76. height: 22.0,
  77. ),
  78. // icon: Text(
  79. // "添加",
  80. // style: TextStyle(fontSize: 15, color: Theme.of(context).accentColor),
  81. // ),
  82. onPressed: () async {
  83. await NavigatorUtil.goPage(
  84. context, (context) => UserFriendAddPage());
  85. setState(() {
  86. _refresh = true;
  87. });
  88. await Future.delayed(Duration(seconds: 1));
  89. setState(() {
  90. _refresh = false;
  91. });
  92. },
  93. )
  94. ]),
  95. SliverPersistentHeader(
  96. delegate: PersistentHeader(
  97. min: tabHeader,
  98. max: tabHeader,
  99. child: Container(
  100. color: Colors.white,
  101. child: Column(
  102. children: <Widget>[
  103. Expanded(
  104. child: Center(
  105. child: Container(
  106. height: 35,
  107. child: TabBar(
  108. isScrollable: true,
  109. labelPadding:
  110. EdgeInsets.symmetric(horizontal: 32),
  111. indicatorWeight: 3,
  112. indicatorPadding:
  113. EdgeInsets.symmetric(horizontal: 6),
  114. tabs: <Widget>[
  115. Tab(text: '好友'),
  116. Tab(text: '我关注'),
  117. Tab(text: '关注我')
  118. ],
  119. onTap: (index) {
  120. _controller?.jumpToPage(index);
  121. },
  122. ),
  123. ),
  124. ),
  125. ),
  126. ],
  127. ),
  128. )),
  129. pinned: true,
  130. ),
  131. ];
  132. },
  133. body: _refresh
  134. ? RequestLoadingWidget()
  135. : PageView.builder(
  136. controller: _controller = PageController(initialPage: _page)
  137. ..addListener(() {
  138. _page = _controller.page.toInt();
  139. }),
  140. physics: NeverScrollableScrollPhysics(),
  141. itemCount: 3,
  142. itemBuilder: (_, index) => _PageDetailPage(index)),
  143. ),
  144. ),
  145. );
  146. }
  147. }
  148. class _PageDetailPage extends StatefulWidget {
  149. final int type;
  150. _PageDetailPage(this.type);
  151. @override
  152. State<StatefulWidget> createState() => _PageDetailState(type);
  153. }
  154. class _PageDetailState
  155. extends ViewStateLifecycle<_PageDetailPage, UserFriendModel> {
  156. TextEditingController _controller;
  157. FocusNode _focusNode;
  158. ValueNotifier<String> _searchValue = ValueNotifier<String>("");
  159. _PageDetailState(int type);
  160. @override
  161. void initState() {
  162. super.initState();
  163. _focusNode = FocusNode();
  164. _controller = new TextEditingController(text: '');
  165. }
  166. @override
  167. void dispose() {
  168. super.dispose();
  169. _focusNode?.dispose();
  170. _controller?.dispose();
  171. }
  172. @override
  173. Widget build(BuildContext context) {
  174. return ProviderWidget<UserFriendModel>(
  175. model: model,
  176. onModelReady: (model) => model.initData(),
  177. builder: (_, model, __) {
  178. return widget.type == 0
  179. ? Column(
  180. children: <Widget>[
  181. _searchWidget(context),
  182. Space(height: 16.0,),
  183. Expanded(
  184. child: AzListView(
  185. data: model.items,
  186. susItemHeight:200,
  187. itemCount: model.items.length,
  188. indexBarData: model.items.map((e) => "${e.getSuspensionTag()}").toList(),
  189. itemBuilder: (BuildContext context, int index) {
  190. return ListTile(
  191. leading: Container(
  192. width: 44,
  193. height: 44,
  194. decoration: BoxDecoration(
  195. shape: BoxShape.rectangle,
  196. borderRadius: BorderRadius.circular(4.0),
  197. image: DecorationImage(
  198. image: CachedNetworkImageProvider(model.items[index].user.avatar,)
  199. ),
  200. ),
  201. ),
  202. title: Text("${model.items[index].user.name}"),
  203. onTap: () {
  204. },
  205. );
  206. },
  207. physics: BouncingScrollPhysics(),
  208. padding: EdgeInsets.zero,
  209. susItemBuilder: (BuildContext context, int index) {
  210. return Column(
  211. children: <Widget>[
  212. Container(
  213. // height: 40,
  214. width: MediaQuery.of(context).size.width,
  215. padding: EdgeInsets.only(left: 16.0),
  216. // color: Color(0xFFF3F4F5),
  217. alignment: Alignment.centerLeft,
  218. child: Text(
  219. '${model.items[index].getSuspensionTag()}',
  220. softWrap: false,
  221. style: TextStyle(
  222. fontSize: 14.0,
  223. color: Color(0xff9999999),
  224. ),
  225. ),
  226. ),
  227. Divider(indent: 12.0,endIndent: 12.0,),
  228. ],
  229. );
  230. },
  231. indexBarOptions: IndexBarOptions(
  232. needRebuild: true,
  233. ignoreDragCancel: true,
  234. textStyle: TextStyle(color:Color(0xff999999),fontSize: 12.0),
  235. selectTextStyle: TextStyle(fontSize: 20, color: Theme.of(context).accentColor),
  236. // downTextStyle: TextStyle(fontSize: 12, color: Theme.of(context).accentColor),
  237. // selectItemDecoration: BoxDecoration(color: Colors.green),
  238. // downItemDecoration:
  239. // BoxDecoration(color: Colors.green),
  240. indexHintWidth: 33,
  241. indexHintHeight: 33,
  242. indexHintDecoration: BoxDecoration(
  243. image: DecorationImage(
  244. image: AssetImage("lib/assets/img/friendlist_bg_letter.png"),
  245. fit: BoxFit.contain,
  246. ),
  247. ),
  248. indexHintTextStyle:TextStyle(fontSize: 18.0,color:Colors.white),
  249. indexHintAlignment: Alignment.centerRight,
  250. indexHintChildAlignment: Alignment(-0.25, 0.0),
  251. indexHintOffset: Offset(0, 0),
  252. ),
  253. ),
  254. ),
  255. ],
  256. ) : EasyRefresh.custom(
  257. firstRefresh: false,
  258. onRefresh: () => model.refresh(),
  259. onLoad: () => model.loadMore(),
  260. enableControlFinishRefresh: true,
  261. controller: model.refreshController,
  262. header: buildClassicalHeader(),
  263. footer: buildClassicalFooter(),
  264. slivers: <Widget>[
  265. SliverToBoxAdapter(
  266. child: _searchWidget(context),
  267. ),
  268. if (model.isBusy)
  269. SliverToBoxAdapter(
  270. child: RequestLoadingWidget(),
  271. ),
  272. SliverList(
  273. delegate: SliverChildBuilderDelegate(
  274. (context, index) {
  275. return _buildItem(model.list[index]);
  276. },
  277. childCount: model.list.length,
  278. ),
  279. ),
  280. if (model.isError)
  281. SliverToBoxAdapter(
  282. child: RequestErrorWidget(
  283. () {
  284. model.initData();
  285. },
  286. msg: widget.type == 0
  287. ? "暂无好友~"
  288. : widget.type == 1 ? "暂无关注~" : "暂无关注我的~",
  289. assets: "emptypage-image-nomotion.png",
  290. ),
  291. ),
  292. ],
  293. );
  294. });
  295. }
  296. Widget _buildItem(UserFriend user) {
  297. var _padding = EdgeInsets.symmetric(horizontal: 4.0);
  298. Widget child;
  299. if (widget.type == 2) {
  300. child = Row(
  301. children: <Widget>[
  302. CircleAvatar(
  303. backgroundImage: userAvatarProvider(user?.socialInfo?.avatar),
  304. radius: 22,
  305. ),
  306. SizedBox(
  307. width: 8,
  308. ),
  309. Expanded(
  310. child: Column(
  311. crossAxisAlignment: CrossAxisAlignment.start,
  312. children: <Widget>[
  313. Text(
  314. "${user?.socialInfo?.name}",
  315. style: Theme.of(context)
  316. .textTheme
  317. .headline3
  318. .copyWith(fontWeight: FontWeight.normal),
  319. ),
  320. SizedBox(
  321. height: 5,
  322. ),
  323. RichText(
  324. text: TextSpan(
  325. style: Theme.of(context).textTheme.subtitle2,
  326. children: <InlineSpan>[
  327. TextSpan(
  328. text: '${DateFormat.formatCreateAt(user?.createdAt)}',
  329. style: Theme.of(context).textTheme.bodyText1,
  330. ),
  331. user.makeFrom == 'follow'
  332. ? TextSpan(
  333. text: '通过线上',
  334. style: Theme.of(context)
  335. .textTheme
  336. .bodyText1
  337. .copyWith(
  338. color: Theme.of(context).accentColor),
  339. )
  340. : TextSpan(
  341. text: '通过扫码',
  342. style: Theme.of(context)
  343. .textTheme
  344. .bodyText1
  345. .copyWith(
  346. color: Theme.of(context).accentColor),
  347. ),
  348. TextSpan(
  349. text: "关注了你",
  350. style: Theme.of(context).textTheme.bodyText1,
  351. )
  352. ]),
  353. ),
  354. // TextSpan(
  355. // "${DateFormat.formatCreateAt(user?.createdAt)}通过${user.makeFrom}关注了你",
  356. //
  357. // ),
  358. ],
  359. ),
  360. ),
  361. if (user?.isFriends == "0")
  362. user?.isIgnore == 0
  363. ? GestureDetector(
  364. child: Container(
  365. width: 64,
  366. height: 30,
  367. alignment: Alignment.center,
  368. margin: _padding,
  369. child: Text(
  370. "关注",
  371. strutStyle: fixedLine,
  372. style: Theme.of(context).textTheme.bodyText2,
  373. ),
  374. decoration: BoxDecoration(
  375. borderRadius: BorderRadius.circular(20),
  376. border: Border.all(
  377. color: Color(0xff999999),
  378. width: .5,
  379. ),
  380. ),
  381. ),
  382. onTap: () async {
  383. await request(context, () async {
  384. var resp = await model.api
  385. .userIgnoreFollow(uid: user?.socialInfo?.id)
  386. .catchError((onError) {});
  387. if (resp?.code == 0) {
  388. setState(() {
  389. user.isIgnore = 1;
  390. });
  391. }
  392. });
  393. },
  394. )
  395. : Container(
  396. width: 64,
  397. height: 30,
  398. margin: _padding,
  399. alignment: Alignment.center,
  400. child: Text(
  401. "已忽略",
  402. strutStyle: fixedLine,
  403. style: Theme.of(context).textTheme.bodyText2,
  404. ),
  405. ),
  406. if (user?.isIgnore == 0)
  407. user.isFriends == "1"
  408. ? Container(
  409. width: 64,
  410. height: 30,
  411. margin: _padding,
  412. alignment: Alignment.center,
  413. child: Text(
  414. "已关注",
  415. strutStyle: fixedLine,
  416. style: Theme.of(context)
  417. .textTheme
  418. .bodyText2
  419. .copyWith(color: Theme.of(context).accentColor),
  420. ),
  421. )
  422. : GestureDetector(
  423. child: Container(
  424. width: 64,
  425. height: 30,
  426. margin: _padding,
  427. alignment: Alignment.center,
  428. child: Text(
  429. "关注",
  430. strutStyle: fixedLine,
  431. style: Theme.of(context)
  432. .textTheme
  433. .bodyText2
  434. .copyWith(color: Theme.of(context).accentColor),
  435. ),
  436. decoration: BoxDecoration(
  437. borderRadius: BorderRadius.circular(20),
  438. border: Border.all(
  439. color: Theme.of(context).accentColor,
  440. width: .5,
  441. ),
  442. ),
  443. ),
  444. onTap: () async {
  445. if (user.isFriends == "1") return;
  446. await request(context, () async {
  447. var resp = await model.api
  448. .userFollow(uid: user?.socialInfo?.id)
  449. .catchError((onError) {});
  450. if (resp?.code == 0) {
  451. ToastUtil.show("关注成功");
  452. setState(() {
  453. user.isFriends = "1";
  454. });
  455. }
  456. });
  457. },
  458. )
  459. ],
  460. );
  461. } else if (widget.type == 1) {
  462. child = Row(
  463. children: <Widget>[
  464. CircleAvatar(
  465. backgroundImage: userAvatarProvider(user?.socialInfo?.avatar),
  466. radius: 22,
  467. ),
  468. SizedBox(
  469. width: 8,
  470. ),
  471. Expanded(
  472. child: Text(
  473. "${user?.socialInfo?.name}",
  474. style: Theme.of(context)
  475. .textTheme
  476. .headline3
  477. .copyWith(fontWeight: FontWeight.normal),
  478. ),
  479. ),
  480. GestureDetector(
  481. child: user.isFriends == "1"
  482. ? Row(
  483. children: <Widget>[
  484. Text("互关好友",
  485. style: TextStyle(
  486. fontSize: 14.0, color: Color(0xff999999)))
  487. ],
  488. )
  489. : Text("已关注",
  490. style: TextStyle(fontSize: 14.0, color: Color(0xff999999))),
  491. // Container(
  492. // width: 82,
  493. // height: 30,
  494. // alignment: Alignment.center,
  495. // child:
  496. // decoration: BoxDecoration(
  497. // borderRadius: BorderRadius.circular(20),
  498. // border: Border.all(
  499. // color: Color(0xffDCDCDC),
  500. // width: .5,
  501. // ),
  502. // ),
  503. // ),
  504. onTap: () async {
  505. bool flag;
  506. if (user.isFriends == "1") {
  507. flag = await showDialog(
  508. context: context,
  509. builder: (context) => CustomAlertDialog(
  510. title: '是否取消关注,解除好友关系',
  511. ok: () {
  512. Navigator.of(context).pop(true);
  513. },
  514. ));
  515. } else {
  516. flag = true;
  517. }
  518. if (flag) {
  519. await request(context, () async {
  520. var resp = await model.api
  521. .userUnFollow(uid: user?.socialInfo?.id)
  522. .catchError((onError) {});
  523. if (resp?.code == 0) {
  524. ToastUtil.show("取关成功");
  525. setState(() {
  526. model.list?.remove(user);
  527. if (model.list?.isEmpty == true) {
  528. model.initData();
  529. }
  530. });
  531. }
  532. });
  533. }
  534. },
  535. )
  536. ],
  537. );
  538. } else {
  539. // child = Row(
  540. // children: <Widget>[
  541. // CircleAvatar(
  542. // backgroundImage: userAvatarProvider(user?.socialInfo?.avatar),
  543. // radius: 22,
  544. // ),
  545. // SizedBox(
  546. // width: 8,
  547. // ),
  548. // Expanded(
  549. // child: Column(
  550. // crossAxisAlignment: CrossAxisAlignment.start,
  551. // children: <Widget>[
  552. // Text(
  553. // "${user?.socialInfo?.name}",
  554. // style: Theme.of(context)
  555. // .textTheme
  556. // .headline3
  557. // .copyWith(fontWeight: FontWeight.normal),
  558. // ),
  559. // SizedBox(
  560. // height: 5,
  561. // ),
  562. // Text(
  563. // "${DateFormat.formatCreateAt(user?.updatedAt)}在线",
  564. // style: Theme.of(context).textTheme.bodyText1,
  565. // ),
  566. // ],
  567. // ),
  568. // ),
  569. // ],
  570. // );
  571. }
  572. // return Column(
  573. // children: <Widget>[
  574. // Padding(
  575. // padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12.0),
  576. // child: InkWell(
  577. // onTap: () async {
  578. //// if(widget.type == 0) await NavigatorUtil.goPage(context, (context) => ChatPage(user?.socialInfo));
  579. //// else
  580. // await NavigatorUtil.goPage(
  581. // context,
  582. // (context) => UserDetailPage(
  583. // PostUser(
  584. // id: "${user?.socialInfo?.id}",
  585. // name: user?.socialInfo?.name,
  586. // avatar: user?.socialInfo?.avatar),
  587. // userFriends: model.list));
  588. // setState(() {});
  589. // },
  590. // child: child),
  591. // ),
  592. // Divider(
  593. // indent: 12.0,
  594. // height: 1,
  595. // endIndent: 12.0,
  596. // )
  597. // ],
  598. // );
  599. return Column(
  600. children: <Widget>[
  601. Padding(
  602. padding: const EdgeInsets.all(12.0),
  603. child: InkWell(
  604. onTap: () async {
  605. await NavigatorUtil.goPage(
  606. context,
  607. (context) => UserDetailPage(
  608. PostUser(
  609. id: "${user?.socialInfo?.id}",
  610. name: user?.socialInfo?.name,
  611. avatar: user?.socialInfo?.avatar),
  612. userFriends: model.list));
  613. setState(() {});
  614. },
  615. child: child),
  616. ),
  617. Divider(
  618. height: 1,
  619. )
  620. ],
  621. );
  622. }
  623. Widget _searchWidget(BuildContext context) {
  624. return Container(
  625. margin: EdgeInsets.fromLTRB(12.0, 0, 12.0, 0),
  626. height: 35,
  627. // padding: EdgeInsets.symmetric(horizontal: 12.0),
  628. // padding: EdgeInsets.fromLTRB(12.0, 0, 12.0, 0),
  629. decoration: BoxDecoration(
  630. color: Color(0xffF1F1F1),
  631. shape: BoxShape.rectangle,
  632. borderRadius: BorderRadius.all(Radius.circular(50)),
  633. ),
  634. child: Row(
  635. children: <Widget>[
  636. SizedBox(
  637. width: 12.0,
  638. ),
  639. Image.asset("lib/assets/img/searchbar_icon_search.png"),
  640. SizedBox(
  641. width: 6,
  642. ),
  643. Expanded(
  644. child: TextField(
  645. controller: _controller,
  646. maxLines: 1,
  647. focusNode: _focusNode,
  648. strutStyle: StrutStyle(forceStrutHeight: true),
  649. decoration: InputDecoration(
  650. hintText: '输入账号/用户昵称',
  651. border: InputBorder.none,
  652. contentPadding: EdgeInsets.symmetric(
  653. vertical: 11.5,
  654. ),
  655. hintStyle: TextStyle(
  656. color: Color(0xff999999),
  657. ),
  658. ),
  659. onChanged: (value) {
  660. _searchValue.value = value;
  661. setState(() {});
  662. },
  663. onSubmitted: (value) {
  664. model.submitValue(value);
  665. // _searchModel.setKeyword(value);
  666. // Provider.of<SearchModel>(context, listen: false).queryValue(value);
  667. },
  668. style: TextStyle(
  669. color: Color(0xff333333),
  670. ),
  671. ),
  672. ),
  673. Visibility(
  674. visible: _searchValue.value?.isNotEmpty == true,
  675. child: GestureDetector(
  676. onTap: () {
  677. model.submitValue(null);
  678. _controller.clear();
  679. },
  680. child: Padding(
  681. padding: const EdgeInsets.all(8.0),
  682. child: Image.asset("lib/assets/img/searchbar_btn_no.png"),
  683. ),
  684. ))
  685. ],
  686. ),
  687. );
  688. }
  689. @override
  690. UserFriendModel createModel() => UserFriendModel(widget.type);
  691. }