diff --git a/lib/common/skeleton/dynamic_card.dart b/lib/common/skeleton/dynamic_card.dart index a238e4e0..6279bf78 100644 --- a/lib/common/skeleton/dynamic_card.dart +++ b/lib/common/skeleton/dynamic_card.dart @@ -6,6 +6,7 @@ class DynamicCardSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme.onInverseSurface; return Skeleton( child: Container( padding: const EdgeInsets.only(left: 12, right: 12, top: 12), @@ -25,7 +26,7 @@ class DynamicCardSkeleton extends StatelessWidget { width: 40, height: 40, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, borderRadius: BorderRadius.circular(20), ), ), @@ -34,13 +35,13 @@ class DynamicCardSkeleton extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: 100, height: 13, margin: const EdgeInsets.only(bottom: 5), ), Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: 50, height: 11, ), @@ -55,31 +56,31 @@ class DynamicCardSkeleton extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: double.infinity, height: 13, margin: const EdgeInsets.only(bottom: 7), ), Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: double.infinity, height: 13, margin: const EdgeInsets.only(bottom: 7), ), Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: 300, height: 13, margin: const EdgeInsets.only(bottom: 7), ), Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: 250, height: 13, margin: const EdgeInsets.only(bottom: 7), ), Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: 100, height: 13, margin: const EdgeInsets.only(bottom: 7), diff --git a/lib/common/skeleton/fav_pgc_item.dart b/lib/common/skeleton/fav_pgc_item.dart new file mode 100644 index 00000000..7f7b1096 --- /dev/null +++ b/lib/common/skeleton/fav_pgc_item.dart @@ -0,0 +1,67 @@ +import 'package:PiliPlus/common/constants.dart'; +import 'package:flutter/material.dart'; +import 'skeleton.dart'; + +class FavPgcItemSkeleton extends StatelessWidget { + const FavPgcItemSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme.onInverseSurface; + return Skeleton( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: StyleString.safeSpace, + vertical: 5, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: 3 / 4, + child: LayoutBuilder( + builder: (context, boxConstraints) { + return Container( + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(4), + ), + width: boxConstraints.maxWidth, + height: boxConstraints.maxHeight, + ); + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 175, + height: 12, + color: color, + ), + const SizedBox(height: 10), + Container( + width: 55, + height: 11, + color: color, + ), + const SizedBox(height: 5), + Container( + width: 35, + height: 11, + color: color, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/common/skeleton/msg_feed_sys_msg_.dart b/lib/common/skeleton/msg_feed_sys_msg_.dart new file mode 100644 index 00000000..ed0a8b0f --- /dev/null +++ b/lib/common/skeleton/msg_feed_sys_msg_.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'skeleton.dart'; + +class MsgFeedSysMsgSkeleton extends StatelessWidget { + const MsgFeedSysMsgSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme.onInverseSurface; + return Skeleton( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 125, + height: 16, + color: color, + ), + const SizedBox(height: 6), + Container( + width: double.infinity, + height: 12, + color: color, + ), + const SizedBox(height: 4), + Container( + width: double.infinity, + height: 12, + color: color, + ), + const SizedBox(height: 4), + Container( + width: 100, + height: 12, + color: color, + ), + const SizedBox(height: 4), + Align( + alignment: Alignment.centerRight, + child: Container( + width: 100, + height: 10, + color: color, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/common/skeleton/msg_feed_top.dart b/lib/common/skeleton/msg_feed_top.dart new file mode 100644 index 00000000..45b0dcee --- /dev/null +++ b/lib/common/skeleton/msg_feed_top.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'skeleton.dart'; + +class MsgFeedTopSkeleton extends StatelessWidget { + const MsgFeedTopSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme.onInverseSurface; + return Skeleton( + child: ListTile( + leading: Container( + width: 45, + height: 45, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: color, + ), + ), + title: UnconstrainedBox( + alignment: Alignment.centerLeft, + child: Container( + width: 100, + height: 11, + color: color, + ), + ), + subtitle: Container( + color: color, + width: 125, + height: 11, + ), + ), + ); + } +} diff --git a/lib/common/skeleton/video_card_h.dart b/lib/common/skeleton/video_card_h.dart index 27e8811e..2d5b2571 100644 --- a/lib/common/skeleton/video_card_h.dart +++ b/lib/common/skeleton/video_card_h.dart @@ -7,6 +7,7 @@ class VideoCardHSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme.onInverseSurface; return Skeleton( child: Padding( padding: const EdgeInsets.symmetric( @@ -23,7 +24,7 @@ class VideoCardHSkeleton extends StatelessWidget { builder: (context, boxConstraints) { return Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, borderRadius: StyleString.mdRadius, ), ); @@ -37,19 +38,19 @@ class VideoCardHSkeleton extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: 200, height: 11, margin: const EdgeInsets.only(bottom: 5), ), Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: 150, height: 13, ), const Spacer(), Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: 100, height: 13, margin: const EdgeInsets.only(bottom: 5), @@ -57,13 +58,13 @@ class VideoCardHSkeleton extends StatelessWidget { Row( children: [ Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: 40, height: 13, margin: const EdgeInsets.only(right: 8), ), Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, width: 40, height: 13, ), diff --git a/lib/common/skeleton/video_card_v.dart b/lib/common/skeleton/video_card_v.dart index 89347232..a7a7c7f1 100644 --- a/lib/common/skeleton/video_card_v.dart +++ b/lib/common/skeleton/video_card_v.dart @@ -7,6 +7,7 @@ class VideoCardVSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme.onInverseSurface; return Skeleton( child: Column( children: [ @@ -16,7 +17,7 @@ class VideoCardVSkeleton extends StatelessWidget { builder: (context, boxConstraints) { return Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, borderRadius: StyleString.mdRadius, ), ); @@ -37,24 +38,24 @@ class VideoCardVSkeleton extends StatelessWidget { width: 200, height: 13, margin: const EdgeInsets.only(bottom: 5), - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, ), Container( width: 150, height: 13, margin: const EdgeInsets.only(bottom: 12), - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, ), Container( width: 110, height: 13, margin: const EdgeInsets.only(bottom: 5), - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, ), Container( width: 75, height: 13, - color: Theme.of(context).colorScheme.onInverseSurface, + color: color, ), ], ), diff --git a/lib/common/skeleton/whisper_item.dart b/lib/common/skeleton/whisper_item.dart new file mode 100644 index 00000000..da596b8c --- /dev/null +++ b/lib/common/skeleton/whisper_item.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'skeleton.dart'; + +class WhisperItemSkeleton extends StatelessWidget { + const WhisperItemSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme.onInverseSurface; + return Skeleton( + child: ListTile( + leading: Container( + width: 45, + height: 45, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: color, + ), + ), + title: UnconstrainedBox( + alignment: Alignment.centerLeft, + child: Container( + width: 100, + height: 11, + color: color, + ), + ), + subtitle: Container( + color: color, + width: 125, + height: 11, + ), + trailing: Container( + color: color, + width: 50, + height: 11, + ), + ), + ); + } +} diff --git a/lib/pages/blacklist/view.dart b/lib/pages/blacklist/view.dart index 455af431..ea5e164b 100644 --- a/lib/pages/blacklist/view.dart +++ b/lib/pages/blacklist/view.dart @@ -1,4 +1,5 @@ -import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; +import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/user/black.dart'; @@ -40,18 +41,27 @@ class _BlackListPageState extends State { ), body: refreshIndicator( onRefresh: () async => await _blackListController.onRefresh(), - child: Obx(() => _buildBody(_blackListController.loadingState.value)), + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: _blackListController.scrollController, + slivers: [ + Obx(() => _buildBody(_blackListController.loadingState.value)) + ], + ), ), ); } Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) { + return const MsgFeedTopSkeleton(); + }, + ), Success() => loadingState.response?.isNotEmpty == true - ? ListView.builder( - physics: const AlwaysScrollableScrollPhysics(), - controller: _blackListController.scrollController, + ? SliverList.builder( itemCount: loadingState.response!.length, itemBuilder: (BuildContext context, int index) { if (index == loadingState.response!.length - 1) { @@ -94,10 +104,10 @@ class _BlackListPageState extends State { ); }, ) - : errorWidget( + : HttpError( callback: _blackListController.onReload, ), - Error() => errorWidget( + Error() => HttpError( errMsg: loadingState.errMsg, callback: _blackListController.onReload, ), diff --git a/lib/pages/fan/view.dart b/lib/pages/fan/view.dart index bae864ce..ca4027fc 100644 --- a/lib/pages/fan/view.dart +++ b/lib/pages/fan/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/fans/result.dart'; @@ -51,7 +52,18 @@ class _FansPageState extends State { Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => HttpError(), + Loading() => SliverGrid( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: Grid.smallCardWidth * 2, + mainAxisExtent: 66, + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + return const MsgFeedTopSkeleton(); + }, + childCount: 16, + ), + ), Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( diff --git a/lib/pages/fav/article/view.dart b/lib/pages/fav/article/view.dart index dc59c020..24babc13 100644 --- a/lib/pages/fav/article/view.dart +++ b/lib/pages/fav/article/view.dart @@ -34,7 +34,14 @@ class _FavArticlePageState extends State }, child: CustomScrollView( slivers: [ - Obx(() => _buildBody(_favArticleController.loadingState.value)), + SliverPadding( + padding: EdgeInsets.only( + top: StyleString.safeSpace - 5, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: + Obx(() => _buildBody(_favArticleController.loadingState.value)), + ), ], ), ); @@ -52,35 +59,29 @@ class _FavArticlePageState extends State ), ), Success() => loadingState.response?.isNotEmpty == true - ? SliverPadding( - padding: EdgeInsets.only( - top: StyleString.safeSpace - 5, - bottom: MediaQuery.paddingOf(context).bottom + 80, - ), - sliver: SliverGrid( - gridDelegate: Grid.videoCardHDelegate(context), - delegate: SliverChildBuilderDelegate( - (context, index) { - if (index == loadingState.response!.length - 1) { - _favArticleController.onLoadMore(); - } - return FavArticleItem( - item: loadingState.response![index], - onDelete: () { - showConfirmDialog( - context: context, - title: '确定取消收藏?', - onConfirm: () { - _favArticleController.onRemove( - index, - loadingState.response![index]['opus_id'], - ); - }); - }, - ); - }, - childCount: loadingState.response!.length, - ), + ? SliverGrid( + gridDelegate: Grid.videoCardHDelegate(context), + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == loadingState.response!.length - 1) { + _favArticleController.onLoadMore(); + } + return FavArticleItem( + item: loadingState.response![index], + onDelete: () { + showConfirmDialog( + context: context, + title: '确定取消收藏?', + onConfirm: () { + _favArticleController.onRemove( + index, + loadingState.response![index]['opus_id'], + ); + }); + }, + ); + }, + childCount: loadingState.response!.length, ), ) : HttpError(callback: _favArticleController.onReload), diff --git a/lib/pages/fav/pgc/child_view.dart b/lib/pages/fav/pgc/child_view.dart index fc5928d6..191433f0 100644 --- a/lib/pages/fav/pgc/child_view.dart +++ b/lib/pages/fav/pgc/child_view.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/common/skeleton/video_card_h.dart'; +import 'package:PiliPlus/common/skeleton/fav_pgc_item.dart'; import 'package:PiliPlus/common/widgets/dialog.dart'; import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/icon_button.dart'; @@ -162,7 +162,7 @@ class _FavPgcChildPageState extends State gridDelegate: Grid.videoCardHDelegate(context), delegate: SliverChildBuilderDelegate( (context, index) { - return const VideoCardHSkeleton(); + return const FavPgcItemSkeleton(); }, childCount: 10, ), diff --git a/lib/pages/fav/video/view.dart b/lib/pages/fav/video/view.dart index 7bb59193..14695d08 100644 --- a/lib/pages/fav/video/view.dart +++ b/lib/pages/fav/video/view.dart @@ -37,8 +37,14 @@ class _FavVideoPageState extends State controller: _favController.scrollController, physics: const AlwaysScrollableScrollPhysics(), slivers: [ - Obx( - () => _buildBody(_favController.loadingState.value), + SliverPadding( + padding: EdgeInsets.only( + top: StyleString.safeSpace - 5, + bottom: 80 + MediaQuery.paddingOf(context).bottom, + ), + sliver: Obx( + () => _buildBody(_favController.loadingState.value), + ), ), ], ), @@ -57,44 +63,38 @@ class _FavVideoPageState extends State ), ), Success() => loadingState.response?.isNotEmpty == true - ? SliverPadding( - padding: EdgeInsets.only( - top: StyleString.safeSpace - 5, - bottom: 80 + MediaQuery.paddingOf(context).bottom, - ), - sliver: SliverGrid( - gridDelegate: Grid.videoCardHDelegate(context), - delegate: SliverChildBuilderDelegate( - childCount: loadingState.response!.length, - (BuildContext context, int index) { - if (index == loadingState.response!.length - 1) { - _favController.onLoadMore(); - } - final item = loadingState.response![index]; - String heroTag = Utils.makeHeroTag(item.fid); - return FavItem( - heroTag: heroTag, - favFolderItem: item, - onTap: () async { - dynamic res = await Get.toNamed( - '/favDetail', - arguments: item, - parameters: { - 'heroTag': heroTag, - 'mediaId': item.id.toString(), - }, - ); - if (res == true) { - List list = - (_favController.loadingState.value as Success) - .response; - list.removeAt(index); - _favController.loadingState.refresh(); - } - }, - ); - }, - ), + ? SliverGrid( + gridDelegate: Grid.videoCardHDelegate(context), + delegate: SliverChildBuilderDelegate( + childCount: loadingState.response!.length, + (BuildContext context, int index) { + if (index == loadingState.response!.length - 1) { + _favController.onLoadMore(); + } + final item = loadingState.response![index]; + String heroTag = Utils.makeHeroTag(item.fid); + return FavItem( + heroTag: heroTag, + favFolderItem: item, + onTap: () async { + dynamic res = await Get.toNamed( + '/favDetail', + arguments: item, + parameters: { + 'heroTag': heroTag, + 'mediaId': item.id.toString(), + }, + ); + if (res == true) { + List list = + (_favController.loadingState.value as Success) + .response; + list.removeAt(index); + _favController.loadingState.refresh(); + } + }, + ); + }, ), ) : HttpError( diff --git a/lib/pages/member/content/member_contribute/content/bangumi/member_bangumi.dart b/lib/pages/member/content/member_contribute/content/bangumi/member_bangumi.dart index 0de0e3dc..971a62be 100644 --- a/lib/pages/member/content/member_contribute/content/bangumi/member_bangumi.dart +++ b/lib/pages/member/content/member_contribute/content/bangumi/member_bangumi.dart @@ -58,7 +58,8 @@ class _MemberBangumiState extends State right: StyleString.safeSpace, top: StyleString.safeSpace, bottom: StyleString.safeSpace + - MediaQuery.of(context).padding.bottom, + MediaQuery.of(context).padding.bottom + + 80, ), sliver: SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( diff --git a/lib/pages/member_coin/view.dart b/lib/pages/member_coin/view.dart index 38467016..58e564de 100644 --- a/lib/pages/member_coin/view.dart +++ b/lib/pages/member_coin/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; -import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/common/skeleton/video_card_v.dart'; +import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/member/coin.dart'; import 'package:PiliPlus/pages/member_coin/controller.dart'; @@ -38,21 +39,39 @@ class _MemberCoinPageState extends State { appBar: AppBar( title: Text('${widget.mid == _ownerMid ? '我' : '${widget.name}'}的最近投币'), ), - body: Obx(() => _buildBody(_ctr.loadingState.value)), + body: CustomScrollView( + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + top: StyleString.safeSpace - 5, + left: StyleString.safeSpace, + right: StyleString.safeSpace, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx(() => _buildBody(_ctr.loadingState.value)), + ), + ], + ), ); } Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => SliverGrid.builder( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.cardSpace, + maxCrossAxisExtent: Grid.smallCardWidth, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), + ), + itemCount: 16, + itemBuilder: (context, index) { + return const VideoCardVSkeleton(); + }, + ), Success() => loadingState.response?.isNotEmpty == true - ? GridView.builder( - padding: EdgeInsets.only( - top: StyleString.safeSpace - 5, - left: StyleString.safeSpace, - right: StyleString.safeSpace, - bottom: MediaQuery.paddingOf(context).bottom + 80, - ), + ? SliverGrid.builder( gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.cardSpace, @@ -65,8 +84,8 @@ class _MemberCoinPageState extends State { return MemberCoinsItem(coinItem: loadingState.response![index]); }, ) - : scrollErrorWidget(callback: _ctr.onReload), - Error() => scrollErrorWidget( + : HttpError(callback: _ctr.onReload), + Error() => HttpError( errMsg: loadingState.errMsg, callback: _ctr.onReload, ), diff --git a/lib/pages/member_like/view.dart b/lib/pages/member_like/view.dart index 60c6b9cd..5bde2469 100644 --- a/lib/pages/member_like/view.dart +++ b/lib/pages/member_like/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; -import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/common/skeleton/video_card_v.dart'; +import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/member/coin.dart'; import 'package:PiliPlus/pages/member_coin/widgets/item.dart'; @@ -38,21 +39,39 @@ class _MemberLikePageState extends State { appBar: AppBar( title: Text('${widget.mid == _ownerMid ? '我' : '${widget.name}'}的推荐'), ), - body: Obx(() => _buildBody(_ctr.loadingState.value)), + body: CustomScrollView( + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + top: StyleString.safeSpace - 5, + left: StyleString.safeSpace, + right: StyleString.safeSpace, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx(() => _buildBody(_ctr.loadingState.value)), + ), + ], + ), ); } Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => SliverGrid.builder( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.cardSpace, + maxCrossAxisExtent: Grid.smallCardWidth, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), + ), + itemCount: 16, + itemBuilder: (context, index) { + return const VideoCardVSkeleton(); + }, + ), Success() => loadingState.response?.isNotEmpty == true - ? GridView.builder( - padding: EdgeInsets.only( - top: StyleString.safeSpace - 5, - left: StyleString.safeSpace, - right: StyleString.safeSpace, - bottom: MediaQuery.paddingOf(context).bottom + 80, - ), + ? SliverGrid.builder( gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.cardSpace, @@ -65,8 +84,8 @@ class _MemberLikePageState extends State { return MemberCoinsItem(coinItem: loadingState.response![index]); }, ) - : scrollErrorWidget(callback: _ctr.onReload), - Error() => scrollErrorWidget( + : HttpError(callback: _ctr.onReload), + Error() => HttpError( errMsg: loadingState.errMsg, callback: _ctr.onReload, ), diff --git a/lib/pages/member_search/search_archive.dart b/lib/pages/member_search/search_archive.dart index 62e6253c..39067b1a 100644 --- a/lib/pages/member_search/search_archive.dart +++ b/lib/pages/member_search/search_archive.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; -import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/common/skeleton/video_card_h.dart'; +import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/video_card_h.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -27,54 +28,64 @@ class _SearchArchiveState extends State @override Widget build(BuildContext context) { super.build(context); - return Obx(() => _buildBody(context, widget.ctr.archiveState.value)); + return refreshIndicator( + onRefresh: () async { + await widget.ctr.refreshArchive(); + }, + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + top: StyleString.safeSpace - 5, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: + Obx(() => _buildBody(context, widget.ctr.archiveState.value)), + ) + ], + ), + ); } Widget _buildBody( BuildContext context, LoadingState?> loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => SliverGrid( + gridDelegate: Grid.videoCardHDelegate(context), + delegate: SliverChildBuilderDelegate( + (context, index) { + return const VideoCardHSkeleton(); + }, + childCount: 10, + ), + ), Success() => loadingState.response?.isNotEmpty == true - ? refreshIndicator( - onRefresh: () async { - await widget.ctr.refreshArchive(); - }, - child: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - SliverPadding( - padding: EdgeInsets.only( - top: StyleString.safeSpace - 5, - bottom: MediaQuery.paddingOf(context).bottom + 80, - ), - sliver: SliverGrid( - gridDelegate: Grid.videoCardHDelegate(context), - delegate: SliverChildBuilderDelegate( - (context, index) { - if (index == loadingState.response!.length - 1) { - EasyThrottle.throttle('searchArchives', - const Duration(milliseconds: 500), () { - widget.ctr.searchArchives(false); - }); - } - return VideoCardH( - videoItem: loadingState.response![index], - ); - }, - childCount: loadingState.response!.length, - ), - ), - ), - ], + ? SliverGrid( + gridDelegate: Grid.videoCardHDelegate(context), + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == loadingState.response!.length - 1) { + EasyThrottle.throttle( + 'searchArchives', const Duration(milliseconds: 500), + () { + widget.ctr.searchArchives(false); + }); + } + return VideoCardH( + videoItem: loadingState.response![index], + ); + }, + childCount: loadingState.response!.length, ), ) - : errorWidget( + : HttpError( callback: () { widget.ctr.archiveState.value = LoadingState.loading(); widget.ctr.refreshArchive(); }, ), - Error() => errorWidget( + Error() => HttpError( errMsg: loadingState.errMsg, callback: () { widget.ctr.archiveState.value = LoadingState.loading(); diff --git a/lib/pages/member_search/search_dynamic.dart b/lib/pages/member_search/search_dynamic.dart index fae7bf9b..fedf9ca3 100644 --- a/lib/pages/member_search/search_dynamic.dart +++ b/lib/pages/member_search/search_dynamic.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; -import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/common/skeleton/dynamic_card.dart'; +import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; @@ -29,86 +30,123 @@ class _SearchDynamicState extends State @override Widget build(BuildContext context) { super.build(context); - return Obx(() => _buildBody(context, widget.ctr.dynamicState.value)); + return refreshIndicator( + onRefresh: () async { + await widget.ctr.refreshDynamic(); + }, + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: + Obx(() => _buildBody(context, widget.ctr.dynamicState.value)), + ) + ], + ), + ); } late final bool dynamicsWaterfallFlow = GStorage.setting .get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true); + Widget skeleton() { + if (!dynamicsWaterfallFlow) { + return SliverCrossAxisGroup( + slivers: [ + const SliverFillRemaining(), + SliverConstrainedCrossAxis( + maxExtent: Grid.smallCardWidth * 2, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return const DynamicCardSkeleton(); + }, + childCount: 10, + ), + ), + ), + const SliverFillRemaining() + ], + ); + } + return SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + crossAxisSpacing: StyleString.cardSpace / 2, + mainAxisSpacing: StyleString.cardSpace / 2, + maxCrossAxisExtent: Grid.smallCardWidth * 2, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: 50, + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + return const DynamicCardSkeleton(); + }, + childCount: 10, + ), + ); + } + Widget _buildBody(BuildContext context, LoadingState?> loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => skeleton(), Success() => loadingState.response?.isNotEmpty == true - ? refreshIndicator( - onRefresh: () async { - await widget.ctr.refreshDynamic(); - }, - child: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - SliverPadding( - padding: EdgeInsets.only( - bottom: MediaQuery.paddingOf(context).bottom + 80, + ? dynamicsWaterfallFlow + ? SliverWaterfallFlow.extent( + maxCrossAxisExtent: Grid.smallCardWidth * 2, + crossAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: StyleString.safeSpace, + lastChildLayoutTypeBuilder: (index) { + if (index == loadingState.response!.length - 1) { + EasyThrottle.throttle( + 'member_dynamics', const Duration(milliseconds: 1000), + () { + widget.ctr.searchDynamic(false); + }); + } + return index == loadingState.response!.length + ? LastChildLayoutType.foot + : LastChildLayoutType.none; + }, + children: (loadingState.response as List) + .map((item) => DynamicPanel(item: item)) + .toList(), + ) + : SliverCrossAxisGroup( + slivers: [ + const SliverFillRemaining(), + SliverConstrainedCrossAxis( + maxExtent: Grid.smallCardWidth * 2, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == loadingState.response!.length - 1) { + EasyThrottle.throttle('member_dynamics', + const Duration(milliseconds: 1000), () { + widget.ctr.searchDynamic(false); + }); + } + return DynamicPanel( + item: loadingState.response![index], + ); + }, + childCount: loadingState.response!.length, + ), + ), ), - sliver: dynamicsWaterfallFlow - ? SliverWaterfallFlow.extent( - maxCrossAxisExtent: Grid.smallCardWidth * 2, - crossAxisSpacing: StyleString.safeSpace, - mainAxisSpacing: StyleString.safeSpace, - lastChildLayoutTypeBuilder: (index) { - if (index == loadingState.response!.length - 1) { - EasyThrottle.throttle('member_dynamics', - const Duration(milliseconds: 1000), () { - widget.ctr.searchDynamic(false); - }); - } - return index == loadingState.response!.length - ? LastChildLayoutType.foot - : LastChildLayoutType.none; - }, - children: (loadingState.response as List) - .map((item) => DynamicPanel(item: item)) - .toList(), - ) - : SliverCrossAxisGroup( - slivers: [ - const SliverFillRemaining(), - SliverConstrainedCrossAxis( - maxExtent: Grid.smallCardWidth * 2, - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - if (index == - loadingState.response!.length - 1) { - EasyThrottle.throttle('member_dynamics', - const Duration(milliseconds: 1000), - () { - widget.ctr.searchDynamic(false); - }); - } - return DynamicPanel( - item: loadingState.response![index], - ); - }, - childCount: loadingState.response!.length, - ), - ), - ), - const SliverFillRemaining(), - ], - ), - ), - ], - ), - ) - : errorWidget( + const SliverFillRemaining(), + ], + ) + : HttpError( callback: () { widget.ctr.dynamicState.value = LoadingState.loading(); widget.ctr.refreshDynamic(); }, ), - Error() => errorWidget( + Error() => HttpError( errMsg: loadingState.errMsg, callback: () { widget.ctr.dynamicState.value = LoadingState.loading(); diff --git a/lib/pages/msg_feed_top/at_me/view.dart b/lib/pages/msg_feed_top/at_me/view.dart index c16d2e2b..a6540c72 100644 --- a/lib/pages/msg_feed_top/at_me/view.dart +++ b/lib/pages/msg_feed_top/at_me/view.dart @@ -1,5 +1,6 @@ +import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; import 'package:PiliPlus/common/widgets/dialog.dart'; -import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -31,20 +32,31 @@ class _AtMePageState extends State { onRefresh: () async { await _atMeController.onRefresh(); }, - child: Obx(() => _buildBody(_atMeController.loadingState.value)), + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + 80), + sliver: Obx(() => _buildBody(_atMeController.loadingState.value)), + ), + ], + ), ), ); } Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) { + return const MsgFeedTopSkeleton(); + }, + ), Success() => loadingState.response?.isNotEmpty == true - ? ListView.separated( + ? SliverList.separated( itemCount: loadingState.response!.length, - physics: const AlwaysScrollableScrollPhysics(), - padding: EdgeInsets.only( - bottom: MediaQuery.paddingOf(context).bottom + 80), itemBuilder: (context, int index) { if (index == loadingState.response!.length - 1) { _atMeController.onLoadMore(); @@ -145,8 +157,8 @@ class _AtMePageState extends State { ); }, ) - : scrollErrorWidget(callback: _atMeController.onReload), - Error() => scrollErrorWidget( + : HttpError(callback: _atMeController.onReload), + Error() => HttpError( errMsg: loadingState.errMsg, callback: _atMeController.onReload, ), diff --git a/lib/pages/msg_feed_top/like_me/view.dart b/lib/pages/msg_feed_top/like_me/view.dart index 3bdab672..340bacf6 100644 --- a/lib/pages/msg_feed_top/like_me/view.dart +++ b/lib/pages/msg_feed_top/like_me/view.dart @@ -1,5 +1,6 @@ +import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; import 'package:PiliPlus/common/widgets/dialog.dart'; -import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -32,22 +33,36 @@ class _LikeMePageState extends State { onRefresh: () async { await _likeMeController.onRefresh(); }, - child: Obx(() => _buildBody(_likeMeController.loadingState.value)), + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + 80), + sliver: + Obx(() => _buildBody(_likeMeController.loadingState.value)), + ), + ], + ), ), ); } Widget _buildBody(LoadingState loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) { + return const MsgFeedTopSkeleton(); + }, + ), Success() => () { Pair, List> pair = loadingState.response; List latest = pair.first; List total = pair.second; if (latest.isNotEmpty || total.isNotEmpty) { - return CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), + return SliverMainAxisGroup( slivers: [ if (latest.isNotEmpty) ...[ _buildHeader('最新'), @@ -99,17 +114,12 @@ class _LikeMePageState extends State { }, ), ], - SliverToBoxAdapter( - child: SizedBox( - height: MediaQuery.paddingOf(context).bottom + 80, - ), - ), ], ); } - return scrollErrorWidget(callback: _likeMeController.onReload); + return HttpError(callback: _likeMeController.onReload); }(), - Error() => scrollErrorWidget( + Error() => HttpError( errMsg: loadingState.errMsg, callback: _likeMeController.onReload, ), @@ -118,14 +128,18 @@ class _LikeMePageState extends State { } Widget _buildHeader(String title) { - return SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.only(left: 16), - child: Text( - title, - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), + return SliverSafeArea( + top: false, + bottom: false, + sliver: SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(left: 16), + child: Text( + title, + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), ), ), ); diff --git a/lib/pages/msg_feed_top/reply_me/view.dart b/lib/pages/msg_feed_top/reply_me/view.dart index d67ac989..666b46e5 100644 --- a/lib/pages/msg_feed_top/reply_me/view.dart +++ b/lib/pages/msg_feed_top/reply_me/view.dart @@ -1,5 +1,6 @@ +import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; import 'package:PiliPlus/common/widgets/dialog.dart'; -import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/msg/msgfeed_reply_me.dart'; @@ -29,20 +30,32 @@ class _ReplyMePageState extends State { onRefresh: () async { await _replyMeController.onRefresh(); }, - child: Obx(() => _buildBody(_replyMeController.loadingState.value)), + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + 80), + sliver: + Obx(() => _buildBody(_replyMeController.loadingState.value)), + ), + ], + ), ), ); } Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) { + return const MsgFeedTopSkeleton(); + }, + ), Success() => loadingState.response?.isNotEmpty == true - ? ListView.separated( + ? SliverList.separated( itemCount: loadingState.response!.length, - physics: const AlwaysScrollableScrollPhysics(), - padding: EdgeInsets.only( - bottom: MediaQuery.paddingOf(context).bottom + 80), itemBuilder: (context, int index) { if (index == loadingState.response!.length - 1) { _replyMeController.onLoadMore(); @@ -165,8 +178,8 @@ class _ReplyMePageState extends State { ); }, ) - : scrollErrorWidget(callback: _replyMeController.onReload), - Error() => scrollErrorWidget( + : HttpError(callback: _replyMeController.onReload), + Error() => HttpError( errMsg: loadingState.errMsg, callback: _replyMeController.onReload, ), diff --git a/lib/pages/msg_feed_top/sys_msg/view.dart b/lib/pages/msg_feed_top/sys_msg/view.dart index cadf8087..02e95fd6 100644 --- a/lib/pages/msg_feed_top/sys_msg/view.dart +++ b/lib/pages/msg_feed_top/sys_msg/view.dart @@ -1,7 +1,8 @@ import 'dart:convert'; +import 'package:PiliPlus/common/skeleton/msg_feed_sys_msg_.dart'; import 'package:PiliPlus/common/widgets/dialog.dart'; -import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/msg/msgfeed_sys_msg.dart'; @@ -36,25 +37,36 @@ class _SysMsgPageState extends State { onRefresh: () async { await _sysMsgController.onRefresh(); }, - child: Obx(() => _buildBody(_sysMsgController.loadingState.value)), + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + 80), + sliver: + Obx(() => _buildBody(_sysMsgController.loadingState.value)), + ), + ], + ), ), ); } Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) { + return const MsgFeedSysMsgSkeleton(); + }, + ), Success() => loadingState.response?.isNotEmpty == true - ? ListView.separated( + ? SliverList.separated( itemCount: loadingState.response!.length, - physics: const AlwaysScrollableScrollPhysics(), - padding: EdgeInsets.only( - bottom: MediaQuery.paddingOf(context).bottom + 80), itemBuilder: (context, int index) { if (index == loadingState.response!.length - 1) { _sysMsgController.onLoadMore(); } - final item = loadingState.response![index]; String? content = item.content; if (content != null) { @@ -124,8 +136,8 @@ class _SysMsgPageState extends State { ); }, ) - : scrollErrorWidget(callback: _sysMsgController.onReload), - Error() => scrollErrorWidget( + : HttpError(callback: _sysMsgController.onReload), + Error() => HttpError( errMsg: loadingState.errMsg, callback: _sysMsgController.onReload, ), diff --git a/lib/pages/search_panel/view.dart b/lib/pages/search_panel/view.dart index 2ef6af16..c9260b76 100644 --- a/lib/pages/search_panel/view.dart +++ b/lib/pages/search_panel/view.dart @@ -1,6 +1,8 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/media_bangumi.dart'; +import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; import 'package:PiliPlus/common/skeleton/video_card_h.dart'; +import 'package:PiliPlus/common/skeleton/video_card_v.dart'; import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -64,32 +66,57 @@ abstract class CommonSearchPanelState< Widget get _builLoading { return SliverGrid( - gridDelegate: widget.searchType == SearchType.media_bangumi || - widget.searchType == SearchType.media_ft - ? SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: 2, - maxCrossAxisExtent: Grid.smallCardWidth * 2, - childAspectRatio: StyleString.aspectRatio * 1.5, - minHeight: MediaQuery.textScalerOf(context).scale(155), - ) - : Grid.videoCardHDelegate(context), + gridDelegate: switch (widget.searchType) { + SearchType.media_bangumi || + SearchType.media_ft => + SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: 2, + maxCrossAxisExtent: Grid.smallCardWidth * 2, + childAspectRatio: StyleString.aspectRatio * 1.5, + minHeight: MediaQuery.textScalerOf(context).scale(155), + ), + SearchType.live_room => SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.cardSpace, + maxCrossAxisExtent: Grid.smallCardWidth, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), + ), + SearchType.bili_user => SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: Grid.smallCardWidth * 2, + mainAxisExtent: 66, + ), + _ => Grid.videoCardHDelegate(context), + }, delegate: SliverChildBuilderDelegate( (context, index) { switch (widget.searchType) { case SearchType.media_bangumi || SearchType.media_ft: return const MediaBangumiSkeleton(); + case SearchType.bili_user: + return const MsgFeedTopSkeleton(); + case SearchType.live_room: + return const VideoCardVSkeleton(); default: return const VideoCardHSkeleton(); } }, - childCount: 15, + childCount: 16, ), ); } Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => _builLoading, + Loading() => widget.searchType == SearchType.live_room + ? SliverPadding( + padding: const EdgeInsets.only( + left: StyleString.cardSpace, + right: StyleString.cardSpace, + ), + sliver: _builLoading, + ) + : _builLoading, Success() => loadingState.response?.isNotEmpty == true ? buildList(loadingState.response!) : HttpError( diff --git a/lib/pages/video/detail/related/view.dart b/lib/pages/video/detail/related/view.dart index 4dd71212..d88017ab 100644 --- a/lib/pages/video/detail/related/view.dart +++ b/lib/pages/video/detail/related/view.dart @@ -28,7 +28,8 @@ class _RelatedVideoPanelState extends State Widget build(BuildContext context) { super.build(context); return SliverPadding( - padding: const EdgeInsets.only(top: StyleString.safeSpace - 5), + padding: + const EdgeInsets.only(top: StyleString.safeSpace - 5, bottom: 80), sliver: Obx(() => _buildBody(_relatedController.loadingState.value)), ); } @@ -45,17 +46,14 @@ class _RelatedVideoPanelState extends State ), ), Success() => loadingState.response?.isNotEmpty == true - ? SliverPadding( - padding: const EdgeInsets.only(bottom: 80), - sliver: SliverGrid( - gridDelegate: Grid.videoCardHDelegate(context), - delegate: SliverChildBuilderDelegate((context, index) { - return VideoCardH( - videoItem: loadingState.response![index], - showPubdate: true, - ); - }, childCount: loadingState.response!.length), - ), + ? SliverGrid( + gridDelegate: Grid.videoCardHDelegate(context), + delegate: SliverChildBuilderDelegate((context, index) { + return VideoCardH( + videoItem: loadingState.response![index], + showPubdate: true, + ); + }, childCount: loadingState.response!.length), ) : const SliverToBoxAdapter(), Error() => HttpError( diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 89c3892c..a2256e58 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/skeleton/whisper_item.dart'; import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -44,7 +45,12 @@ class _WhisperPageState extends State { Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => const SliverToBoxAdapter(), + Loading() => SliverList.builder( + itemCount: 12, + itemBuilder: (context, index) { + return const WhisperItemSkeleton(); + }, + ), Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( @@ -85,58 +91,58 @@ class _WhisperPageState extends State { } Widget get _buildTopItems => SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), - child: Row( - children: List.generate(_whisperController.msgFeedTopItems.length, - (index) { - return Expanded( - child: GestureDetector( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Obx( - () => Badge( - isLabelVisible: - _whisperController.unreadCounts[index] > 0, - label: Text( - " ${_whisperController.unreadCounts[index]} "), - alignment: Alignment.topRight, - child: CircleAvatar( - radius: 22, - backgroundColor: - Theme.of(context).colorScheme.onInverseSurface, - child: Icon( - _whisperController.msgFeedTopItems[index]['icon'], - size: 20, - color: Theme.of(context).colorScheme.primary, - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: + List.generate(_whisperController.msgFeedTopItems.length, (index) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.all(10), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Obx( + () => Badge( + isLabelVisible: + _whisperController.unreadCounts[index] > 0, + label: + Text(" ${_whisperController.unreadCounts[index]} "), + alignment: Alignment.topRight, + child: CircleAvatar( + radius: 22, + backgroundColor: + Theme.of(context).colorScheme.onInverseSurface, + child: Icon( + _whisperController.msgFeedTopItems[index]['icon'], + size: 20, + color: Theme.of(context).colorScheme.primary, ), ), ), - const SizedBox(height: 6), - Text( - _whisperController.msgFeedTopItems[index]['name'], - style: const TextStyle(fontSize: 13), - ), - ], - ), - onTap: () { - if (!_whisperController.msgFeedTopItems[index]['enabled']) { - SmartDialog.showToast('已禁用'); - return; - } - _whisperController.unreadCounts[index] = 0; - Get.toNamed( - _whisperController.msgFeedTopItems[index]['route'], - ); - }, + ), + const SizedBox(height: 6), + Text( + _whisperController.msgFeedTopItems[index]['name'], + style: const TextStyle(fontSize: 13), + ), + ], ), - ); - }).toList(), - ), + ), + onTap: () { + if (!_whisperController.msgFeedTopItems[index]['enabled']) { + SmartDialog.showToast('已禁用'); + return; + } + _whisperController.unreadCounts[index] = 0; + Get.toNamed( + _whisperController.msgFeedTopItems[index]['route'], + ); + }, + ); + }).toList(), ), ); }