mod: add skeleton

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-20 21:37:44 +08:00
parent abdde1f811
commit 95caf111ae
24 changed files with 740 additions and 347 deletions

View File

@@ -6,6 +6,7 @@ class DynamicCardSkeleton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.onInverseSurface;
return Skeleton( return Skeleton(
child: Container( child: Container(
padding: const EdgeInsets.only(left: 12, right: 12, top: 12), padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
@@ -25,7 +26,7 @@ class DynamicCardSkeleton extends StatelessWidget {
width: 40, width: 40,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
), ),
@@ -34,13 +35,13 @@ class DynamicCardSkeleton extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: 100, width: 100,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 5), margin: const EdgeInsets.only(bottom: 5),
), ),
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: 50, width: 50,
height: 11, height: 11,
), ),
@@ -55,31 +56,31 @@ class DynamicCardSkeleton extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: double.infinity, width: double.infinity,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 7), margin: const EdgeInsets.only(bottom: 7),
), ),
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: double.infinity, width: double.infinity,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 7), margin: const EdgeInsets.only(bottom: 7),
), ),
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: 300, width: 300,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 7), margin: const EdgeInsets.only(bottom: 7),
), ),
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: 250, width: 250,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 7), margin: const EdgeInsets.only(bottom: 7),
), ),
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: 100, width: 100,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 7), margin: const EdgeInsets.only(bottom: 7),

View File

@@ -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,
),
],
),
),
],
),
),
);
}
}

View File

@@ -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,
),
),
],
),
),
);
}
}

View File

@@ -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,
),
),
);
}
}

View File

@@ -7,6 +7,7 @@ class VideoCardHSkeleton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.onInverseSurface;
return Skeleton( return Skeleton(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -23,7 +24,7 @@ class VideoCardHSkeleton extends StatelessWidget {
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
borderRadius: StyleString.mdRadius, borderRadius: StyleString.mdRadius,
), ),
); );
@@ -37,19 +38,19 @@ class VideoCardHSkeleton extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: 200, width: 200,
height: 11, height: 11,
margin: const EdgeInsets.only(bottom: 5), margin: const EdgeInsets.only(bottom: 5),
), ),
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: 150, width: 150,
height: 13, height: 13,
), ),
const Spacer(), const Spacer(),
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: 100, width: 100,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 5), margin: const EdgeInsets.only(bottom: 5),
@@ -57,13 +58,13 @@ class VideoCardHSkeleton extends StatelessWidget {
Row( Row(
children: [ children: [
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: 40, width: 40,
height: 13, height: 13,
margin: const EdgeInsets.only(right: 8), margin: const EdgeInsets.only(right: 8),
), ),
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
width: 40, width: 40,
height: 13, height: 13,
), ),

View File

@@ -7,6 +7,7 @@ class VideoCardVSkeleton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.onInverseSurface;
return Skeleton( return Skeleton(
child: Column( child: Column(
children: [ children: [
@@ -16,7 +17,7 @@ class VideoCardVSkeleton extends StatelessWidget {
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
borderRadius: StyleString.mdRadius, borderRadius: StyleString.mdRadius,
), ),
); );
@@ -37,24 +38,24 @@ class VideoCardVSkeleton extends StatelessWidget {
width: 200, width: 200,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 5), margin: const EdgeInsets.only(bottom: 5),
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
), ),
Container( Container(
width: 150, width: 150,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
), ),
Container( Container(
width: 110, width: 110,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 5), margin: const EdgeInsets.only(bottom: 5),
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
), ),
Container( Container(
width: 75, width: 75,
height: 13, height: 13,
color: Theme.of(context).colorScheme.onInverseSurface, color: color,
), ),
], ],
), ),

View File

@@ -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,
),
),
);
}
}

View File

