new_social_index_page.dart 24 KB

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