new_social_index_page.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. import 'dart:math';
  2. import 'dart:ui';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/rendering.dart';
  6. import 'package:flutter/services.dart';
  7. import 'package:flutter_easyrefresh/easy_refresh.dart';
  8. import 'package:provider/provider.dart';
  9. import 'package:shared_preferences/shared_preferences.dart';
  10. import 'package:sport/bean/forum.dart';
  11. import 'package:sport/bean/post.dart';
  12. import 'package:sport/pages/social/post_page.dart';
  13. import 'package:sport/pages/social/post_widget.dart';
  14. import 'package:sport/pages/social/search_page.dart';
  15. import 'package:sport/provider/lib/provider_widget.dart';
  16. import 'package:sport/provider/lib/view_state.dart';
  17. import 'package:sport/provider/lib/view_state_lifecycle.dart';
  18. import 'package:sport/provider/social_detail_model.dart';
  19. import 'package:sport/provider/social_index_model.dart';
  20. import 'package:sport/router/navigator_util.dart';
  21. import 'package:sport/router/routes.dart';
  22. import 'package:sport/services/api/inject_api.dart';
  23. import 'package:sport/services/api/resp.dart';
  24. import 'package:sport/services/userid.dart';
  25. import 'package:sport/widgets/appbar.dart';
  26. import 'package:sport/widgets/error.dart';
  27. import 'package:sport/widgets/loading.dart';
  28. import 'package:sport/widgets/misc.dart';
  29. import 'package:sport/widgets/persistent_header.dart';
  30. import 'package:sport/widgets/space.dart';
  31. final List<String> _tabs = ['热门', '关注', '最新', '精华', '官方'];
  32. class NewSocialIndexPage extends StatefulWidget {
  33. NewSocialIndexPage();
  34. @override
  35. State<StatefulWidget> createState() => _PageState();
  36. }
  37. class _PageState extends ViewStateLifecycle<NewSocialIndexPage, SocialDetailModel> with TickerProviderStateMixin, UserId, InjectApi {
  38. ScrollController _controller;
  39. double _expandedHeight = 0;
  40. int _brightness = 0;
  41. TabController _tabController;
  42. Future<RespPage<Post>> _getPostListByOfficial;
  43. final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
  44. @override
  45. SocialDetailModel createModel() => SocialDetailModel(0);
  46. SocialIndexModel indexModel = new SocialIndexModel();
  47. // 右边draw中的 当前选择的索引...
  48. ValueNotifier<int> drawOneIndex = ValueNotifier(0);
  49. ValueNotifier<int> drawTwoIndex = ValueNotifier(0);
  50. String forumId = "";
  51. String isOfficial;
  52. List buttonList = [];
  53. List<Forum> buttonData = [
  54. new Forum(
  55. gameName: "全部",
  56. ),
  57. new Forum(
  58. gameName: "用户发布",
  59. forumId: "0",
  60. ),
  61. new Forum(gameName: "官方发布", forumId: "1"),
  62. ];
  63. ValueNotifier<bool> isShowSelect = ValueNotifier(false);
  64. void _refresh() {
  65. model?.setForumIdAndOrigin(_tabController.index, forumId, isOfficial);
  66. _controller.animateTo(0, duration: Duration(milliseconds: 100), curve: Curves.ease);
  67. }
  68. @override
  69. void initState() {
  70. super.initState();
  71. _tabController = TabController(length: _tabs.length, initialIndex: 0, vsync: this)
  72. ..addListener(() {
  73. if (_tabController.index.toDouble() == _tabController.animation.value) {
  74. _refresh();
  75. }
  76. });
  77. _controller = ScrollController()
  78. ..addListener(() {
  79. if (_controller.position.pixels >= _expandedHeight - 70) {
  80. if (_brightness == 0) {
  81. setState(() {
  82. _brightness = 1;
  83. });
  84. }
  85. } else {
  86. if (_brightness == 1) {
  87. setState(() {
  88. _brightness = 0;
  89. });
  90. }
  91. }
  92. });
  93. initButtonList();
  94. }
  95. @override
  96. void dispose() {
  97. super.dispose();
  98. _controller?.dispose();
  99. _tabController?.dispose();
  100. PaintingBinding.instance.imageCache.clear();
  101. }
  102. Future<int> _getCount() async {
  103. int count = 0;
  104. try {
  105. count += (await model.api.getNoticeCount("comment", "0")).data;
  106. } catch (e) {
  107. print(e);
  108. }
  109. if (count == 0) {
  110. try {
  111. count += (await model.api.getNoticeCount("like", "0")).data;
  112. } catch (e) {
  113. print(e);
  114. }
  115. }
  116. return count;
  117. }
  118. Widget _buildSearchWidget() {
  119. return GestureDetector(
  120. onTap: () {
  121. Navigator.push(context, new MaterialPageRoute(builder: (context) => SearchPage()));
  122. },
  123. child: Container(
  124. // margin: EdgeInsets.fromLTRB(12.0, 0, 12.0, 12.0),
  125. width: MediaQuery.of(context).size.width * 0.8,
  126. height: 35,
  127. padding: EdgeInsets.fromLTRB(12.0, 0, 12.0, 0),
  128. decoration: BoxDecoration(
  129. color: Color(0xffF1F1F1),
  130. shape: BoxShape.rectangle,
  131. borderRadius: BorderRadius.all(Radius.circular(50)),
  132. ),
  133. child: Row(
  134. children: <Widget>[
  135. Image.asset("lib/assets/img/searchbar_icon_search.png"),
  136. Space(
  137. width: 4,
  138. ),
  139. Text(
  140. "输入关键词",
  141. strutStyle: StrutStyle(forceStrutHeight: true),
  142. style: TextStyle(fontSize: 14, color: Color(0xff999999)),
  143. ),
  144. ],
  145. ),
  146. ),
  147. );
  148. }
  149. // @title button 的title
  150. // @index 当前显示的下标
  151. // @targetIndex 目标下标
  152. // @type origin / project
  153. Widget _buildDrawerButtonItem(Forum data, int index, ValueNotifier<int> targetIndex, String type) {
  154. return InkWell(
  155. child: Container(
  156. // height: 35.0,
  157. decoration: BoxDecoration(
  158. color: index == targetIndex.value ? Theme.of(context).accentColor : Colors.white,
  159. borderRadius: BorderRadius.all(Radius.circular(20.0)),
  160. border: Border.all(color: index == targetIndex.value ? Colors.white : Theme.of(context).dividerTheme.color)),
  161. padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 25.0),
  162. child: Text(
  163. data.gameName,
  164. strutStyle: StrutStyle(forceStrutHeight: true),
  165. style: TextStyle(fontSize: 14.0, color: index == targetIndex.value ? Colors.white : Color(0xff999999)),
  166. ),
  167. ),
  168. onTap: () {
  169. targetIndex.value = index;
  170. if (type == "project") {
  171. forumId = data.forumId;
  172. } else if (type == "origin") {
  173. isOfficial = data.forumId;
  174. }
  175. if (drawOneIndex.value == 0 && drawTwoIndex.value == 0) {
  176. isShowSelect.value = false;
  177. } else {
  178. isShowSelect.value = true;
  179. }
  180. model.setForumIdAndOrigin(_tabController.index, forumId, isOfficial);
  181. },
  182. );
  183. }
  184. Widget _buildDrawButtonContainer(Widget buttons, {String type = 'top'}) {
  185. return Padding(
  186. padding: EdgeInsets.only(left: 16.0),
  187. child: Column(
  188. crossAxisAlignment: CrossAxisAlignment.start,
  189. children: <Widget>[
  190. type == 'top'
  191. ? Space(
  192. height: 21.0,
  193. )
  194. : Container(),
  195. type == 'bottom'
  196. ? Space(
  197. height: 43.0,
  198. )
  199. : Container(),
  200. Text("运动项目", style: TextStyle(color: Color(0xff333333), fontSize: 16.0, fontWeight: FontWeight.bold)),
  201. Space(
  202. height: 16.0,
  203. ),
  204. buttons,
  205. type == 'bottom'
  206. ? Space(
  207. height: 21.0,
  208. )
  209. : Container(),
  210. ],
  211. ),
  212. );
  213. }
  214. Widget _buildButtons(ValueNotifier<int> targetIndex, {List<dynamic> list}) {
  215. return ValueListenableBuilder(
  216. valueListenable: targetIndex,
  217. builder: (BuildContext context, int value, Widget child) => Wrap(
  218. alignment: WrapAlignment.start,
  219. crossAxisAlignment: WrapCrossAlignment.start,
  220. spacing: 8.0,
  221. runSpacing: 16.0,
  222. children: list != null
  223. ? list.asMap().entries.map((e) => _buildDrawerButtonItem(e.value, e.key, targetIndex, "project")).toList()
  224. : buttonData.asMap().entries.map((e) => _buildDrawerButtonItem(e.value, e.key, targetIndex, "origin")).toList(),
  225. ),
  226. );
  227. }
  228. Widget _buildEndrawer() {
  229. return Container(
  230. height: double.infinity,
  231. width: MediaQuery.of(context).size.width * 0.65,
  232. decoration: BoxDecoration(
  233. color: Colors.white,
  234. borderRadius: BorderRadius.only(
  235. topLeft: Radius.circular(10.0),
  236. bottomLeft: Radius.circular(10.0),
  237. ),
  238. ),
  239. child: Column(
  240. crossAxisAlignment: CrossAxisAlignment.start,
  241. children: <Widget>[
  242. _buildDrawButtonContainer(
  243. _buildButtons(drawOneIndex, list: buttonList),
  244. type: "bottom",
  245. ),
  246. Divider(),
  247. _buildDrawButtonContainer(
  248. _buildButtons(drawTwoIndex),
  249. type: "top",
  250. ),
  251. ],
  252. ));
  253. }
  254. Widget _buildFilterButton(String name, ValueNotifier<int> index, String type) {
  255. return InkWell(
  256. child: Container(
  257. padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 16.0),
  258. decoration: BoxDecoration(border: Border.all(color: Theme.of(context).accentColor), borderRadius: BorderRadius.all(Radius.circular(44.0))),
  259. child: Row(
  260. crossAxisAlignment: CrossAxisAlignment.center,
  261. children: <Widget>[
  262. Text(
  263. "$name",
  264. strutStyle: StrutStyle(forceStrutHeight: true),
  265. style: TextStyle(color: Theme.of(context).accentColor, fontSize: 12.0),
  266. ),
  267. Space(
  268. width: 5.0,
  269. ),
  270. Image.asset(
  271. "lib/assets/img/btn_close_yellow.png",
  272. width: 7.0,
  273. height: 7.0,
  274. )
  275. ],
  276. ),
  277. ),
  278. onTap: () {
  279. type == "one" ? model.setForumIdAndOrigin(_tabController.index, "", isOfficial) : model.setForumIdAndOrigin(_tabController.index, forumId, "");
  280. index.value = 0;
  281. },
  282. );
  283. }
  284. initButtonList() async {
  285. RespList<Forum> data = await api.getForumIndex();
  286. setState(() {
  287. buttonList = data.results;
  288. if (data.results.length > 0) {
  289. buttonList.insert(0, new Forum(gameName: "全部"));
  290. }
  291. });
  292. }
  293. @override
  294. Widget build(BuildContext context) {
  295. return Scaffold(
  296. key: _scaffoldKey,
  297. backgroundColor: Colors.white,
  298. endDrawer: _buildEndrawer(),
  299. body: ProviderWidget2<SocialDetailModel, SocialIndexModel>(
  300. model1: model,
  301. model2: indexModel,
  302. onModelReady: (model1, model2) => model.initData(),
  303. builder: (_, model, model2, __) {
  304. return EasyRefresh.builder(
  305. controller: model.refreshController,
  306. enableControlFinishRefresh: true,
  307. enableControlFinishLoad: true,
  308. onRefresh: () => model.refresh(),
  309. onLoad: model.isIdle ? () => model.loadMore() : null,
  310. header: buildClassicalHeader(),
  311. footer: buildClassicalFooter(),
  312. builder: (context, physics, header, footer) {
  313. return AnnotatedRegion<SystemUiOverlayStyle>(
  314. value: SystemUiOverlayStyle.dark,
  315. child: Material(
  316. color: Colors.white,
  317. child: SafeArea(
  318. child: CustomScrollView(
  319. controller: _controller,
  320. physics: physics,
  321. slivers: <Widget>[
  322. buildSliverAppBar(context, "社区",
  323. canBack: false,
  324. pinned: false,
  325. height: 100.0,
  326. padding: const EdgeInsets.fromLTRB(12.0, 0, 0, 6.0),
  327. actions: <Widget>[
  328. Selector<SocialIndexModel, ViewState>(
  329. selector: (_, SocialIndexModel model) => model.viewState,
  330. shouldRebuild: (_, v) => v == ViewState.idle,
  331. builder: (BuildContext context, ViewState value, Widget child) {
  332. return IconButton(
  333. icon: Stack(
  334. alignment: Alignment.center,
  335. children: <Widget>[
  336. Image.asset("lib/assets/img/bbs_icon_news.png"),
  337. FutureBuilder(
  338. future: SharedPreferences.getInstance(),
  339. builder: (BuildContext context, AsyncSnapshot<SharedPreferences> snapshot) {
  340. if (snapshot.connectionState == ConnectionState.done) {
  341. if (snapshot.data.getBool("message_setting") ?? true == true) {
  342. return FutureBuilder(
  343. future: _getCount(),
  344. builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
  345. if (snapshot.data != null && snapshot.data > 0) {
  346. return Align(
  347. alignment: Alignment.topRight,
  348. child: Container(
  349. margin: const EdgeInsets.only(top: 6.0),
  350. width: 10,
  351. height: 10,
  352. decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.red),
  353. child: Center(
  354. child: Text(""),
  355. ),
  356. ),
  357. );
  358. }
  359. return Container();
  360. });
  361. }
  362. }
  363. return Container();
  364. }),
  365. ],
  366. ),
  367. onPressed: () async {
  368. await NavigatorUtil.go(context, Routes.socialMessage);
  369. setState(() {});
  370. },
  371. );
  372. }),
  373. ],
  374. paddingLeading: false),
  375. header,
  376. SliverPersistentHeader(
  377. delegate: PersistentHeader(
  378. min: 50,
  379. max: 50,
  380. child: Container(
  381. color: Colors.white,
  382. padding: EdgeInsets.only(bottom: 10),
  383. child: TabBar(
  384. isScrollable: true,
  385. indicatorPadding: EdgeInsets.symmetric(horizontal: 8),
  386. indicatorWeight: 3,
  387. controller: _tabController,
  388. tabs: _tabs.map((e) => Tab(text: e)).toList(),
  389. ),
  390. )),
  391. pinned: true,
  392. ),
  393. SliverToBoxAdapter(
  394. child: Padding(
  395. padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 12.0),
  396. child: Row(
  397. children: <Widget>[
  398. _buildSearchWidget(),
  399. Space(
  400. width: 15.0,
  401. ),
  402. ValueListenableBuilder(
  403. valueListenable: isShowSelect,
  404. builder: (context, flag, child) => InkWell(
  405. child: flag
  406. ? Image.asset(
  407. "lib/assets/img/bbs_icon_choose_press.png",
  408. width: 22,
  409. height: 22,
  410. )
  411. : Image.asset(
  412. "lib/assets/img/bbs_icon_choose_normal.png",
  413. width: 22,
  414. height: 22,
  415. ),
  416. onTap: () => _scaffoldKey.currentState.openEndDrawer(),
  417. ),
  418. ),
  419. ],
  420. ),
  421. ),
  422. ),
  423. SliverToBoxAdapter(
  424. child: Padding(
  425. padding: EdgeInsets.only(left: 12.0),
  426. child: Row(
  427. children: <Widget>[
  428. if (drawOneIndex.value != 0) _buildFilterButton('${buttonList[drawOneIndex.value].gameName}', drawOneIndex, "one"),
  429. Space(
  430. width: 10.0,
  431. ),
  432. if (drawTwoIndex.value != 0) _buildFilterButton('${buttonData[drawTwoIndex.value].gameName}', drawTwoIndex, "two"),
  433. ],
  434. ),
  435. ),
  436. ),
  437. if (model.isBusy)
  438. SliverToBoxAdapter(
  439. child: RequestLoadingWidget(),
  440. ),
  441. if (model.isEmpty)
  442. SliverFillRemaining(
  443. child: Center(
  444. child: RequestErrorWidget(
  445. null,
  446. msg: "暂无帖子~",
  447. assets: RequestErrorWidget.ASSETS_NO_INVITATION,
  448. ),
  449. ),
  450. ),
  451. if (model.isIdle)
  452. SliverList(
  453. delegate: SliverChildBuilderDelegate(
  454. (context, index) {
  455. Post post = model.list[index];
  456. return PostWidget(post, model, selfId == post.userId);
  457. },
  458. childCount: model.list.length,
  459. ),
  460. ),
  461. ],
  462. ),
  463. ),
  464. ),
  465. );
  466. });
  467. },
  468. ),
  469. floatingActionButtonLocation: const _EndFloatFloatingActionButtonLocation(),
  470. floatingActionButtonAnimator:const _ScalingFabMotionAnimator(),
  471. floatingActionButton: InkWell(
  472. child: Image.asset("lib/assets/img/bbs_icon_edit.png"),
  473. onTap: () async {
  474. // print('FloatingActionButton');
  475. var result = await NavigatorUtil.goPage(
  476. context,
  477. (context) => PostPage(
  478. "",
  479. forums: buttonList,
  480. ));
  481. if (result == true) {
  482. if (_tabController.index.toDouble() == _tabController.animation.value) {
  483. _refresh();
  484. } else {
  485. _tabController?.index = 2;
  486. }
  487. }
  488. },
  489. ),
  490. );
  491. }
  492. }
  493. class _EndFloatFloatingActionButtonLocation extends FloatingActionButtonLocation {
  494. const _EndFloatFloatingActionButtonLocation();
  495. double _rightOffset(ScaffoldPrelayoutGeometry scaffoldGeometry, {double offset = 0.0}) {
  496. return scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.minInsets.right - scaffoldGeometry.floatingActionButtonSize.width + offset;
  497. }
  498. double getDockedY(ScaffoldPrelayoutGeometry scaffoldGeometry) {
  499. final double contentBottom = scaffoldGeometry.contentBottom;
  500. final double bottomSheetHeight = scaffoldGeometry.bottomSheetSize.height;
  501. final double fabHeight = scaffoldGeometry.floatingActionButtonSize.height;
  502. final double snackBarHeight = scaffoldGeometry.snackBarSize.height;
  503. double fabY = contentBottom - fabHeight / 2.0;
  504. // The FAB should sit with a margin between it and the snack bar.
  505. if (snackBarHeight > 0.0) fabY = min(fabY, contentBottom - snackBarHeight - fabHeight - kFloatingActionButtonMargin);
  506. // The FAB should sit with its center in front of the top of the bottom sheet.
  507. if (bottomSheetHeight > 0.0) fabY = min(fabY, contentBottom - bottomSheetHeight - fabHeight / 2.0);
  508. final double maxFabY = scaffoldGeometry.scaffoldSize.height - fabHeight;
  509. return min(maxFabY, fabY);
  510. }
  511. @override
  512. Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
  513. final double fabX = _rightOffset(scaffoldGeometry, offset: -8.0);
  514. final double fabY = getDockedY(scaffoldGeometry);
  515. return Offset(fabX, fabY - 8);
  516. }
  517. @override
  518. String toString() => 'FloatingActionButtonLocation.endFloat';
  519. }
  520. class _ScalingFabMotionAnimator extends FloatingActionButtonAnimator {
  521. const _ScalingFabMotionAnimator();
  522. @override
  523. Offset getOffset({ Offset begin, Offset end, double progress }) {
  524. return end;
  525. }
  526. @override
  527. Animation<double> getScaleAnimation({ Animation<double> parent }) {
  528. return AlwaysStoppedAnimation(1.0);
  529. }
  530. @override
  531. Animation<double> getRotationAnimation({ Animation<double> parent }) {
  532. return AlwaysStoppedAnimation(1.0);
  533. }
  534. // If the animation was just starting, we'll continue from where we left off.
  535. // If the animation was finishing, we'll treat it as if we were starting at that point in reverse.
  536. // This avoids a size jump during the animation.
  537. @override
  538. double getAnimationRestart(double previousValue) => 1.0;
  539. }