social_detail_page.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import 'dart:ui';
  2. import 'package:cached_network_image/cached_network_image.dart';
  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:sport/bean/forum.dart';
  9. import 'package:sport/bean/post.dart';
  10. import 'package:sport/pages/social/post_detail_page.dart';
  11. import 'package:sport/pages/social/post_page.dart';
  12. import 'package:sport/pages/social/post_widget.dart';
  13. import 'package:sport/provider/lib/provider_widget.dart';
  14. import 'package:sport/provider/lib/view_state_lifecycle.dart';
  15. import 'package:sport/provider/social_detail_model.dart';
  16. import 'package:sport/router/navigator_util.dart';
  17. import 'package:sport/services/api/resp.dart';
  18. import 'package:sport/services/userid.dart';
  19. import 'package:sport/widgets/appbar.dart';
  20. import 'package:sport/widgets/error.dart';
  21. import 'package:sport/widgets/loading.dart';
  22. import 'package:sport/widgets/misc.dart';
  23. import 'package:sport/widgets/persistent_header.dart';
  24. import 'package:sport/widgets/space.dart';
  25. class SocialDetailPage extends StatefulWidget {
  26. final Forum forum;
  27. final int index;
  28. SocialDetailPage(this.forum, {this.index});
  29. @override
  30. State<StatefulWidget> createState() => _PageState();
  31. }
  32. class _PageState extends ViewStateLifecycle<SocialDetailPage, SocialDetailModel>
  33. with TickerProviderStateMixin, UserId {
  34. ScrollController _controller;
  35. double _expandedHeight = 0;
  36. int _brightness = 0;
  37. TabController _tabController;
  38. final List<String> _tabs = ['热门', '关注', '最新', '精华', '我的'];
  39. Future<RespPage<Post>> _getPostListByOfficial;
  40. @override
  41. SocialDetailModel createModel() => SocialDetailModel(widget.index ?? 0);
  42. @override
  43. void initState() {
  44. super.initState();
  45. _getPostListByOfficial = model.api
  46. .getPostListByOfficial(forumId: widget.forum.forumId, limit: 3);
  47. _tabController = TabController(
  48. length: _tabs.length, initialIndex: widget.index ?? 0, vsync: this)
  49. ..addListener(() {
  50. if (_tabController.index.toDouble() == _tabController.animation.value) {
  51. model?.swtichTab(_tabController.index);
  52. _controller.animateTo(0,
  53. duration: Duration(milliseconds: 100), curve: Curves.ease);
  54. }
  55. });
  56. _controller = ScrollController()
  57. ..addListener(() {
  58. if (_controller.position.pixels >= _expandedHeight - 70) {
  59. if (_brightness == 0) {
  60. setState(() {
  61. _brightness = 1;
  62. });
  63. }
  64. } else {
  65. if (_brightness == 1) {
  66. setState(() {
  67. _brightness = 0;
  68. });
  69. }
  70. }
  71. });
  72. }
  73. @override
  74. void dispose() {
  75. super.dispose();
  76. _controller?.dispose();
  77. _tabController?.dispose();
  78. PaintingBinding.instance.imageCache.clear();
  79. }
  80. Widget _buildHeaderWidget() {
  81. return Stack(children: <Widget>[
  82. CachedNetworkImage(
  83. imageUrl: "${widget.forum.cover}",
  84. fit: BoxFit.cover,
  85. width: double.infinity,
  86. height: MediaQuery.of(context).size.width * 154.0 / 375 +
  87. MediaQuery.of(context).padding.top,
  88. ),
  89. BackdropFilter(
  90. filter: ImageFilter.blur(sigmaX: 30.0, sigmaY: 30.0),
  91. child: Center(
  92. child: Container(
  93. color: Colors.black.withOpacity(.3),
  94. ),
  95. ),
  96. ),
  97. Align(
  98. alignment: Alignment.center,
  99. child: Column(
  100. mainAxisSize: MainAxisSize.min,
  101. children: <Widget>[
  102. Padding(
  103. padding: const EdgeInsets.fromLTRB(12.0, 50, 12.0, 0),
  104. child: Row(
  105. mainAxisAlignment: MainAxisAlignment.start,
  106. children: <Widget>[
  107. CircleAvatar(
  108. backgroundImage:
  109. CachedNetworkImageProvider("${widget.forum.cover}"),
  110. radius: 30.0),
  111. Space(
  112. width: 12,
  113. ),
  114. Column(
  115. crossAxisAlignment: CrossAxisAlignment.start,
  116. children: <Widget>[
  117. Text(
  118. "${widget.forum.name}",
  119. style: Theme.of(context).textTheme.headline4,
  120. ),
  121. Space(
  122. height: 10,
  123. ),
  124. Row(
  125. children: <Widget>[
  126. Image.asset(
  127. "lib/assets/img/bbs_icon_reportnumber.png"),
  128. Space(
  129. width: 4,
  130. ),
  131. Text(
  132. "帖子数:${widget.forum.subjectCount}",
  133. style: Theme.of(context)
  134. .textTheme
  135. .subtitle2
  136. .copyWith(color: Colors.white),
  137. ),
  138. ],
  139. ),
  140. ],
  141. ),
  142. ],
  143. ),
  144. ),
  145. ],
  146. ),
  147. ),
  148. Positioned(
  149. left: 0,
  150. right: 0,
  151. bottom: -1,
  152. child: Container(
  153. height: 10,
  154. decoration: BoxDecoration(
  155. borderRadius: BorderRadius.vertical(top: Radius.circular(10)),
  156. color: Colors.white),
  157. ),
  158. ),
  159. ]);
  160. }
  161. Color getColor(String tag) {
  162. if (tag == "榜单" || tag == "公告")
  163. return Theme.of(context).accentColor;
  164. else if (tag == "置顶") {
  165. return const Color(0xff5498FF);
  166. }
  167. return const Color(0xff00DC42);
  168. }
  169. Widget _buildHeaderListWidget() {
  170. return Container(
  171. decoration: BoxDecoration(
  172. color: Colors.white,
  173. borderRadius: BorderRadius.vertical(top: Radius.circular(10))),
  174. child: FutureBuilder<RespPage<Post>>(
  175. future: _getPostListByOfficial,
  176. builder:
  177. (BuildContext context, AsyncSnapshot<RespPage<Post>> snapshot) {
  178. if (snapshot.connectionState == ConnectionState.done &&
  179. snapshot.data?.pageResult?.results?.isNotEmpty == true) {
  180. return Column(children: <Widget>[
  181. Space(
  182. height: 5,
  183. ),
  184. Column(
  185. children: snapshot.data.pageResult.results
  186. .map((e) => InkWell(
  187. onTap: () {
  188. Navigator.push(context,
  189. MaterialPageRoute(builder: (context) {
  190. return PostDetailPage(e, false,
  191. snapshot.data?.pageResult?.results ?? []);
  192. }));
  193. },
  194. child: Row(
  195. children: <Widget>[
  196. Container(
  197. decoration: BoxDecoration(
  198. color: Colors.white,
  199. borderRadius:
  200. BorderRadius.all(Radius.circular(2)),
  201. border: Border.all(
  202. color: getColor(e.tags?.first),
  203. width: .5,
  204. )),
  205. padding: EdgeInsets.symmetric(horizontal: 2),
  206. margin: EdgeInsets.fromLTRB(12.0, 5, 6, 5),
  207. child: Text(
  208. "${e.tags.first}",
  209. strutStyle: fixedLine,
  210. style: Theme.of(context)
  211. .textTheme
  212. .subtitle2
  213. .copyWith(color: getColor(e.tags?.first)),
  214. )),
  215. Expanded(
  216. child: Text(
  217. "${e.title?.isNotEmpty == true ? e.title : e.content}",
  218. maxLines: 1,
  219. overflow: TextOverflow.ellipsis,
  220. style: Theme.of(context).textTheme.subtitle1),
  221. )
  222. ],
  223. )))
  224. .toList(),
  225. ),
  226. Space(
  227. height: 5,
  228. ),
  229. Divider()
  230. ]);
  231. }
  232. return Container();
  233. },
  234. ),
  235. );
  236. }
  237. @override
  238. Widget build(BuildContext context) {
  239. _expandedHeight = MediaQuery.of(context).size.width * 154.0 / 375;
  240. return Scaffold(
  241. backgroundColor: Colors.white,
  242. body: Stack(
  243. children: <Widget>[
  244. ProviderWidget<SocialDetailModel>(
  245. model: model,
  246. onModelReady: (model) => model.initData(),
  247. builder: (_, model, __) {
  248. return EasyRefresh.builder(
  249. controller: model.refreshController,
  250. enableControlFinishRefresh: true,
  251. enableControlFinishLoad: true,
  252. onRefresh: () => model.refresh(),
  253. onLoad: model.isIdle ? () => model.loadMore() : null,
  254. header: buildClassicalHeader(),
  255. footer: buildClassicalFooter(),
  256. builder: (context, physics, header, footer) {
  257. return CustomScrollView(
  258. controller: _controller,
  259. physics: physics,
  260. slivers: <Widget>[
  261. SliverAppBar(
  262. expandedHeight: _expandedHeight,
  263. pinned: true,
  264. elevation: 0,
  265. iconTheme: IconThemeData(
  266. color: _brightness == 0
  267. ? Colors.white
  268. : Colors.black),
  269. brightness: _brightness == 0
  270. ? Brightness.dark
  271. : Brightness.light,
  272. title: _brightness == 0
  273. ? Text("")
  274. : Text(
  275. "${widget.forum.name}",
  276. style: titleStyle,
  277. ),
  278. backgroundColor: Colors.white,
  279. flexibleSpace: FlexibleSpaceBar(
  280. collapseMode: CollapseMode.pin,
  281. background: _buildHeaderWidget(),
  282. ),
  283. leading: IconButton(
  284. icon: Image.asset(
  285. "lib/assets/img/topbar_return${_brightness == 0 ? "_white" : ""}.png"),
  286. onPressed: () {
  287. Navigator.of(context).pop();
  288. },
  289. ),
  290. ),
  291. header,
  292. SliverToBoxAdapter(
  293. child: _buildHeaderListWidget(),
  294. ),
  295. SliverPersistentHeader(
  296. delegate: PersistentHeader(
  297. min: 34,
  298. max: 34,
  299. child: Container(
  300. color: Colors.white,
  301. padding: EdgeInsets.only(bottom: 5),
  302. child: TabBar(
  303. isScrollable: true,
  304. indicatorPadding:
  305. EdgeInsets.symmetric(horizontal: 8),
  306. indicatorWeight: 3,
  307. controller: _tabController,
  308. tabs:
  309. _tabs.map((e) => Tab(text: e)).toList(),
  310. ),
  311. )),
  312. pinned: true,
  313. ),
  314. if (model.isBusy)
  315. SliverToBoxAdapter(
  316. child: RequestLoadingWidget(),
  317. ),
  318. if (model.isEmpty)
  319. SliverFillRemaining(
  320. child: Center(
  321. child: RequestErrorWidget(
  322. null,
  323. msg: "暂无帖子~",
  324. assets:
  325. RequestErrorWidget.ASSETS_NO_INVITATION,
  326. ),
  327. ),
  328. ),
  329. if (model.isIdle)
  330. SliverList(
  331. delegate: SliverChildBuilderDelegate(
  332. (context, index) {
  333. Post post = model.list[index];
  334. return PostWidget(
  335. post, model, selfId == post.userId);
  336. },
  337. childCount: model.list.length,
  338. ),
  339. ),
  340. ],
  341. );
  342. });
  343. },
  344. ),
  345. Positioned(
  346. right: 12.0,
  347. bottom: 12.0,
  348. child: GestureDetector(
  349. onTap: () {
  350. NavigatorUtil.goPage(
  351. context,
  352. (context) => PostPage(
  353. widget.forum.forumId,
  354. forum: widget.forum,
  355. ));
  356. },
  357. child: Image.asset("lib/assets/img/bbs_icon_edit.png")))
  358. ],
  359. ));
  360. }
  361. }