@@ -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/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/user/black.dart'; import 'package:PiliPlus/models/user/black.dart';
@@ -40,18 +41,27 @@ class _BlackListPageState extends State<BlackListPage> {
), ),
body: refreshIndicator( body: refreshIndicator(
onRefresh: () async => await _blackListController.onRefresh(), 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<List<BlackListItem>?> loadingState) { Widget _buildBody(LoadingState<List<BlackListItem>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => SliverList.builder(
itemCount: 12,
itemBuilder: (context, index) {
return const MsgFeedTopSkeleton();
},
),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? ListView.builder( ? SliverList.builder(
physics: const AlwaysScrollableScrollPhysics(),
controller: _blackListController.scrollController,
itemCount: loadingState.response!.length, itemCount: loadingState.response!.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {
@@ -94,10 +104,10 @@ class _BlackListPageState extends State<BlackListPage> {
); );
}, },
) )
: errorWidget( : HttpError(
callback: _blackListController.onReload, callback: _blackListController.onReload,
), ),
Error() => errorWidget( Error() => HttpError(
errMsg: loadingState.errMsg, errMsg: loadingState.errMsg,
callback: _blackListController.onReload, callback: _blackListController.onReload,
), ),

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/common/skeleton/msg_feed_top.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/fans/result.dart'; import 'package:PiliPlus/models/fans/result.dart';
@@ -51,7 +52,18 @@ class _FansPageState extends State<FansPage> {
Widget _buildBody(LoadingState<List<FansItemModel>?> loadingState) { Widget _buildBody(LoadingState<List<FansItemModel>?> loadingState) {
return switch (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 Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(

View File

@@ -34,7 +34,14 @@ class _FavArticlePageState extends State<FavArticlePage>
}, },
child: CustomScrollView( child: CustomScrollView(
slivers: [ 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<FavArticlePage>
), ),
), ),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverGrid(
padding: EdgeInsets.only( gridDelegate: Grid.videoCardHDelegate(context),
top: StyleString.safeSpace - 5, delegate: SliverChildBuilderDelegate(
bottom: MediaQuery.paddingOf(context).bottom + 80, (context, index) {
), if (index == loadingState.response!.length - 1) {
sliver: SliverGrid( _favArticleController.onLoadMore();
gridDelegate: Grid.videoCardHDelegate(context), }
delegate: SliverChildBuilderDelegate( return FavArticleItem(
(context, index) { item: loadingState.response![index],
if (index == loadingState.response!.length - 1) { onDelete: () {
_favArticleController.onLoadMore(); showConfirmDialog(
} context: context,
return FavArticleItem( title: '确定取消收藏?',
item: loadingState.response![index], onConfirm: () {
onDelete: () { _favArticleController.onRemove(
showConfirmDialog( index,
context: context, loadingState.response![index]['opus_id'],
title: '确定取消收藏?', );
onConfirm: () { });
_favArticleController.onRemove( },
index, );
loadingState.response![index]['opus_id'], },
); childCount: loadingState.response!.length,
});
},
);
},
childCount: loadingState.response!.length,
),
), ),
) )
: HttpError(callback: _favArticleController.onReload), : HttpError(callback: _favArticleController.onReload),

View File

@@ -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/dialog.dart';
import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/icon_button.dart'; import 'package:PiliPlus/common/widgets/icon_button.dart';
@@ -162,7 +162,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
gridDelegate: Grid.videoCardHDelegate(context), gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return const VideoCardHSkeleton(); return const FavPgcItemSkeleton();
}, },
childCount: 10, childCount: 10,
), ),

View File

