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
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),

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
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,
),

View File

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

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/http/loading_state.dart';
import 'package:PiliPlus/models/user/black.dart';
@@ -40,18 +41,27 @@ class _BlackListPageState extends State<BlackListPage> {
),
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<List<BlackListItem>?> 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<BlackListPage> {
);
},
)
: errorWidget(
: HttpError(
callback: _blackListController.onReload,
),
Error() => errorWidget(
Error() => HttpError(
errMsg: loadingState.errMsg,
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/http/loading_state.dart';
import 'package:PiliPlus/models/fans/result.dart';
@@ -51,7 +52,18 @@ class _FansPageState extends State<FansPage> {
Widget _buildBody(LoadingState<List<FansItemModel>?> 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(

View File

@@ -34,7 +34,14 @@ class _FavArticlePageState extends State<FavArticlePage>
},
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<FavArticlePage>
),
),
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),

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

View File

@@ -37,8 +37,14 @@ class _FavVideoPageState extends State<FavVideoPage>
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<FavVideoPage>
),
),
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<FavFolderItemData> 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<FavFolderItemData> list =
(_favController.loadingState.value as Success)
.response;
list.removeAt(index);
_favController.loadingState.refresh();
}
},
);
},
),
)
: HttpError(

View File

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

View File

@@ -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<MemberCoinPage> {
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<List<MemberCoinsDataModel>?> 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<MemberCoinPage> {
return MemberCoinsItem(coinItem: loadingState.response![index]);
},
)
: scrollErrorWidget(callback: _ctr.onReload),
Error() => scrollErrorWidget(
: HttpError(callback: _ctr.onReload),
Error() => HttpError(
errMsg: loadingState.errMsg,
callback: _ctr.onReload,
),

View File

@@ -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<MemberLikePage> {
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<List<MemberCoinsDataModel>?> 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<MemberLikePage> {
return MemberCoinsItem(coinItem: loadingState.response![index]);
},
)
: scrollErrorWidget(callback: _ctr.onReload),
Error() => scrollErrorWidget(
: HttpError(callback: _ctr.onReload),
Error() => HttpError(
errMsg: loadingState.errMsg,
callback: _ctr.onReload,
),

View File

@@ -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<SearchArchive>
@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<List<VListItemModel>?> 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();

View File

@@ -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<SearchDynamic>
@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<List<DynamicItemModel>?> 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();

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/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<AtMePage> {
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<List<AtMeItems>?> 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<AtMePage> {
);
},
)
: scrollErrorWidget(callback: _atMeController.onReload),
Error() => scrollErrorWidget(
: HttpError(callback: _atMeController.onReload),
Error() => HttpError(
errMsg: loadingState.errMsg,
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/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<LikeMePage> {
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<LikeMeItems>, List<LikeMeItems>> pair =
loadingState.response;
List<LikeMeItems> latest = pair.first;
List<LikeMeItems> 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<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,
callback: _likeMeController.onReload,
),
@@ -118,14 +128,18 @@ class _LikeMePageState extends State<LikeMePage> {
}
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,
),
),
),
),
);

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/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<ReplyMePage> {
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<List<ReplyMeItems>?> 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<ReplyMePage> {
);
},
)
: scrollErrorWidget(callback: _replyMeController.onReload),
Error() => scrollErrorWidget(
: HttpError(callback: _replyMeController.onReload),
Error() => HttpError(
errMsg: loadingState.errMsg,
callback: _replyMeController.onReload,
),

View File

@@ -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<SysMsgPage> {
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<List<SystemNotifyList>?> 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<SysMsgPage> {
);
},
)
: scrollErrorWidget(callback: _sysMsgController.onReload),
Error() => scrollErrorWidget(
: HttpError(callback: _sysMsgController.onReload),
Error() => HttpError(
errMsg: loadingState.errMsg,
callback: _sysMsgController.onReload,
),

View File

@@ -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<List<T>?> 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(

View File

@@ -28,7 +28,8 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
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<RelatedVideoPanel>
),
),
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(

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/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
@@ -44,7 +45,12 @@ class _WhisperPageState extends State<WhisperPage> {
Widget _buildBody(LoadingState<List<SessionList>?> 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<WhisperPage> {
}
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(),
),
);
}