social_detail_page.dart 16 KB

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