diff --git a/lib/internal/extension.dart b/lib/internal/extension.dart index 0307d4f..126b160 100644 --- a/lib/internal/extension.dart +++ b/lib/internal/extension.dart @@ -18,7 +18,6 @@ extension IterableExt on Iterable { bool eq(Iterable other) { if (this == null) return other == null; if (other == null || this.length != other.length) return false; - if (!identical(this, other)) return false; for (int index = 0; index < this.length; index += 1) { if (this.elementAt(index) != other.elementAt(index)) return false; } @@ -41,12 +40,12 @@ extension ListExt on List { bool eq(List other) { if (this == null) return other == null; if (other == null || this.length != other.length) return false; - if (!identical(this, other)) return false; for (int index = 0; index < this.length; index += 1) { if (this[index] != other[index]) return false; } return true; } + static a(){} bool ne(List other) => !this.eq(other); } diff --git a/lib/ui/fragments/index_fragment.dart b/lib/ui/fragments/index_fragment.dart index 2efb185..fd5a45a 100644 --- a/lib/ui/fragments/index_fragment.dart +++ b/lib/ui/fragments/index_fragment.dart @@ -48,7 +48,7 @@ class IndexFragment extends StatelessWidget { final Color backgroundColor = Theme.of(context).backgroundColor; final Color scaffoldBackgroundColor = Theme.of(context).scaffoldBackgroundColor; - final Color headline3Color = Theme.of(context).textTheme.subtitle1.color; + final Color subtitleColor = Theme.of(context).textTheme.subtitle1.color; final IndexModel indexModel = Provider.of(context, listen: false); return Scaffold( @@ -99,7 +99,7 @@ class IndexFragment extends StatelessWidget { return [ _buildWeekSection( scaffoldBackgroundColor, - headline3Color, + subtitleColor, bangumiRow, ), BangumiSliverGridFragment( @@ -118,7 +118,7 @@ class IndexFragment extends StatelessWidget { Widget _buildWeekSection( final Color scaffoldBackgroundColor, - final Color headline3Color, + final Color subtitleColor, final BangumiRow bangumiRow, ) { final simple = [ @@ -145,14 +145,6 @@ class IndexFragment extends StatelessWidget { ), decoration: BoxDecoration( color: scaffoldBackgroundColor, - // boxShadow: [ - // BoxShadow( - // offset: Offset(0, 4.0), - // blurRadius: 12.0, - // spreadRadius: -12.0, - // color: Colors.black26, - // ) - // ], ), child: Row( mainAxisSize: MainAxisSize.max, @@ -173,7 +165,7 @@ class IndexFragment extends StatelessWidget { child: Text( simple, style: TextStyle( - color: headline3Color, + color: subtitleColor, fontSize: 12.0, height: 1.25, ), diff --git a/lib/ui/pages/login_page.dart b/lib/ui/pages/login_page.dart index 48eaaa9..5f962ab 100644 --- a/lib/ui/pages/login_page.dart +++ b/lib/ui/pages/login_page.dart @@ -2,6 +2,7 @@ import 'package:extended_image/extended_image.dart'; import 'package:ff_annotation_route/ff_annotation_route.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:mikan_flutter/internal/extension.dart'; @@ -13,6 +14,7 @@ import 'package:mikan_flutter/providers/models/index_model.dart'; import 'package:mikan_flutter/providers/models/login_model.dart'; import 'package:mikan_flutter/providers/models/subscribed_model.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; @FFRoute( name: "login", @@ -24,9 +26,10 @@ class LoginPage extends StatelessWidget { Widget build(BuildContext context) { final Color accentColor = Theme.of(context).accentColor; final Color primaryColor = Theme.of(context).primaryColor; + final Color scaffoldBackgroundColor = Theme.of(context).scaffoldBackgroundColor; return AnnotatedRegion( value: context.fitSystemUiOverlayStyle, - child: ListenableProxyProvider( + child: ChangeNotifierProxyProvider( create: (_) => LoginModel(), update: (_, indexModel, loginModel) { loginModel.user = indexModel.user; @@ -41,215 +44,233 @@ class LoginPage extends StatelessWidget { top: Sz.statusBarHeight + 36.0, bottom: 36.0, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ExtendedImage.asset( - "assets/mikan.png", - width: 96.0, - ), - SizedBox(width: 24.0), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Welcome to Mikan", - style: TextStyle(fontSize: 16.0), - ), - Text( - "蜜柑计划", - style: TextStyle( - fontSize: 40.0, - height: 1.25, - fontWeight: FontWeight.w500, - ), - ), - ], - ) - ], - ), - SizedBox( - height: 56.0, - ), - Text( - 'Sign Up', - style: TextStyle(fontSize: 24.0), - ), - SizedBox( - height: 24.0, - ), - Container( - decoration: BoxDecoration( - color: Colors.grey.withOpacity(0.1), - borderRadius: - const BorderRadius.all(Radius.circular(16.0)), + child: Builder(builder: (context) { + final LoginModel loginModel = + Provider.of(context, listen: false); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildHeader(), + SizedBox( + height: 42.0, ), - child: Builder( - builder: (context) { - return TextField( - controller: - Provider.of(context, listen: false) - .accountController, - decoration: InputDecoration( - border: InputBorder.none, - labelText: '帐号', - prefixIcon: Icon( - FluentIcons.inprivate_account_24_regular)), - keyboardType: TextInputType.text, - ); - }, + _buildAccountField(loginModel), + SizedBox( + height: 16.0, ), - ), - SizedBox( - height: 16.0, - ), - Container( - decoration: BoxDecoration( - color: Colors.grey.withOpacity(0.1), - borderRadius: - const BorderRadius.all(Radius.circular(16.0)), + _buildPasswordField(loginModel), + SizedBox( + height: 16.0, ), - child: Builder( - builder: (BuildContext context) { - return TextField( - obscureText: true, - controller: - Provider - .of(context, listen: false) - .passwordController, - decoration: InputDecoration( - border: InputBorder.none, - labelText: '密码', - prefixIcon: - Icon(FluentIcons.password_24_regular)), - keyboardType: TextInputType.visiblePassword, - ); - }, + _buildRememberRow(accentColor, loginModel), + SizedBox( + height: 16.0, ), + FlatButton( + onPressed: () {}, + child: Text("还没有账号?赶紧来注册一个吧~"), + ), + SizedBox( + height: 16.0, + ), + _buildLoginButton( + context, + primaryColor, + accentColor, + scaffoldBackgroundColor, + ), + SizedBox( + height: 56.0, + ), + ], + ); + }), + ), + ), + ), + ), + ); + } + + Widget _buildLoginButton( + final BuildContext context, + final Color primaryColor, + final Color accentColor, + final Color scaffoldBackgroundColor, + ) { + return Selector>( + selector: (_, model) => Tuple2(model.user, model.loading), + shouldRebuild: (pre, next) => pre != next, + builder: (_, tuple, __) { + final User user = tuple.item1; + final bool loading = tuple.item2; + final bool isNotOk = user == null || user?.token?.isNullOrBlank == true; + final Color btnColor = loading ? primaryColor : accentColor; + final Color iconColor = + btnColor.computeLuminance() < 0.5 ? Colors.white : Colors.black; + return RaisedButton( + onPressed: () { + if (isNotOk || loading) return; + context.read().submit(() { + context.read().refresh(); + context.read().refresh(); + Navigator.popUntil( + context, + (route) => route.settings.name == Routes.home, + ); + }); + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(16.0), + ), + ), + color: scaffoldBackgroundColor.withOpacity(0), + padding: EdgeInsets.zero, + child: Container( + height: 48.0, + decoration: BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(16.0), + ), + gradient: LinearGradient( + colors: [ + btnColor, + btnColor.withOpacity(0.64), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (loading) + SpinKitWave( + size: 20.0, + type: SpinKitWaveType.center, + color: iconColor, ), - SizedBox( - height: 16.0, - ), - Row( - children: [ - Builder(builder: (context) { - return Selector( - selector: (_, model) => model.rememberMe, - builder: (_, checked, __) { - return Checkbox( - value: checked, - visualDensity: VisualDensity(), - activeColor: accentColor, - onChanged: (val) { - context.read().rememberMe = val; - }, - ); - }, - ); - }), - Expanded(child: Text("记住密码")), - FlatButton( - onPressed: () {}, - child: Text("忘记密码"), - ) - ], - ), - SizedBox( - height: 16.0, - ), - FlatButton( - onPressed: () {}, - child: Text("还没有账号?赶紧来注册一个吧~"), - ), - SizedBox( - height: 16.0, - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Selector( - selector: (_, model) => model.loading, - shouldRebuild: (pre, next) => pre != next, - builder: (_, loading, __) { - return Selector( - builder: (_, user, __) { - final bool isNotOk = user == null || - user?.token?.isNullOrBlank == true; - final Color iconColor = - (loading ? primaryColor : accentColor) - .computeLuminance() < - 0.5 - ? Colors.white - : Colors.black; - return Ink( - decoration: ShapeDecoration( - shape: CircleBorder(), - gradient: LinearGradient( - colors: [ - loading ? primaryColor : accentColor, - (loading ? primaryColor : accentColor) - .withOpacity(0.8), - ], - ), - shadows: [ - BoxShadow( - blurRadius: 8, - color: Colors.black.withAlpha(24), - ) - ], - ), - child: Builder(builder: (context) { - return IconButton( - iconSize: 36.0, - color: iconColor, - icon: isNotOk || loading - ? SpinKitFoldingCube( - color: iconColor, - size: 16.0, - duration: const Duration( - milliseconds: 1600), - ) - : Icon( - FluentIcons.caret_right_24_regular), - onPressed: () { - if (isNotOk || loading) return; - context.read().submit( - () { - context.read().refresh(); - context - .read() - .refresh(); - Navigator.popUntil( - context, - (route) => - route.settings.name == - Routes.home, - ); - }, - ); - }, - ); - }), - ); - }, - selector: (_, model) => model.user, - ); - }, - ) - ], + SizedBox(width: 12.0), + Text( + loading ? "登录中..." : "登录", + style: TextStyle( + color: iconColor, + fontSize: 16.0, + fontWeight: FontWeight.bold, ), - SizedBox( - height: 56.0, + ), + SizedBox(width: 12.0), + if (loading) + SpinKitWave( + size: 20.0, + type: SpinKitWaveType.center, + color: iconColor, ), - ], - ), + ], ), ), + ); + }, + ); + } + + Widget _buildRememberRow( + final Color accentColor, + final LoginModel loginModel, + ) { + return Row( + children: [ + Selector( + selector: (_, model) => model.rememberMe, + builder: (_, checked, __) { + return Checkbox( + value: checked, + visualDensity: VisualDensity(), + activeColor: accentColor, + onChanged: (val) { + loginModel.rememberMe = val; + }, + ); + }, + ), + Expanded(child: Text("记住密码")), + FlatButton( + onPressed: () {}, + child: Text("忘记密码"), + ) + ], + ); + } + + Widget _buildAccountField(final LoginModel loginModel) { + return Container( + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + ), + child: TextField( + controller: loginModel.accountController, + decoration: InputDecoration( + isDense: true, + border: InputBorder.none, + labelText: '帐号', + prefixIcon: Icon( + FluentIcons.inprivate_account_24_regular, + ), ), + keyboardType: TextInputType.text, ), ); } + + Widget _buildPasswordField(final LoginModel loginModel) { + return Container( + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + ), + child: TextField( + obscureText: true, + controller: loginModel.passwordController, + decoration: InputDecoration( + isDense: true, + border: InputBorder.none, + labelText: '密码', + prefixIcon: Icon(FluentIcons.password_24_regular), + ), + keyboardType: TextInputType.visiblePassword, + ), + ); + } + + Widget _buildHeader() { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ExtendedImage.asset( + "assets/mikan.png", + width: 72.0, + ), + SizedBox(width: 24.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Welcome to Mikan", + style: TextStyle(fontSize: 14.0), + ), + Text( + "蜜柑计划", + style: TextStyle( + fontSize: 32.0, + height: 1.25, + fontWeight: FontWeight.w500, + ), + ), + ], + ) + ], + ); + } } diff --git a/lib/ui/pages/season_list_page.dart b/lib/ui/pages/season_list_page.dart index 1544b4f..fb732f4 100644 --- a/lib/ui/pages/season_list_page.dart +++ b/lib/ui/pages/season_list_page.dart @@ -38,6 +38,7 @@ class SeasonListPage extends StatelessWidget { final Color backgroundColor = Theme.of(context).backgroundColor; final Color scaffoldBackgroundColor = Theme.of(context).scaffoldBackgroundColor; + final Color subtitleColor = Theme.of(context).textTheme.subtitle1.color; return AnnotatedRegion( value: context.fitSystemUiOverlayStyle, child: Scaffold( @@ -79,140 +80,11 @@ class SeasonListPage extends StatelessWidget { enablePullUp: true, onRefresh: seasonListModel.refresh, onLoading: seasonListModel.loadMore, - child: CustomScrollView( - slivers: [ - Selector( - selector: (_, model) => model.hasScrolled, - builder: (_, hasScrolled, __) { - return SliverPinnedToBoxAdapter( - child: AnimatedContainer( - decoration: BoxDecoration( - color: hasScrolled - ? backgroundColor - : scaffoldBackgroundColor, - boxShadow: hasScrolled - ? [ - BoxShadow( - color: Colors.black - .withOpacity(0.024), - offset: Offset(0, 1), - blurRadius: 3.0, - spreadRadius: 3.0, - ), - ] - : null, - borderRadius: hasScrolled - ? BorderRadius.only( - bottomLeft: Radius.circular(16.0), - bottomRight: Radius.circular(16.0), - ) - : null, - ), - padding: EdgeInsets.only( - top: 16.0 + Sz.statusBarHeight, - left: 16.0, - right: 16.0, - bottom: 16.0, - ), - duration: Duration(milliseconds: 240), - child: Row( - children: [ - Text( - "季度番组列表", - style: TextStyle( - fontSize: 24, - height: 1.25, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ); - }, - ), - ...List.generate(seasons.length, (index) { - final SeasonBangumis seasonBangumis = - seasons[index]; - final String seasonTitle = - seasonBangumis.season.title; - return [ - SliverToBoxAdapter( - child: Container( - padding: EdgeInsets.only( - top: 16.0, - left: 16.0, - right: 16.0, - bottom: 8.0, - ), - decoration: BoxDecoration( - color: scaffoldBackgroundColor, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Expanded( - child: Text( - seasonTitle, - style: TextStyle( - fontSize: 20, - height: 1.25, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - ), - ...List.generate( - seasonBangumis.bangumiRows.length, - (ind) { - final BangumiRow bangumiRow = - seasonBangumis.bangumiRows[ind]; - return [ - SliverToBoxAdapter( - child: Container( - padding: EdgeInsets.only( - top: 16.0, - left: 16.0, - right: 16.0, - bottom: 8.0, - ), - decoration: BoxDecoration( - color: scaffoldBackgroundColor, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Expanded( - child: Text( - bangumiRow.name, - style: TextStyle( - fontSize: 18, - height: 1.25, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - ), - BangumiSliverGridFragment( - flag: seasonTitle, - bangumis: bangumiRow.bangumis, - ), - ]; - }, - ).expand((element) => element), - ]; - }).expand((element) => element), - ], + child: _buildContentWrapper( + backgroundColor, + scaffoldBackgroundColor, + subtitleColor, + seasons, ), ); }, @@ -224,4 +96,168 @@ class SeasonListPage extends StatelessWidget { ), ); } + + Widget _buildContentWrapper( + final Color backgroundColor, + final Color scaffoldBackgroundColor, + final Color subtitleColor, + final List seasons, + ) { + return CustomScrollView( + slivers: [ + _buildHeader(backgroundColor, scaffoldBackgroundColor), + ...List.generate(seasons.length, (index) { + final SeasonBangumis seasonBangumis = seasons[index]; + final String seasonTitle = seasonBangumis.season.title; + return [ + _buildSeasonSection(seasonTitle), + ...List.generate( + seasonBangumis.bangumiRows.length, + (ind) { + final BangumiRow bangumiRow = seasonBangumis.bangumiRows[ind]; + return [ + _buildBangumiRowSection(subtitleColor, bangumiRow), + BangumiSliverGridFragment( + flag: seasonTitle, + bangumis: bangumiRow.bangumis, + ), + ]; + }, + ).expand((element) => element), + ]; + }).expand((element) => element), + ], + ); + } + + Widget _buildSeasonSection(final String seasonTitle) { + return SliverToBoxAdapter( + child: Container( + padding: EdgeInsets.only( + top: 16.0, + left: 16.0, + right: 16.0, + bottom: 8.0, + ), + child: Text( + seasonTitle, + style: TextStyle( + fontSize: 20, + height: 1.25, + fontWeight: FontWeight.bold, + ), + ), + ), + ); + } + + Widget _buildBangumiRowSection( + final Color subtitleColor, + final BangumiRow bangumiRow, + ) { + final simple = [ + if (bangumiRow.updatedNum > 0) "🚀 ${bangumiRow.updatedNum}部", + if (bangumiRow.subscribedUpdatedNum > 0) + "💖 ${bangumiRow.subscribedUpdatedNum}部", + if (bangumiRow.subscribedNum > 0) "❤ ${bangumiRow.subscribedNum}部", + "🎬 ${bangumiRow.num}部" + ].join(","); + final full = [ + if (bangumiRow.updatedNum > 0) "更新${bangumiRow.updatedNum}部", + if (bangumiRow.subscribedUpdatedNum > 0) + "订阅更新${bangumiRow.subscribedUpdatedNum}部", + if (bangumiRow.subscribedNum > 0) "订阅${bangumiRow.subscribedNum}部", + "共${bangumiRow.num}部" + ].join(","); + return SliverToBoxAdapter( + child: Container( + padding: EdgeInsets.only( + top: 16.0, + left: 16.0, + right: 16.0, + bottom: 8.0, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Text( + bangumiRow.name, + style: TextStyle( + fontSize: 18, + height: 1.25, + fontWeight: FontWeight.bold, + ), + ), + ), + Tooltip( + message: full, + child: Text( + simple, + style: TextStyle( + color: subtitleColor, + fontSize: 12.0, + height: 1.25, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildHeader( + final Color backgroundColor, + final Color scaffoldBackgroundColor, + ) { + return Selector( + selector: (_, model) => model.hasScrolled, + builder: (_, hasScrolled, __) { + return SliverPinnedToBoxAdapter( + child: AnimatedContainer( + decoration: BoxDecoration( + color: hasScrolled ? backgroundColor : scaffoldBackgroundColor, + boxShadow: hasScrolled + ? [ + BoxShadow( + color: Colors.black.withOpacity(0.024), + offset: Offset(0, 1), + blurRadius: 3.0, + spreadRadius: 3.0, + ), + ] + : null, + borderRadius: hasScrolled + ? BorderRadius.only( + bottomLeft: Radius.circular(16.0), + bottomRight: Radius.circular(16.0), + ) + : null, + ), + padding: EdgeInsets.only( + top: 16.0 + Sz.statusBarHeight, + left: 16.0, + right: 16.0, + bottom: 16.0, + ), + duration: Duration(milliseconds: 240), + child: Row( + children: [ + Text( + "季度番组", + style: TextStyle( + fontSize: 24, + height: 1.25, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ); + }, + ); + } } diff --git a/lib/ui/pages/subgroup_page.dart b/lib/ui/pages/subgroup_page.dart index d9d2449..4088b10 100644 --- a/lib/ui/pages/subgroup_page.dart +++ b/lib/ui/pages/subgroup_page.dart @@ -70,121 +70,11 @@ class SubgroupPage extends StatelessWidget { enablePullDown: true, enablePullUp: false, onRefresh: subgroupModel.refresh, - child: CustomScrollView( - slivers: [ - Selector( - selector: (_, model) => model.hasScrolled, - builder: (_, hasScrolled, __) { - return SliverPinnedToBoxAdapter( - child: AnimatedContainer( - decoration: BoxDecoration( - color: hasScrolled - ? backgroundColor - : scaffoldBackgroundColor, - boxShadow: hasScrolled - ? [ - BoxShadow( - color: Colors.black - .withOpacity(0.024), - offset: Offset(0, 1), - blurRadius: 3.0, - spreadRadius: 3.0, - ), - ] - : null, - borderRadius: hasScrolled - ? BorderRadius.only( - bottomLeft: Radius.circular(16.0), - bottomRight: Radius.circular(16.0), - ) - : null, - ), - padding: EdgeInsets.only( - top: 16.0 + Sz.statusBarHeight, - left: 16.0, - right: 16.0, - bottom: 16.0, - ), - duration: Duration(milliseconds: 240), - child: Row( - children: [ - Text( - subgroup.name, - style: TextStyle( - fontSize: 24, - height: 1.25, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ); - }, - ), - if (subgroupModel.loading) - SliverFillRemaining( - child: Center( - child: CupertinoActivityIndicator(), - ), - ), - if (galleries.isSafeNotEmpty) - ...List.generate(galleries.length, (index) { - final SeasonGallery gallery = galleries[index]; - final String section = - "${gallery.date} ${gallery.season}"; - return [ - SliverToBoxAdapter( - child: Container( - padding: EdgeInsets.only( - top: 16.0, - left: 16.0, - right: 16.0, - bottom: 8.0, - ), - decoration: BoxDecoration( - color: scaffoldBackgroundColor, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Expanded( - child: Text( - section, - style: TextStyle( - fontSize: 18, - height: 1.25, - fontWeight: FontWeight.bold, - ), - ), - ), - // Tooltip( - // message: full, - // child: Text( - // simple, - // style: TextStyle( - // color: Theme.of(context) - // .textTheme - // .bodyText1 - // .color, - // fontSize: 12.0, - // height: 1.25, - // ), - // ), - // ), - ], - ), - ), - ), - BangumiSliverGridFragment( - flag: section, - bangumis: gallery.bangumis, - ), - ]; - }).expand((element) => element), - ], + child: _buildContentWrapper( + backgroundColor, + scaffoldBackgroundColor, + subgroupModel, + galleries, ), ); }, @@ -196,4 +86,109 @@ class SubgroupPage extends StatelessWidget { ), ); } + + Widget _buildContentWrapper( + final Color backgroundColor, + final Color scaffoldBackgroundColor, + final SubgroupModel subgroupModel, + final List galleries, + ) { + return CustomScrollView( + slivers: [ + _buildHeader(backgroundColor, scaffoldBackgroundColor), + if (subgroupModel.loading) + SliverFillRemaining( + child: Center( + child: CupertinoActivityIndicator(), + ), + ), + if (galleries.isSafeNotEmpty) + ...List.generate(galleries.length, (index) { + final SeasonGallery gallery = galleries[index]; + final String section = "${gallery.date} ${gallery.season}"; + return [ + _buildYearSeasonSection(section), + BangumiSliverGridFragment( + flag: section, + bangumis: gallery.bangumis, + ), + ]; + }).expand((element) => element), + ], + ); + } + + Widget _buildYearSeasonSection(final String section) { + return SliverToBoxAdapter( + child: Container( + padding: EdgeInsets.only( + top: 16.0, + left: 16.0, + right: 16.0, + bottom: 8.0, + ), + child: Text( + section, + style: TextStyle( + fontSize: 18, + height: 1.25, + fontWeight: FontWeight.bold, + ), + ), + ), + ); + } + + Widget _buildHeader( + final Color backgroundColor, + final Color scaffoldBackgroundColor, + ) { + return Selector( + selector: (_, model) => model.hasScrolled, + builder: (_, hasScrolled, __) { + return SliverPinnedToBoxAdapter( + child: AnimatedContainer( + decoration: BoxDecoration( + color: hasScrolled ? backgroundColor : scaffoldBackgroundColor, + boxShadow: hasScrolled + ? [ + BoxShadow( + color: Colors.black.withOpacity(0.024), + offset: Offset(0, 1), + blurRadius: 3.0, + spreadRadius: 3.0, + ), + ] + : null, + borderRadius: hasScrolled + ? BorderRadius.only( + bottomLeft: Radius.circular(16.0), + bottomRight: Radius.circular(16.0), + ) + : null, + ), + padding: EdgeInsets.only( + top: 16.0 + Sz.statusBarHeight, + left: 16.0, + right: 16.0, + bottom: 16.0, + ), + duration: Duration(milliseconds: 240), + child: Row( + children: [ + Text( + subgroup.name, + style: TextStyle( + fontSize: 24, + height: 1.25, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ); + }, + ); + } } diff --git a/lib/ui/pages/subscribed_season_page.dart b/lib/ui/pages/subscribed_season_page.dart index 7ec20a4..a33e60e 100644 --- a/lib/ui/pages/subscribed_season_page.dart +++ b/lib/ui/pages/subscribed_season_page.dart @@ -113,10 +113,7 @@ class SubscribedSeasonPage extends StatelessWidget { final SeasonGallery gallery = galleries[index]; final String seasonTitle = gallery.season; return [ - _buildSeasonSection( - scaffoldBackgroundColor, - seasonTitle, - ), + _buildSeasonSection(seasonTitle), gallery.bangumis.isNullOrEmpty ? _buildEmptySubscribedContainer( backgroundColor, @@ -168,10 +165,7 @@ class SubscribedSeasonPage extends StatelessWidget { ); } - Widget _buildSeasonSection( - final Color scaffoldBackgroundColor, - final String seasonTitle, - ) { + Widget _buildSeasonSection(final String seasonTitle) { return SliverToBoxAdapter( child: Container( padding: EdgeInsets.only( @@ -180,38 +174,13 @@ class SubscribedSeasonPage extends StatelessWidget { right: 16.0, bottom: 8.0, ), - decoration: BoxDecoration( - color: scaffoldBackgroundColor, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Text( - seasonTitle, - style: TextStyle( - fontSize: 18, - height: 1.25, - fontWeight: FontWeight.bold, - ), - ), - ), - // Tooltip( - // message: full, - // child: Text( - // simple, - // style: TextStyle( - // color: Theme.of(context) - // .textTheme - // .bodyText1 - // .color, - // fontSize: 12.0, - // height: 1.25, - // ), - // ), - // ), - ], + child: Text( + seasonTitle, + style: TextStyle( + fontSize: 18, + height: 1.25, + fontWeight: FontWeight.bold, + ), ), ), ); @@ -255,7 +224,7 @@ class SubscribedSeasonPage extends StatelessWidget { child: Row( children: [ Text( - "季度订阅番组", + "季度订阅", style: TextStyle( fontSize: 24, height: 1.25, diff --git a/pubspec.yaml b/pubspec.yaml index f6b9880..bdf6831 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: mikan_flutter description: mikanani.me -version: 0.0.4-alpha+4 +version: 0.0.5-alpha+5 environment: sdk: ">=2.8.4 <3.0.0" @@ -38,6 +38,7 @@ dependencies: waterfall_flow: ^2.0.5 modal_bottom_sheet: ^1.0.0+1 jiffy: ^3.0.1 + tuple: ^1.0.3 dev_dependencies: build_runner: ^1.10.6