import 'dart:math'; import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_easyrefresh/easy_refresh.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sport/bean/forum.dart'; import 'package:sport/bean/post.dart'; import 'package:sport/pages/social/post_page.dart'; import 'package:sport/pages/social/post_widget.dart'; import 'package:sport/pages/social/search_page.dart'; import 'package:sport/provider/lib/provider_widget.dart'; import 'package:sport/provider/lib/view_state.dart'; import 'package:sport/provider/lib/view_state_lifecycle.dart'; import 'package:sport/provider/social_detail_model.dart'; import 'package:sport/provider/social_index_model.dart'; import 'package:sport/router/navigator_util.dart'; import 'package:sport/router/routes.dart'; import 'package:sport/services/api/inject_api.dart'; import 'package:sport/services/api/resp.dart'; import 'package:sport/services/userid.dart'; import 'package:sport/widgets/appbar.dart'; import 'package:sport/widgets/error.dart'; import 'package:sport/widgets/loading.dart'; import 'package:sport/widgets/misc.dart'; import 'package:sport/widgets/persistent_header.dart'; import 'package:sport/widgets/space.dart'; final List _tabs = ['热门', '关注', '最新', '精华', '官方']; class NewSocialIndexPage extends StatefulWidget { NewSocialIndexPage(); @override State createState() => _PageState(); } class _PageState extends ViewStateLifecycle with TickerProviderStateMixin, UserId, InjectApi { ScrollController _controller; double _expandedHeight = 0; int _brightness = 0; TabController _tabController; Future> _getPostListByOfficial; final GlobalKey _scaffoldKey = new GlobalKey(); @override SocialDetailModel createModel() => SocialDetailModel(0); SocialIndexModel indexModel = new SocialIndexModel(); // 右边draw中的 当前选择的索引... ValueNotifier drawOneIndex = ValueNotifier(0); ValueNotifier drawTwoIndex = ValueNotifier(0); String forumId = ""; String isOfficial; List buttonList = []; List buttonData = [ new Forum( gameName: "全部", ), new Forum( gameName: "用户发布", forumId: "0", ), new Forum(gameName: "官方发布", forumId: "1"), ]; ValueNotifier isShowSelect = ValueNotifier(false); void _refresh() { model?.setForumIdAndOrigin(_tabController.index, forumId, isOfficial); _controller.animateTo(0, duration: Duration(milliseconds: 100), curve: Curves.ease); } @override void initState() { super.initState(); _tabController = TabController(length: _tabs.length, initialIndex: 0, vsync: this) ..addListener(() { if (_tabController.index.toDouble() == _tabController.animation.value) { _refresh(); } }); _controller = ScrollController() ..addListener(() { if (_controller.position.pixels >= _expandedHeight - 70) { if (_brightness == 0) { setState(() { _brightness = 1; }); } } else { if (_brightness == 1) { setState(() { _brightness = 0; }); } } }); initButtonList(); } @override void dispose() { super.dispose(); _controller?.dispose(); _tabController?.dispose(); PaintingBinding.instance.imageCache.clear(); } Future _getCount() async { int count = 0; try { count += (await model.api.getNoticeCount("comment", "0")).data; } catch (e) { print(e); } if (count == 0) { try { count += (await model.api.getNoticeCount("like", "0")).data; } catch (e) { print(e); } } return count; } Widget _buildSearchWidget() { return GestureDetector( onTap: () { Navigator.push(context, new MaterialPageRoute(builder: (context) => SearchPage())); }, child: Container( // margin: EdgeInsets.fromLTRB(12.0, 0, 12.0, 12.0), width: MediaQuery.of(context).size.width * 0.8, height: 35, padding: EdgeInsets.fromLTRB(12.0, 0, 12.0, 0), decoration: BoxDecoration( color: Color(0xffF1F1F1), shape: BoxShape.rectangle, borderRadius: BorderRadius.all(Radius.circular(50)), ), child: Row( children: [ Image.asset("lib/assets/img/searchbar_icon_search.png"), Space( width: 4, ), Text( "输入关键词", strutStyle: StrutStyle(forceStrutHeight: true), style: TextStyle(fontSize: 14, color: Color(0xff999999)), ), ], ), ), ); } // @title button 的title // @index 当前显示的下标 // @targetIndex 目标下标 // @type origin / project Widget _buildDrawerButtonItem(Forum data, int index, ValueNotifier targetIndex, String type) { return InkWell( child: Container( // height: 35.0, decoration: BoxDecoration( color: index == targetIndex.value ? Theme.of(context).accentColor : Colors.white, borderRadius: BorderRadius.all(Radius.circular(20.0)), border: Border.all(color: index == targetIndex.value ? Colors.white : Theme.of(context).dividerTheme.color)), padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 25.0), child: Text( data.gameName, strutStyle: StrutStyle(forceStrutHeight: true), style: TextStyle(fontSize: 14.0, color: index == targetIndex.value ? Colors.white : Color(0xff999999)), ), ), onTap: () { targetIndex.value = index; if (type == "project") { forumId = data.forumId; } else if (type == "origin") { isOfficial = data.forumId; } if (drawOneIndex.value == 0 && drawTwoIndex.value == 0) { isShowSelect.value = false; } else { isShowSelect.value = true; } model.setForumIdAndOrigin(_tabController.index, forumId, isOfficial); }, ); } Widget _buildDrawButtonContainer(Widget buttons, {String type = 'top'}) { return Padding( padding: EdgeInsets.only(left: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ type == 'top' ? Space( height: 21.0, ) : Container(), type == 'bottom' ? Space( height: 43.0, ) : Container(), Text("运动项目", style: TextStyle(color: Color(0xff333333), fontSize: 16.0, fontWeight: FontWeight.bold)), Space( height: 16.0, ), buttons, type == 'bottom' ? Space( height: 21.0, ) : Container(), ], ), ); } Widget _buildButtons(ValueNotifier targetIndex, {List list}) { return ValueListenableBuilder( valueListenable: targetIndex, builder: (BuildContext context, int value, Widget child) => Wrap( alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, spacing: 8.0, runSpacing: 16.0, children: list != null ? list.asMap().entries.map((e) => _buildDrawerButtonItem(e.value, e.key, targetIndex, "project")).toList() : buttonData.asMap().entries.map((e) => _buildDrawerButtonItem(e.value, e.key, targetIndex, "origin")).toList(), ), ); } Widget _buildEndrawer() { return Container( height: double.infinity, width: MediaQuery.of(context).size.width * 0.65, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(10.0), bottomLeft: Radius.circular(10.0), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDrawButtonContainer( _buildButtons(drawOneIndex, list: buttonList), type: "bottom", ), Divider(), _buildDrawButtonContainer( _buildButtons(drawTwoIndex), type: "top", ), ], )); } Widget _buildFilterButton(String name, ValueNotifier index, String type) { return InkWell( child: Container( padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 16.0), decoration: BoxDecoration(border: Border.all(color: Theme.of(context).accentColor), borderRadius: BorderRadius.all(Radius.circular(44.0))), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "$name", strutStyle: StrutStyle(forceStrutHeight: true), style: TextStyle(color: Theme.of(context).accentColor, fontSize: 12.0), ), Space( width: 5.0, ), Image.asset( "lib/assets/img/btn_close_yellow.png", width: 7.0, height: 7.0, ) ], ), ), onTap: () { type == "one" ? model.setForumIdAndOrigin(_tabController.index, "", isOfficial) : model.setForumIdAndOrigin(_tabController.index, forumId, ""); index.value = 0; }, ); } initButtonList() async { RespList data = await api.getForumIndex(); setState(() { buttonList = data.results; if (data.results.length > 0) { buttonList.insert(0, new Forum(gameName: "全部")); } }); } @override Widget build(BuildContext context) { return Scaffold( key: _scaffoldKey, backgroundColor: Colors.white, endDrawer: _buildEndrawer(), body: ProviderWidget2( model1: model, model2: indexModel, onModelReady: (model1, model2) => model.initData(), builder: (_, model, model2, __) { return EasyRefresh.builder( controller: model.refreshController, enableControlFinishRefresh: true, enableControlFinishLoad: true, onRefresh: () => model.refresh(), onLoad: model.isIdle ? () => model.loadMore() : null, header: buildClassicalHeader(), footer: buildClassicalFooter(), builder: (context, physics, header, footer) { return AnnotatedRegion( value: SystemUiOverlayStyle.dark, child: Material( color: Colors.white, child: SafeArea( child: CustomScrollView( controller: _controller, physics: physics, slivers: [ buildSliverAppBar(context, "社区", canBack: false, pinned: false, height: 100.0, padding: const EdgeInsets.fromLTRB(12.0, 0, 0, 6.0), actions: [ Selector( selector: (_, SocialIndexModel model) => model.viewState, shouldRebuild: (_, v) => v == ViewState.idle, builder: (BuildContext context, ViewState value, Widget child) { return IconButton( icon: Stack( alignment: Alignment.center, children: [ Image.asset("lib/assets/img/bbs_icon_news.png"), FutureBuilder( future: SharedPreferences.getInstance(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.data.getBool("message_setting") ?? true == true) { return FutureBuilder( future: _getCount(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.data != null && snapshot.data > 0) { return Align( alignment: Alignment.topRight, child: Container( margin: const EdgeInsets.only(top: 6.0), width: 10, height: 10, decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.red), child: Center( child: Text(""), ), ), ); } return Container(); }); } } return Container(); }), ], ), onPressed: () async { await NavigatorUtil.go(context, Routes.socialMessage); setState(() {}); }, ); }), ], paddingLeading: false), header, SliverPersistentHeader( delegate: PersistentHeader( min: 50, max: 50, child: Container( color: Colors.white, padding: EdgeInsets.only(bottom: 10), child: TabBar( isScrollable: true, indicatorPadding: EdgeInsets.symmetric(horizontal: 8), indicatorWeight: 3, controller: _tabController, tabs: _tabs.map((e) => Tab(text: e)).toList(), ), )), pinned: true, ), SliverToBoxAdapter( child: Padding( padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 12.0), child: Row( children: [ _buildSearchWidget(), Space( width: 15.0, ), ValueListenableBuilder( valueListenable: isShowSelect, builder: (context, flag, child) => InkWell( child: flag ? Image.asset( "lib/assets/img/bbs_icon_choose_press.png", width: 22, height: 22, ) : Image.asset( "lib/assets/img/bbs_icon_choose_normal.png", width: 22, height: 22, ), onTap: () => _scaffoldKey.currentState.openEndDrawer(), ), ), ], ), ), ), SliverToBoxAdapter( child: Padding( padding: EdgeInsets.only(left: 12.0), child: Row( children: [ if (drawOneIndex.value != 0) _buildFilterButton('${buttonList[drawOneIndex.value].gameName}', drawOneIndex, "one"), Space( width: 10.0, ), if (drawTwoIndex.value != 0) _buildFilterButton('${buttonData[drawTwoIndex.value].gameName}', drawTwoIndex, "two"), ], ), ), ), if (model.isBusy) SliverToBoxAdapter( child: RequestLoadingWidget(), ), if (model.isEmpty) SliverFillRemaining( child: Center( child: RequestErrorWidget( null, msg: "暂无帖子~", assets: RequestErrorWidget.ASSETS_NO_INVITATION, ), ), ), if (model.isIdle) SliverList( delegate: SliverChildBuilderDelegate( (context, index) { Post post = model.list[index]; return PostWidget(post, model, selfId == post.userId); }, childCount: model.list.length, ), ), ], ), ), ), ); }); }, ), floatingActionButtonLocation: const _EndFloatFloatingActionButtonLocation(), floatingActionButtonAnimator:const _ScalingFabMotionAnimator(), floatingActionButton: InkWell( child: Image.asset("lib/assets/img/bbs_icon_edit.png"), onTap: () async { // print('FloatingActionButton'); var result = await NavigatorUtil.goPage( context, (context) => PostPage( "", forums: buttonList, )); if (result == true) { if (_tabController.index.toDouble() == _tabController.animation.value) { _refresh(); } else { _tabController?.index = 2; } } }, ), ); } } class _EndFloatFloatingActionButtonLocation extends FloatingActionButtonLocation { const _EndFloatFloatingActionButtonLocation(); double _rightOffset(ScaffoldPrelayoutGeometry scaffoldGeometry, {double offset = 0.0}) { return scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.minInsets.right - scaffoldGeometry.floatingActionButtonSize.width + offset; } double getDockedY(ScaffoldPrelayoutGeometry scaffoldGeometry) { final double contentBottom = scaffoldGeometry.contentBottom; final double bottomSheetHeight = scaffoldGeometry.bottomSheetSize.height; final double fabHeight = scaffoldGeometry.floatingActionButtonSize.height; final double snackBarHeight = scaffoldGeometry.snackBarSize.height; double fabY = contentBottom - fabHeight / 2.0; // The FAB should sit with a margin between it and the snack bar. if (snackBarHeight > 0.0) fabY = min(fabY, contentBottom - snackBarHeight - fabHeight - kFloatingActionButtonMargin); // The FAB should sit with its center in front of the top of the bottom sheet. if (bottomSheetHeight > 0.0) fabY = min(fabY, contentBottom - bottomSheetHeight - fabHeight / 2.0); final double maxFabY = scaffoldGeometry.scaffoldSize.height - fabHeight; return min(maxFabY, fabY); } @override Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) { final double fabX = _rightOffset(scaffoldGeometry, offset: -8.0); final double fabY = getDockedY(scaffoldGeometry); return Offset(fabX, fabY - 8); } @override String toString() => 'FloatingActionButtonLocation.endFloat'; } class _ScalingFabMotionAnimator extends FloatingActionButtonAnimator { const _ScalingFabMotionAnimator(); @override Offset getOffset({ Offset begin, Offset end, double progress }) { return end; } @override Animation getScaleAnimation({ Animation parent }) { return AlwaysStoppedAnimation(1.0); } @override Animation getRotationAnimation({ Animation parent }) { return AlwaysStoppedAnimation(1.0); } // If the animation was just starting, we'll continue from where we left off. // If the animation was finishing, we'll treat it as if we were starting at that point in reverse. // This avoids a size jump during the animation. @override double getAnimationRestart(double previousValue) => 1.0; }