@@ -37,8 +37,14 @@ class _FavVideoPageState extends State<FavVideoPage>
controller: _favController.scrollController, controller: _favController.scrollController,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
Obx( SliverPadding(
() => _buildBody(_favController.loadingState.value), 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<FavVideoPage>
), ),
), ),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverGrid(
padding: EdgeInsets.only( gridDelegate: Grid.videoCardHDelegate(context),
top: StyleString.safeSpace - 5, delegate: SliverChildBuilderDelegate(
bottom: 80 + MediaQuery.paddingOf(context).bottom, childCount: loadingState.response!.length,
), (BuildContext context, int index) {
sliver: SliverGrid( if (index == loadingState.response!.length - 1) {
gridDelegate: Grid.videoCardHDelegate(context), _favController.onLoadMore();
delegate: SliverChildBuilderDelegate( }
childCount: loadingState.response!.length, final item = loadingState.response![index];
(BuildContext context, int index) { String heroTag = Utils.makeHeroTag(item.fid);
if (index == loadingState.response!.length - 1) { return FavItem(
_favController.onLoadMore(); heroTag: heroTag,
} favFolderItem: item,
final item = loadingState.response![index]; onTap: () async {
String heroTag = Utils.makeHeroTag(item.fid); dynamic res = await Get.toNamed(
return FavItem( '/favDetail',
heroTag: heroTag, arguments: item,
favFolderItem: item, parameters: {
onTap: () async { 'heroTag': heroTag,
dynamic res = await Get.toNamed( 'mediaId': item.id.toString(),
'/favDetail', },
arguments: item, );
parameters: { if (res == true) {
'heroTag': heroTag, List<FavFolderItemData> list =
'mediaId': item.id.toString(), (_favController.loadingState.value as Success)
}, .response;
); list.removeAt(index);
if (res == true) { _favController.loadingState.refresh();
List<FavFolderItemData> list = }
(_favController.loadingState.value as Success) },
.response; );
list.removeAt(index); },
_favController.loadingState.refresh();
}
},
);
},
),
), ),
) )
: HttpError( : HttpError(

View File

@@ -58,7 +58,8 @@ class _MemberBangumiState extends State<MemberBangumi>
right: StyleString.safeSpace, right: StyleString.safeSpace,
top: StyleString.safeSpace, top: StyleString.safeSpace,
bottom: StyleString.safeSpace + bottom: StyleString.safeSpace +
MediaQuery.of(context).padding.bottom, MediaQuery.of(context).padding.bottom +
80,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: SliverGridDelegateWithExtentAndRatio(

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/constants.dart'; 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/http/loading_state.dart';
import 'package:PiliPlus/models/member/coin.dart'; import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/pages/member_coin/controller.dart'; import 'package:PiliPlus/pages/member_coin/controller.dart';
@@ -38,21 +39,39 @@ class _MemberCoinPageState extends State<MemberCoinPage> {
appBar: AppBar( appBar: AppBar(
title: Text('${widget.mid == _ownerMid ? '' : '${widget.name}'}'), 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<List<MemberCoinsDataModel>?> loadingState) { Widget _buildBody(LoadingState<List<MemberCoinsDataModel>?> loadingState) {
return switch (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 Success() => loadingState.response?.isNotEmpty == true
? GridView.builder( ? SliverGrid.builder(
padding: EdgeInsets.only(
top: StyleString.safeSpace - 5,
left: StyleString.safeSpace,
right: StyleString.safeSpace,
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.cardSpace,
@@ -65,8 +84,8 @@ class _MemberCoinPageState extends State<MemberCoinPage> {
return MemberCoinsItem(coinItem: loadingState.response![index]); return MemberCoinsItem(coinItem: loadingState.response![index]);
}, },
) )
: scrollErrorWidget(callback: _ctr.onReload), : HttpError(callback: _ctr.onReload),
Error() => scrollErrorWidget( Error() => HttpError(
errMsg: loadingState.errMsg, errMsg: loadingState.errMsg,
callback: _ctr.onReload, callback: _ctr.onReload,
), ),

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/constants.dart'; 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/http/loading_state.dart';
import 'package:PiliPlus/models/member/coin.dart'; import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/pages/member_coin/widgets/item.dart'; import 'package:PiliPlus/pages/member_coin/widgets/item.dart';
@@ -38,21 +39,39 @@ class _MemberLikePageState extends State<MemberLikePage> {
appBar: AppBar( appBar: AppBar(
title: Text('${widget.mid == _ownerMid ? '' : '${widget.name}'}'), 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<List<MemberCoinsDataModel>?> loadingState) { Widget _buildBody(LoadingState<List<MemberCoinsDataModel>?> loadingState) {
return switch (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 Success() => loadingState.response?.isNotEmpty == true
? GridView.builder( ? SliverGrid.builder(
padding: EdgeInsets.only(
top: StyleString.safeSpace - 5,
left: StyleString.safeSpace,
right: StyleString.safeSpace,
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.cardSpace,
@@ -65,8 +84,8 @@ class _MemberLikePageState extends State<MemberLikePage> {
return MemberCoinsItem(coinItem: loadingState.response![index]); return MemberCoinsItem(coinItem: loadingState.response![index]);
}, },
) )
: scrollErrorWidget(callback: _ctr.onReload), : HttpError(callback: _ctr.onReload),
Error() => scrollErrorWidget( Error() => HttpError(
errMsg: loadingState.errMsg, errMsg: loadingState.errMsg,
callback: _ctr.onReload, callback: _ctr.onReload,
), ),

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/constants.dart'; 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/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/video_card_h.dart'; import 'package:PiliPlus/common/widgets/video_card_h.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
@@ -27,54 +28,64 @@ class _SearchArchiveState extends State<SearchArchive>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(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( Widget _buildBody(
BuildContext context, LoadingState<List<VListItemModel>?> loadingState) { BuildContext context, LoadingState<List<VListItemModel>?> loadingState) {
return switch (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 Success() => loadingState.response?.isNotEmpty == true
? refreshIndicator( ? SliverGrid(
onRefresh: () async { gridDelegate: Grid.videoCardHDelegate(context),
await widget.ctr.refreshArchive(); delegate: SliverChildBuilderDelegate(
}, (context, index) {
child: CustomScrollView( if (index == loadingState.response!.length - 1) {
physics: const AlwaysScrollableScrollPhysics(), EasyThrottle.throttle(
slivers: [ 'searchArchives', const Duration(milliseconds: 500),
SliverPadding( () {
padding: EdgeInsets.only( widget.ctr.searchArchives(false);
top: StyleString.safeSpace - 5, });
bottom: MediaQuery.paddingOf(context).bottom + 80, }
), return VideoCardH(
sliver: SliverGrid( videoItem: loadingState.response![index],
gridDelegate: Grid.videoCardHDelegate(context), );
delegate: SliverChildBuilderDelegate( },
(context, index) { childCount: loadingState.response!.length,
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: () { callback: () {
widget.ctr.archiveState.value = LoadingState.loading(); widget.ctr.archiveState.value = LoadingState.loading();
widget.ctr.refreshArchive(); widget.ctr.refreshArchive();
}, },
), ),
Error() => errorWidget( Error() => HttpError(
errMsg: loadingState.errMsg, errMsg: loadingState.errMsg,
callback: () { callback: () {
widget.ctr.archiveState.value = LoadingState.loading(); widget.ctr.archiveState.value = LoadingState.loading();

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/constants.dart'; 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/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/dynamics/result.dart';
@@ -29,86 +30,123 @@ class _SearchDynamicState extends State<SearchDynamic>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(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 late final bool dynamicsWaterfallFlow = GStorage.setting
.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true); .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, Widget _buildBody(BuildContext context,
LoadingState<List<DynamicItemModel>?> loadingState) { LoadingState<List<DynamicItemModel>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => skeleton(),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? refreshIndicator( ? dynamicsWaterfallFlow
onRefresh: () async { ? SliverWaterfallFlow.extent(
await widget.ctr.refreshDynamic(); maxCrossAxisExtent: Grid.smallCardWidth * 2,
}, crossAxisSpacing: StyleString.safeSpace,
child: CustomScrollView( mainAxisSpacing: StyleString.safeSpace,
physics: const AlwaysScrollableScrollPhysics(), lastChildLayoutTypeBuilder: (index) {
slivers: [ if (index == loadingState.response!.length - 1) {
SliverPadding( EasyThrottle.throttle(
padding: EdgeInsets.only( 'member_dynamics', const Duration(milliseconds: 1000),
bottom: MediaQuery.paddingOf(context).bottom + 80, () {
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 const SliverFillRemaining(),
? SliverWaterfallFlow.extent( ],
maxCrossAxisExtent: Grid.smallCardWidth * 2, )
crossAxisSpacing: StyleString.safeSpace, : HttpError(
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(
callback: () { callback: () {
widget.ctr.dynamicState.value = LoadingState.loading(); widget.ctr.dynamicState.value = LoadingState.loading();
widget.ctr.refreshDynamic(); widget.ctr.refreshDynamic();
}, },
), ),
Error() => errorWidget( Error() => HttpError(
errMsg: loadingState.errMsg, errMsg: loadingState.errMsg,
callback: () { callback: () {
widget.ctr.dynamicState.value = LoadingState.loading(); widget.ctr.dynamicState.value = LoadingState.loading();

View File

@@ -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/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/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
@@ -31,20 +32,31 @@ class _AtMePageState extends State<AtMePage> {
onRefresh: () async { onRefresh: () async {
await _atMeController.onRefresh(); 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<List<AtMeItems>?> loadingState) { Widget _buildBody(LoadingState<List<AtMeItems>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => SliverList.builder(
itemCount: 12,
itemBuilder: (context, index) {
return const MsgFeedTopSkeleton();
},
),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? ListView.separated( ? SliverList.separated(
itemCount: loadingState.response!.length, itemCount: loadingState.response!.length,
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
itemBuilder: (context, int index) { itemBuilder: (context, int index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {
_atMeController.onLoadMore(); _atMeController.onLoadMore();
@@ -145,8 +157,8 @@ class _AtMePageState extends State<AtMePage> {
); );
}, },
) )
: scrollErrorWidget(callback: _atMeController.onReload), : HttpError(callback: _atMeController.onReload),
Error() => scrollErrorWidget( Error() => HttpError(
errMsg: loadingState.errMsg, errMsg: loadingState.errMsg,
callback: _atMeController.onReload, callback: _atMeController.onReload,
), ),

View File

@@ -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/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/pair.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
@@ -32,22 +33,36 @@ class _LikeMePageState extends State<LikeMePage> {
onRefresh: () async { onRefresh: () async {
await _likeMeController.onRefresh(); 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) { Widget _buildBody(LoadingState loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => SliverList.builder(
itemCount: 12,
itemBuilder: (context, index) {
return const MsgFeedTopSkeleton();
},
),
Success() => () { Success() => () {
Pair<List<LikeMeItems>, List<LikeMeItems>> pair = Pair<List<LikeMeItems>, List<LikeMeItems>> pair =
loadingState.response; loadingState.response;
List<LikeMeItems> latest = pair.first; List<LikeMeItems> latest = pair.first;
List<LikeMeItems> total = pair.second; List<LikeMeItems> total = pair.second;
if (latest.isNotEmpty || total.isNotEmpty) { if (latest.isNotEmpty || total.isNotEmpty) {
return CustomScrollView( return SliverMainAxisGroup(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
if (latest.isNotEmpty) ...[ if (latest.isNotEmpty) ...[
_buildHeader('最新'), _buildHeader('最新'),
@@ -99,17 +114,12 @@ class _LikeMePageState extends State<LikeMePage> {
}, },
), ),
], ],
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, errMsg: loadingState.errMsg,
callback: _likeMeController.onReload, callback: _likeMeController.onReload,
), ),
@@ -118,14 +128,18 @@ class _LikeMePageState extends State<LikeMePage> {
} }
Widget _buildHeader(String title) { Widget _buildHeader(String title) {
return SliverToBoxAdapter( return SliverSafeArea(
child: Padding( top: false,
padding: const EdgeInsets.only(left: 16), bottom: false,
child: Text( sliver: SliverToBoxAdapter(
title, child: Padding(
style: Theme.of(context).textTheme.labelLarge!.copyWith( padding: const EdgeInsets.only(left: 16),
color: Theme.of(context).colorScheme.secondary, child: Text(
), title,
style: Theme.of(context).textTheme.labelLarge!.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
),
), ),
), ),
); );

View File

@@ -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/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/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/msg/msgfeed_reply_me.dart'; import 'package:PiliPlus/models/msg/msgfeed_reply_me.dart';
@@ -29,20 +30,32 @@ class _ReplyMePageState extends State<ReplyMePage> {
onRefresh: () async { onRefresh: () async {
await _replyMeController.onRefresh(); 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<List<ReplyMeItems>?> loadingState) { Widget _buildBody(LoadingState<List<ReplyMeItems>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => SliverList.builder(
itemCount: 12,
itemBuilder: (context, index) {
return const MsgFeedTopSkeleton();
},
),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? ListView.separated( ? SliverList.separated(
itemCount: loadingState.response!.length, itemCount: loadingState.response!.length,
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
itemBuilder: (context, int index) { itemBuilder: (context, int index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {
_replyMeController.onLoadMore(); _replyMeController.onLoadMore();
@@ -165,8 +178,8 @@ class _ReplyMePageState extends State<ReplyMePage> {
); );
}, },
) )
: scrollErrorWidget(callback: _replyMeController.onReload), : HttpError(callback: _replyMeController.onReload),
Error() => scrollErrorWidget( Error() => HttpError(
errMsg: loadingState.errMsg, errMsg: loadingState.errMsg,
callback: _replyMeController.onReload, callback: _replyMeController.onReload,
), ),

View File

@@ -1,7 +1,8 @@
import 'dart:convert'; 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/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/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/msg/msgfeed_sys_msg.dart'; import 'package:PiliPlus/models/msg/msgfeed_sys_msg.dart';
@@ -36,25 +37,36 @@ class _SysMsgPageState extends State<SysMsgPage> {
onRefresh: () async { onRefresh: () async {
await _sysMsgController.onRefresh(); 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<List<SystemNotifyList>?> loadingState) { Widget _buildBody(LoadingState<List<SystemNotifyList>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => SliverList.builder(
itemCount: 12,
itemBuilder: (context, index) {
return const MsgFeedSysMsgSkeleton();
},
),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? ListView.separated( ? SliverList.separated(
itemCount: loadingState.response!.length, itemCount: loadingState.response!.length,
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
itemBuilder: (context, int index) { itemBuilder: (context, int index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {
_sysMsgController.onLoadMore(); _sysMsgController.onLoadMore();
} }
final item = loadingState.response![index]; final item = loadingState.response![index];
String? content = item.content; String? content = item.content;
if (content != null) { if (content != null) {
@@ -124,8 +136,8 @@ class _SysMsgPageState extends State<SysMsgPage> {
); );
}, },
) )
: scrollErrorWidget(callback: _sysMsgController.onReload), : HttpError(callback: _sysMsgController.onReload),
Error() => scrollErrorWidget( Error() => HttpError(
errMsg: loadingState.errMsg, errMsg: loadingState.errMsg,
callback: _sysMsgController.onReload, callback: _sysMsgController.onReload,
), ),

View File

@@ -1,6 +1,8 @@
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/media_bangumi.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_h.dart';
import 'package:PiliPlus/common/skeleton/video_card_v.dart';
import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
@@ -64,32 +66,57 @@ abstract class CommonSearchPanelState<
Widget get _builLoading { Widget get _builLoading {
return SliverGrid( return SliverGrid(
gridDelegate: widget.searchType == SearchType.media_bangumi || gridDelegate: switch (widget.searchType) {
widget.searchType == SearchType.media_ft SearchType.media_bangumi ||
? SliverGridDelegateWithExtentAndRatio( SearchType.media_ft =>
mainAxisSpacing: 2, SliverGridDelegateWithExtentAndRatio(
maxCrossAxisExtent: Grid.smallCardWidth * 2, mainAxisSpacing: 2,
childAspectRatio: StyleString.aspectRatio * 1.5, maxCrossAxisExtent: Grid.smallCardWidth * 2,
minHeight: MediaQuery.textScalerOf(context).scale(155), childAspectRatio: StyleString.aspectRatio * 1.5,
) minHeight: MediaQuery.textScalerOf(context).scale(155),
: Grid.videoCardHDelegate(context), ),
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( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
switch (widget.searchType) { switch (widget.searchType) {
case SearchType.media_bangumi || SearchType.media_ft: case SearchType.media_bangumi || SearchType.media_ft:
return const MediaBangumiSkeleton(); return const MediaBangumiSkeleton();
case SearchType.bili_user:
return const MsgFeedTopSkeleton();
case SearchType.live_room:
return const VideoCardVSkeleton();
default: default:
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
} }
}, },
childCount: 15, childCount: 16,
), ),
); );
} }
Widget _buildBody(LoadingState<List<T>?> loadingState) { Widget _buildBody(LoadingState<List<T>?> loadingState) {
return switch (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 Success() => loadingState.response?.isNotEmpty == true
? buildList(loadingState.response!) ? buildList(loadingState.response!)
: HttpError( : HttpError(

View File

@@ -28,7 +28,8 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return SliverPadding( 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)), sliver: Obx(() => _buildBody(_relatedController.loadingState.value)),
); );
} }
@@ -45,17 +46,14 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
), ),
), ),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverGrid(
padding: const EdgeInsets.only(bottom: 80), gridDelegate: Grid.videoCardHDelegate(context),
sliver: SliverGrid( delegate: SliverChildBuilderDelegate((context, index) {
gridDelegate: Grid.videoCardHDelegate(context), return VideoCardH(
delegate: SliverChildBuilderDelegate((context, index) { videoItem: loadingState.response![index],
return VideoCardH( showPubdate: true,
videoItem: loadingState.response![index], );
showPubdate: true, }, childCount: loadingState.response!.length),
);
}, childCount: loadingState.response!.length),
),
) )
: const SliverToBoxAdapter(), : const SliverToBoxAdapter(),
Error() => HttpError( Error() => HttpError(

View File

@@ -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/http_error.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
@@ -44,7 +45,12 @@ class _WhisperPageState extends State<WhisperPage> {
Widget _buildBody(LoadingState<List<SessionList>?> loadingState) { Widget _buildBody(LoadingState<List<SessionList>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => const SliverToBoxAdapter(), Loading() => SliverList.builder(
itemCount: 12,
itemBuilder: (context, index) {
return const WhisperItemSkeleton();
},
),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
@@ -85,58 +91,58 @@ class _WhisperPageState extends State<WhisperPage> {
} }
Widget get _buildTopItems => SliverToBoxAdapter( Widget get _buildTopItems => SliverToBoxAdapter(
child: Padding( child: Row(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), mainAxisAlignment: MainAxisAlignment.spaceEvenly,
child: Row( children:
children: List.generate(_whisperController.msgFeedTopItems.length, List.generate(_whisperController.msgFeedTopItems.length, (index) {
(index) { return GestureDetector(
return Expanded( behavior: HitTestBehavior.opaque,
child: GestureDetector( child: Padding(
child: Column( padding: const EdgeInsets.all(10),
mainAxisSize: MainAxisSize.min, child: Column(
crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ mainAxisAlignment: MainAxisAlignment.center,
Obx( children: [
() => Badge( Obx(
isLabelVisible: () => Badge(
_whisperController.unreadCounts[index] > 0, isLabelVisible:
label: Text( _whisperController.unreadCounts[index] > 0,
" ${_whisperController.unreadCounts[index]} "), label:
alignment: Alignment.topRight, Text(" ${_whisperController.unreadCounts[index]} "),
child: CircleAvatar( alignment: Alignment.topRight,
radius: 22, child: CircleAvatar(
backgroundColor: radius: 22,
Theme.of(context).colorScheme.onInverseSurface, backgroundColor:
child: Icon( Theme.of(context).colorScheme.onInverseSurface,
_whisperController.msgFeedTopItems[index]['icon'], child: Icon(
size: 20, _whisperController.msgFeedTopItems[index]['icon'],
color: Theme.of(context).colorScheme.primary, size: 20,
), color: Theme.of(context).colorScheme.primary,
), ),
), ),
), ),
const SizedBox(height: 6), ),
Text( const SizedBox(height: 6),
_whisperController.msgFeedTopItems[index]['name'], Text(
style: const TextStyle(fontSize: 13), _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'],
);
},
), ),
); ),
}).toList(), onTap: () {
), if (!_whisperController.msgFeedTopItems[index]['enabled']) {
SmartDialog.showToast('已禁用');
return;
}
_whisperController.unreadCounts[index] = 0;
Get.toNamed(
_whisperController.msgFeedTopItems[index]['route'],
);
},
);
}).toList(),
), ),
); );
} }