Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-05-18 16:56:43 +08:00
parent f642bfcf48
commit ab80b2a5af
24 changed files with 876 additions and 871 deletions

View File

@@ -196,8 +196,11 @@ class _BangumiPageState extends CommonPageState<BangumiPage, BangumiController>
List<Widget> _buildRcmd(ThemeData theme) => [
_buildRcmdTitle(theme),
SliverPadding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 0, StyleString.safeSpace, 0),
padding: EdgeInsets.only(
left: StyleString.safeSpace,
right: StyleString.safeSpace,
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(
() => _buildRcmdBody(controller.loadingState.value),
),
@@ -330,11 +333,15 @@ class _BangumiPageState extends CommonPageState<BangumiPage, BangumiController>
? Column(
children: [
_buildFollowTitle(theme),
SizedBox(
height: Grid.smallCardWidth / 2 / 0.75 +
MediaQuery.textScalerOf(context).scale(50),
child: Obx(
() => _buildFollowBody(controller.followState.value),
MediaQuery.removePadding(
context: context,
removeLeft: context.orientation == Orientation.landscape,
child: SizedBox(
height: Grid.smallCardWidth / 2 / 0.75 +
MediaQuery.textScalerOf(context).scale(50),
child: Obx(
() => _buildFollowBody(controller.followState.value),
),
),
),
],
@@ -415,31 +422,27 @@ class _BangumiPageState extends CommonPageState<BangumiPage, BangumiController>
return switch (loadingState) {
Loading() => loadingWidget,
Success(:var response) => response?.isNotEmpty == true
? MediaQuery.removePadding(
context: context,
removeLeft: context.orientation == Orientation.landscape,
child: ListView.builder(
controller: controller.followController,
scrollDirection: Axis.horizontal,
itemCount: response!.length,
itemBuilder: (context, index) {
if (index == response.length - 1) {
controller.queryBangumiFollow(false);
}
return Container(
width: Grid.smallCardWidth / 2,
margin: EdgeInsets.only(
left: StyleString.safeSpace,
right: index == response.length - 1
? StyleString.safeSpace
: 0,
),
child: BangumiCardV(
bangumiItem: response[index],
),
);
},
),
? ListView.builder(
controller: controller.followController,
scrollDirection: Axis.horizontal,
itemCount: response!.length,
itemBuilder: (context, index) {
if (index == response.length - 1) {
controller.queryBangumiFollow(false);
}
return Container(
width: Grid.smallCardWidth / 2,
margin: EdgeInsets.only(
left: StyleString.safeSpace,
right: index == response.length - 1
? StyleString.safeSpace
: 0,
),
child: BangumiCardV(
bangumiItem: response[index],
),
);
},
)
: Center(
child: Text(

View File

@@ -46,7 +46,12 @@ class _BlackListPageState extends State<BlackListPage> {
physics: const AlwaysScrollableScrollPhysics(),
controller: _blackListController.scrollController,
slivers: [
Obx(() => _buildBody(_blackListController.loadingState.value))
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver: Obx(
() => _buildBody(_blackListController.loadingState.value)),
)
],
),
),

View File

@@ -55,7 +55,12 @@ abstract class CommonSearchPageState<S extends CommonSearchPage, R, T>
physics: const AlwaysScrollableScrollPhysics(),
controller: controller.scrollController,
slivers: [
Obx(() => _buildBody(controller.loadingState.value)),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
sliver: Obx(() => _buildBody(controller.loadingState.value)),
),
],
),
),

View File

@@ -124,7 +124,12 @@ class _DynamicsTabPageState
physics: const AlwaysScrollableScrollPhysics(),
controller: controller.scrollController,
slivers: [
Obx(() => _buildBody(controller.loadingState.value)),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(() => _buildBody(controller.loadingState.value)),
),
],
),
);
@@ -134,57 +139,51 @@ class _DynamicsTabPageState
return switch (loadingState) {
Loading() => DynamicsTabPage.dynSkeleton(dynamicsWaterfallFlow),
Success(:var response) => response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: dynamicsWaterfallFlow
? SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
crossAxisSpacing: StyleString.cardSpace / 2,
lastChildLayoutTypeBuilder: (index) {
if (index == response.length - 1) {
controller.onLoadMore();
}
return index == response.length
? LastChildLayoutType.foot
: LastChildLayoutType.none;
},
children: [
for (int index = 0; index < response!.length; index++)
DynamicPanel(
item: response[index],
? dynamicsWaterfallFlow
? SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
crossAxisSpacing: StyleString.cardSpace / 2,
lastChildLayoutTypeBuilder: (index) {
if (index == response.length - 1) {
controller.onLoadMore();
}
return index == response.length
? LastChildLayoutType.foot
: LastChildLayoutType.none;
},
children: [
for (int index = 0; index < response!.length; index++)
DynamicPanel(
item: response[index],
onRemove: (idStr) => controller.onRemove(index, idStr),
onBlock: () => controller.onBlock(index),
)
],
)
: SliverCrossAxisGroup(
slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.smallCardWidth * 2,
sliver: SliverList.builder(
itemBuilder: (context, index) {
if (index == response.length - 1) {
controller.onLoadMore();
}
final item = response[index];
return DynamicPanel(
item: item,
onRemove: (idStr) =>
controller.onRemove(index, idStr),
onBlock: () => controller.onBlock(index),
)
],
)
: SliverCrossAxisGroup(
slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.smallCardWidth * 2,
sliver: SliverList.builder(
itemBuilder: (context, index) {
if (index == response.length - 1) {
controller.onLoadMore();
}
final item = response[index];
return DynamicPanel(
item: item,
onRemove: (idStr) =>
controller.onRemove(index, idStr),
onBlock: () => controller.onBlock(index),
);
},
itemCount: response!.length,
),
),
const SliverFillRemaining(),
],
);
},
itemCount: response!.length,
),
),
)
const SliverFillRemaining(),
],
)
: HttpError(
onReload: controller.onReload,
),

View File

@@ -109,7 +109,12 @@ class _DynTopicPageState extends State<DynTopicPage> {
}
return const SliverToBoxAdapter();
}),
Obx(() => _buildBody(_controller.loadingState.value)),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(() => _buildBody(_controller.loadingState.value)),
),
],
),
),
@@ -302,56 +307,51 @@ class _DynTopicPageState extends State<DynTopicPage> {
return switch (loadingState) {
Loading() => DynamicsTabPage.dynSkeleton(dynamicsWaterfallFlow),
Success(:var response) => response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: dynamicsWaterfallFlow
? SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
crossAxisSpacing: StyleString.cardSpace / 2,
lastChildLayoutTypeBuilder: (index) {
if (index == response.length - 1) {
_controller.onLoadMore();
}
return index == response.length
? LastChildLayoutType.foot
: LastChildLayoutType.none;
},
children: [
for (var item in response!)
if (item.dynamicCardItem != null)
DynamicPanel(item: item.dynamicCardItem!)
else
Text(item.topicType ?? 'err'),
],
)
: SliverCrossAxisGroup(
slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.smallCardWidth * 2,
sliver: SliverList.builder(
itemBuilder: (context, index) {
if (index == response.length - 1) {
_controller.onLoadMore();
}
final item = response[index];
if (item.dynamicCardItem != null) {
return DynamicPanel(
item: item.dynamicCardItem!,
);
} else {
return Text(item.topicType ?? 'err');
}
},
itemCount: response!.length,
),
),
const SliverFillRemaining(),
],
? dynamicsWaterfallFlow
? SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
crossAxisSpacing: StyleString.cardSpace / 2,
lastChildLayoutTypeBuilder: (index) {
if (index == response.length - 1) {
_controller.onLoadMore();
}
return index == response.length
? LastChildLayoutType.foot
: LastChildLayoutType.none;
},
children: [
for (var item in response!)
if (item.dynamicCardItem != null)
DynamicPanel(item: item.dynamicCardItem!)
else
Text(item.topicType ?? 'err'),
],
)
: SliverCrossAxisGroup(
slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.smallCardWidth * 2,
sliver: SliverList.builder(
itemBuilder: (context, index) {
if (index == response.length - 1) {
_controller.onLoadMore();
}
final item = response[index];
if (item.dynamicCardItem != null) {
return DynamicPanel(
item: item.dynamicCardItem!,
);
} else {
return Text(item.topicType ?? 'err');
}
},
itemCount: response!.length,
),
),
)
const SliverFillRemaining(),
],
)
: HttpError(
onReload: _controller.onReload,
),

View File

@@ -61,7 +61,12 @@ class _FansPageState extends State<FansPage> {
physics: const AlwaysScrollableScrollPhysics(),
controller: _fansController.scrollController,
slivers: [
Obx(() => _buildBody(_fansController.loadingState.value)),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver:
Obx(() => _buildBody(_fansController.loadingState.value)),
),
],
),
),
@@ -84,74 +89,70 @@ class _FansPageState extends State<FansPage> {
),
),
Success(:var response) => response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
mainAxisExtent: 66,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == response.length - 1) {
_fansController.onLoadMore();
}
final item = response[index];
String heroTag = Utils.makeHeroTag(item.mid);
return ListTile(
onTap: () {
if (widget.onSelect != null) {
widget.onSelect!(UserModel(
mid: item.mid!,
name: item.uname!,
avatar: item.face!,
));
return;
}
Get.toNamed(
'/member?mid=${item.mid}',
arguments: {'face': item.face, 'heroTag': heroTag},
);
},
onLongPress: widget.onSelect != null
? null
: isOwner
? () {
showConfirmDialog(
context: context,
title: '确定移除 ${item.uname} ',
onConfirm: () {
_fansController.onRemoveFan(
index, item.mid!);
},
);
}
: null,
leading: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 45,
height: 45,
type: ImageType.avatar,
src: item.face,
),
? SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
mainAxisExtent: 66,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == response.length - 1) {
_fansController.onLoadMore();
}
final item = response[index];
String heroTag = Utils.makeHeroTag(item.mid);
return ListTile(
onTap: () {
if (widget.onSelect != null) {
widget.onSelect!(UserModel(
mid: item.mid!,
name: item.uname!,
avatar: item.face!,
));
return;
}
Get.toNamed(
'/member?mid=${item.mid}',
arguments: {'face': item.face, 'heroTag': heroTag},
);
},
onLongPress: widget.onSelect != null
? null
: isOwner
? () {
showConfirmDialog(
context: context,
title: '确定移除 ${item.uname} ',
onConfirm: () {
_fansController.onRemoveFan(
index, item.mid!);
},
);
}
: null,
leading: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 45,
height: 45,
type: ImageType.avatar,
src: item.face,
),
title: Text(
item.uname!,
style: const TextStyle(fontSize: 14),
),
subtitle: Text(
item.sign ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
dense: true,
trailing: const SizedBox(width: 6),
);
},
childCount: response!.length,
),
),
title: Text(
item.uname!,
style: const TextStyle(fontSize: 14),
),
subtitle: Text(
item.sign ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
dense: true,
trailing: const SizedBox(width: 6),
);
},
childCount: response!.length,
),
)
: HttpError(

View File

@@ -37,7 +37,12 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
onRefresh: _favNoteController.onRefresh,
child: CustomScrollView(
slivers: [
Obx(() => _buildBody(_favNoteController.loadingState.value)),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver: Obx(
() => _buildBody(_favNoteController.loadingState.value)),
),
],
),
),
@@ -139,26 +144,22 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
),
),
Success(:var response) => response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver: SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == response.length - 1) {
_favNoteController.onLoadMore();
}
return FavNoteItem(
item: response[index],
ctr: _favNoteController,
onSelect: () {
_favNoteController.onSelect(index);
},
);
},
childCount: response!.length,
),
? SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == response.length - 1) {
_favNoteController.onLoadMore();
}
return FavNoteItem(
item: response[index],
ctr: _favNoteController,
onSelect: () {
_favNoteController.onSelect(index);
},
);
},
childCount: response!.length,
),
)
: HttpError(onReload: _favNoteController.onReload),

View File

@@ -44,7 +44,12 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
onRefresh: _favPgcController.onRefresh,
child: CustomScrollView(
slivers: [
Obx(() => _buildBody(_favPgcController.loadingState.value)),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver: Obx(
() => _buildBody(_favPgcController.loadingState.value)),
),
],
),
),
@@ -162,48 +167,44 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
),
),
Success(:var response) => response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver: SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == response.length - 1) {
_favPgcController.onLoadMore();
}
final item = response[index];
return FavPgcItem(
item: item,
ctr: _favPgcController,
onSelect: () {
_favPgcController.onSelect(index);
},
onUpdateStatus: () {
showPgcFollowDialog(
context: context,
type: widget.type == 0 ? '追番' : '追剧',
followStatus: widget.followStatus,
onUpdateStatus: (followStatus) {
if (followStatus == -1) {
_favPgcController.bangumiDel(
index,
item.seasonId,
);
} else {
_favPgcController.onUpdate(
index,
followStatus,
item.seasonId,
);
}
},
);
},
);
},
childCount: response!.length,
),
? SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == response.length - 1) {
_favPgcController.onLoadMore();
}
final item = response[index];
return FavPgcItem(
item: item,
ctr: _favPgcController,
onSelect: () {
_favPgcController.onSelect(index);
},
onUpdateStatus: () {
showPgcFollowDialog(
context: context,
type: widget.type == 0 ? '追番' : '追剧',
followStatus: widget.followStatus,
onUpdateStatus: (followStatus) {
if (followStatus == -1) {
_favPgcController.bangumiDel(
index,
item.seasonId,
);
} else {
_favPgcController.onUpdate(
index,
followStatus,
item.seasonId,
);
}
},
);
},
);
},
childCount: response!.length,
),
)
: HttpError(onReload: _favPgcController.onReload),

View File

@@ -70,8 +70,13 @@ class _FavDetailPageState extends State<FavDetailPage> {
controller: _favDetailController.scrollController,
slivers: [
_buildHeader(theme),
Obx(() => _buildBody(
theme, _favDetailController.loadingState.value)),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 85,
),
sliver: Obx(() => _buildBody(
theme, _favDetailController.loadingState.value)),
),
],
),
),
@@ -365,123 +370,116 @@ class _FavDetailPageState extends State<FavDetailPage> {
),
),
Success(:var response) => response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 85,
),
sliver: SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == response.length) {
_favDetailController.onLoadMore();
return Container(
height: 60,
alignment: Alignment.center,
child: Text(
_favDetailController.isEnd ? '没有更多了' : '加载中...',
style: TextStyle(
color: theme.colorScheme.outline,
fontSize: 13,
),
? SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == response.length) {
_favDetailController.onLoadMore();
return Container(
height: 60,
alignment: Alignment.center,
child: Text(
_favDetailController.isEnd ? '没有更多了' : '加载中...',
style: TextStyle(
color: theme.colorScheme.outline,
fontSize: 13,
),
);
}
FavDetailItemData item = response[index];
return Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: FavVideoCardH(
videoItem: item,
onDelFav: _favDetailController.isOwner.value
? () => _favDetailController.onCancelFav(
index,
item.id!,
item.type!,
)
: null,
onViewFav: () {
PageUtils.toVideoPage(
'bvid=${item.bvid}&cid=${item.cid}',
arguments: {
'videoItem': item,
'heroTag': Utils.makeHeroTag(item.bvid),
'sourceType': 'fav',
'mediaId': _favDetailController.item.value.id,
'oid': item.id,
'favTitle':
_favDetailController.item.value.title,
'count': _favDetailController
.item.value.mediaCount,
'desc': true,
'isContinuePlaying': index != 0,
'isOwner': _favDetailController.isOwner.value,
},
);
},
onTap: _favDetailController.enableMultiSelect.value
? () {
),
);
}
FavDetailItemData item = response[index];
return Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: FavVideoCardH(
videoItem: item,
onDelFav: _favDetailController.isOwner.value
? () => _favDetailController.onCancelFav(
index,
item.id!,
item.type!,
)
: null,
onViewFav: () {
PageUtils.toVideoPage(
'bvid=${item.bvid}&cid=${item.cid}',
arguments: {
'videoItem': item,
'heroTag': Utils.makeHeroTag(item.bvid),
'sourceType': 'fav',
'mediaId': _favDetailController.item.value.id,
'oid': item.id,
'favTitle':
_favDetailController.item.value.title,
'count':
_favDetailController.item.value.mediaCount,
'desc': true,
'isContinuePlaying': index != 0,
'isOwner': _favDetailController.isOwner.value,
},
);
},
onTap: _favDetailController.enableMultiSelect.value
? () {
_favDetailController.onSelect(index);
}
: null,
onLongPress: _favDetailController.isOwner.value
? () {
if (_favDetailController
.enableMultiSelect.value.not) {
_favDetailController
.enableMultiSelect.value = true;
_favDetailController.onSelect(index);
}
: null,
onLongPress: _favDetailController.isOwner.value
? () {
if (_favDetailController
.enableMultiSelect.value.not) {
_favDetailController
.enableMultiSelect.value = true;
_favDetailController.onSelect(index);
}
}
: null,
),
}
: null,
),
Positioned(
top: 5,
left: 12,
bottom: 5,
child: IgnorePointer(
child: LayoutBuilder(
builder: (context, constraints) =>
AnimatedOpacity(
opacity: item.checked == true ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: Container(
alignment: Alignment.center,
height: constraints.maxHeight,
width: constraints.maxHeight *
StyleString.aspectRatio,
decoration: BoxDecoration(
borderRadius: StyleString.mdRadius,
color: Colors.black.withValues(alpha: 0.6),
),
child: SizedBox(
width: 34,
height: 34,
child: AnimatedScale(
scale: item.checked == true ? 1 : 0,
duration:
const Duration(milliseconds: 250),
curve: Curves.easeInOut,
child: IconButton(
style: ButtonStyle(
padding: WidgetStateProperty.all(
EdgeInsets.zero),
backgroundColor:
WidgetStateProperty.resolveWith(
(states) {
return theme.colorScheme.surface
.withValues(alpha: 0.8);
},
),
),
onPressed: null,
icon: Icon(
Icons.done_all_outlined,
color: theme.colorScheme.primary,
),
Positioned(
top: 5,
left: 12,
bottom: 5,
child: IgnorePointer(
child: LayoutBuilder(
builder: (context, constraints) => AnimatedOpacity(
opacity: item.checked == true ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: Container(
alignment: Alignment.center,
height: constraints.maxHeight,
width: constraints.maxHeight *
StyleString.aspectRatio,
decoration: BoxDecoration(
borderRadius: StyleString.mdRadius,
color: Colors.black.withValues(alpha: 0.6),
),
child: SizedBox(
width: 34,
height: 34,
child: AnimatedScale(
scale: item.checked == true ? 1 : 0,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
child: IconButton(
style: ButtonStyle(
padding: WidgetStateProperty.all(
EdgeInsets.zero),
backgroundColor:
WidgetStateProperty.resolveWith(
(states) {
return theme.colorScheme.surface
.withValues(alpha: 0.8);
},
),
),
onPressed: null,
icon: Icon(
Icons.done_all_outlined,
color: theme.colorScheme.primary,
),
),
),
),
@@ -489,11 +487,11 @@ class _FavDetailPageState extends State<FavDetailPage> {
),
),
),
],
);
},
childCount: response!.length + 1,
),
),
],
);
},
childCount: response!.length + 1,
),
)
: HttpError(

View File

@@ -25,49 +25,44 @@ class _FavSearchPageState extends CommonSearchPageState<FavSearchPage,
@override
Widget buildList(List<FavDetailItemData> list) {
return SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
sliver: SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context, minHeight: 110),
delegate: SliverChildBuilderDelegate(
childCount: list.length,
(context, index) {
if (index == list.length - 1) {
controller.onLoadMore();
}
final item = list[index];
return FavVideoCardH(
videoItem: item,
onDelFav: controller.isOwner == true
? () {
controller.onCancelFav(
index,
item.id!,
item.type,
);
}
: null,
onViewFav: () {
PageUtils.toVideoPage(
'bvid=${item.bvid}&cid=${item.cid}',
arguments: {
'videoItem': item,
'heroTag': Utils.makeHeroTag(item.bvid),
'sourceType': 'fav',
'mediaId': controller.mediaId,
'oid': item.id,
'favTitle': controller.title,
'count': controller.count,
'desc': true,
'isContinuePlaying': true,
},
);
},
);
},
),
return SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context, minHeight: 110),
delegate: SliverChildBuilderDelegate(
childCount: list.length,
(context, index) {
if (index == list.length - 1) {
controller.onLoadMore();
}
final item = list[index];
return FavVideoCardH(
videoItem: item,
onDelFav: controller.isOwner == true
? () {
controller.onCancelFav(
index,
item.id!,
item.type,
);
}
: null,
onViewFav: () {
PageUtils.toVideoPage(
'bvid=${item.bvid}&cid=${item.cid}',
arguments: {
'videoItem': item,
'heroTag': Utils.makeHeroTag(item.bvid),
'sourceType': 'fav',
'mediaId': controller.mediaId,
'oid': item.id,
'favTitle': controller.title,
'count': controller.count,
'desc': true,
'isContinuePlaying': true,
},
);
},
);
},
),
);
}

View File

@@ -30,26 +30,21 @@ class _FollowSearchPageState extends CommonSearchPageState<FollowSearchPage,
@override
Widget buildList(List<FollowItemModel> list) {
return SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
sliver: SliverList.builder(
itemCount: list.length,
itemBuilder: ((context, index) {
if (index == list.length - 1) {
controller.onLoadMore();
}
return FollowItem(
item: list[index],
onSelect: widget.mid != null && widget.isFromSelect != false
? (userModel) {
Get.back(result: userModel);
}
: null,
);
}),
),
return SliverList.builder(
itemCount: list.length,
itemBuilder: ((context, index) {
if (index == list.length - 1) {
controller.onLoadMore();
}
return FollowItem(
item: list[index],
onSelect: widget.mid != null && widget.isFromSelect != false
? (userModel) {
Get.back(result: userModel);
}
: null,
);
}),
);
}
}

View File

@@ -24,29 +24,24 @@ class _HistorySearchPageState
@override
Widget buildList(List<HisListItem> list) {
return SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
sliver: SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context, minHeight: 110),
delegate: SliverChildBuilderDelegate(
childCount: list.length,
(context, index) {
if (index == list.length - 1) {
controller.onLoadMore();
}
final item = list[index];
return HistoryItem(
videoItem: item,
ctr: controller,
onChoose: null,
onDelete: (kid, business) {
controller.onDelHistory(index, kid, business);
},
);
},
),
return SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context, minHeight: 110),
delegate: SliverChildBuilderDelegate(
childCount: list.length,
(context, index) {
if (index == list.length - 1) {
controller.onLoadMore();
}
final item = list[index];
return HistoryItem(
videoItem: item,
ctr: controller,
onChoose: null,
onDelete: (kid, business) {
controller.onDelHistory(index, kid, business);
},
);
},
),
);
}

View File

@@ -26,64 +26,59 @@ class _LaterSearchPageState
@override
Widget buildList(List<HotVideoItemModel> list) {
return SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
sliver: SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context, minHeight: 110),
delegate: SliverChildBuilderDelegate(
childCount: list.length,
(context, index) {
if (index == list.length - 1) {
controller.onLoadMore();
}
final item = list[index];
return Stack(
clipBehavior: Clip.none,
children: [
VideoCardH(
videoItem: item,
source: 'later',
onViewLater: (cid) {
PageUtils.toVideoPage(
'bvid=${item.bvid}&cid=$cid',
arguments: {
'videoItem': item,
'oid': item.aid,
'heroTag': Utils.makeHeroTag(item.bvid),
'sourceType': 'watchLater',
'count': controller.count,
'favTitle': '稍后再看',
'mediaId': controller.mid,
'desc': false,
'isContinuePlaying': index != 0,
},
return SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context, minHeight: 110),
delegate: SliverChildBuilderDelegate(
childCount: list.length,
(context, index) {
if (index == list.length - 1) {
controller.onLoadMore();
}
final item = list[index];
return Stack(
clipBehavior: Clip.none,
children: [
VideoCardH(
videoItem: item,
source: 'later',
onViewLater: (cid) {
PageUtils.toVideoPage(
'bvid=${item.bvid}&cid=$cid',
arguments: {
'videoItem': item,
'oid': item.aid,
'heroTag': Utils.makeHeroTag(item.bvid),
'sourceType': 'watchLater',
'count': controller.count,
'favTitle': '稍后再看',
'mediaId': controller.mid,
'desc': false,
'isContinuePlaying': index != 0,
},
);
},
),
Positioned(
right: 12,
bottom: 0,
child: iconButton(
tooltip: '移除',
context: context,
onPressed: () {
controller.toViewDel(
context,
index,
item.aid,
);
},
icon: Icons.clear,
iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
bgColor: Colors.transparent,
),
Positioned(
right: 12,
bottom: 0,
child: iconButton(
tooltip: '移除',
context: context,
onPressed: () {
controller.toViewDel(
context,
index,
item.aid,
);
},
icon: Icons.clear,
iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
bgColor: Colors.transparent,
),
),
],
);
},
),
),
],
);
},
),
);
}

View File

@@ -41,7 +41,14 @@ class _MemberFavoriteState extends State<MemberFavorite>
return refreshIndicator(
onRefresh: _controller.onRefresh,
child: CustomScrollView(
slivers: [Obx(() => _buildBody(theme, _controller.loadingState.value))],
slivers: [
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver:
Obx(() => _buildBody(theme, _controller.loadingState.value)),
),
],
),
);
}
@@ -68,11 +75,6 @@ class _MemberFavoriteState extends State<MemberFavorite>
child: Obx(
() => _buildItem(theme, _controller.second.value, false)),
),
SliverToBoxAdapter(
child: SizedBox(
height: 80 + MediaQuery.of(context).padding.bottom,
),
),
],
)
: HttpError(

View File

@@ -219,7 +219,7 @@ class _MemberVideoState extends State<MemberVideo>
}
final SpaceArchiveItem item = response[index];
return VideoCardHMemberVideo(
key: ValueKey('${item.param}'),
// key: ValueKey('${item.param}'),
videoItem: item,
fromViewAid: _controller.fromViewAid,
);

View File

@@ -121,8 +121,12 @@ class _SearchTrendingPageState extends State<SearchTrendingPage> {
filterQuality: FilterQuality.low,
),
),
Obx(() =>
_buildBody(theme, _controller.loadingState.value)),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 100),
sliver: Obx(() =>
_buildBody(theme, _controller.loadingState.value)),
),
],
),
),
@@ -138,77 +142,73 @@ class _SearchTrendingPageState extends State<SearchTrendingPage> {
return switch (loadingState) {
Loading() => const SliverToBoxAdapter(child: LinearProgressIndicator()),
Success(:var response) => response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 100),
sliver: SliverList.separated(
itemCount: response!.length,
itemBuilder: (context, index) {
final item = response[index];
return ListTile(
dense: true,
onTap: () {
Get.toNamed(
'/searchResult',
parameters: {
'keyword': item.keyword!,
},
);
},
leading: index < _controller.topCount
? const Icon(
size: 17,
Icons.vertical_align_top_outlined,
color: Color(0xFFd1403e),
)
: Text(
'${index + 1 - _controller.topCount}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: switch (index - _controller.topCount) {
0 => const Color(0xFFfdad13),
1 => const Color(0xFF8aace1),
2 => const Color(0xFFdfa777),
_ => theme.colorScheme.outline,
},
fontSize: 17,
fontStyle: FontStyle.italic,
),
),
title: Row(
children: [
Flexible(
child: Text(
item.keyword!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
strutStyle: const StrutStyle(height: 1, leading: 0),
style: const TextStyle(height: 1, fontSize: 15),
? SliverList.separated(
itemCount: response!.length,
itemBuilder: (context, index) {
final item = response[index];
return ListTile(
dense: true,
onTap: () {
Get.toNamed(
'/searchResult',
parameters: {
'keyword': item.keyword!,
},
);
},
leading: index < _controller.topCount
? const Icon(
size: 17,
Icons.vertical_align_top_outlined,
color: Color(0xFFd1403e),
)
: Text(
'${index + 1 - _controller.topCount}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: switch (index - _controller.topCount) {
0 => const Color(0xFFfdad13),
1 => const Color(0xFF8aace1),
2 => const Color(0xFFdfa777),
_ => theme.colorScheme.outline,
},
fontSize: 17,
fontStyle: FontStyle.italic,
),
),
if (item.icon?.isNotEmpty == true) ...[
const SizedBox(width: 4),
CachedNetworkImage(
imageUrl: Utils.thumbnailImgUrl(item.icon!),
height: 16,
),
] else if (item.showLiveIcon == true) ...[
const SizedBox(width: 4),
Image.asset(
'assets/images/live/live.gif',
width: 51,
height: 16,
),
],
title: Row(
children: [
Flexible(
child: Text(
item.keyword!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
strutStyle: const StrutStyle(height: 1, leading: 0),
style: const TextStyle(height: 1, fontSize: 15),
),
),
if (item.icon?.isNotEmpty == true) ...[
const SizedBox(width: 4),
CachedNetworkImage(
imageUrl: Utils.thumbnailImgUrl(item.icon!),
height: 16,
),
] else if (item.showLiveIcon == true) ...[
const SizedBox(width: 4),
Image.asset(
'assets/images/live/live.gif',
width: 51,
height: 16,
),
],
),
);
},
separatorBuilder: (context, index) => Divider(
height: 1,
indent: 48,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
],
),
);
},
separatorBuilder: (context, index) => Divider(
height: 1,
indent: 48,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
)
: HttpError(

View File

@@ -82,20 +82,20 @@ class _SettingsSearchPageState extends State<SettingsSearchPage> {
bottom: false,
child: CustomScrollView(
slivers: [
Obx(
() => _list.isEmpty
? const HttpError()
: SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: SliverWaterfallFlow.extent(
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(
() => _list.isEmpty
? const HttpError()
: SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
children: [
..._list.map((item) => item.widget),
],
),
),
),
),
],
),

View File

@@ -40,7 +40,13 @@ class _SubDetailPageState extends State<SubDetailPage> {
slivers: [
_buildAppBar(theme),
_buildCount(theme),
Obx(() => _buildBody(_subDetailController.loadingState.value)),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(
() => _buildBody(_subDetailController.loadingState.value)),
),
],
),
),
@@ -58,23 +64,18 @@ class _SubDetailPageState extends State<SubDetailPage> {
),
),
Success(:var response) => response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
childCount: response!.length,
(context, index) {
if (index == response.length - 1) {
_subDetailController.onLoadMore();
}
return SubVideoCardH(
videoItem: response[index],
);
},
),
? SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
childCount: response!.length,
(context, index) {
if (index == response.length - 1) {
_subDetailController.onLoadMore();
}
return SubVideoCardH(
videoItem: response[index],
);
},
),
)
: HttpError(

View File

@@ -155,7 +155,7 @@ class _MediaListPanelState
widget.loadMoreMedia();
}
return SizedBox(
key: ValueKey('${item.aid}'),
// key: ValueKey('${item.aid}'),
height: 98,
child: InkWell(
onTap: () async {

View File

@@ -4,16 +4,16 @@ import 'package:PiliPlus/models/common/member/contribute_type.dart';
import 'package:PiliPlus/models/member/info.dart';
import 'package:PiliPlus/models/space_archive/data.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:get/get.dart';
class HorizontalMemberPageController extends CommonDataController {
HorizontalMemberPageController({this.mid, required this.lastAid});
class HorizontalMemberPageController
extends CommonListController<SpaceArchiveData, SpaceArchiveItem> {
HorizontalMemberPageController({this.mid, required this.currAid});
dynamic mid;
int currentPage = 0;
Rx<LoadingState<MemberInfoModel>> userState =
LoadingState<MemberInfoModel>.loading().obs;
RxMap userStat = {}.obs;
@@ -54,27 +54,29 @@ class HorizontalMemberPageController extends CommonDataController {
bool customHandleResponse(bool isRefresh, Success response) {
SpaceArchiveData data = response.response;
count.value = data.count ?? -1;
if (currentPage == 0 || isLoadPrevious) {
hasPrev = data.hasPrev ?? false;
}
if (currentPage == 0 || !isLoadPrevious) {
hasNext = data.hasNext ?? false;
}
if (currentPage != 0 && loadingState.value is Success) {
data.item ??= <SpaceArchiveItem>[];
if (isRefresh) {
if (isLoadPrevious) {
data.item!.addAll((loadingState.value as Success).response);
hasPrev = data.hasPrev ?? false;
} else {
data.item!.insertAll(0, (loadingState.value as Success).response);
hasNext = data.hasNext ?? false;
}
}
if (isLoadPrevious && loadingState.value is Success) {
data.item ??= <SpaceArchiveItem>[];
data.item!.addAll((loadingState.value as Success).response);
} else if (!isRefresh && loadingState.value is Success) {
data.item ??= <SpaceArchiveItem>[];
data.item!.insertAll(0, (loadingState.value as Success).response);
}
firstAid = data.item?.firstOrNull?.param;
lastAid = data.item?.lastOrNull?.param;
loadingState.value = Success(data.item);
isLoadPrevious = false;
page++;
return true;
}
String? currAid;
String? firstAid;
String? lastAid;
RxString order = 'pubdate'.obs;
@@ -84,27 +86,45 @@ class HorizontalMemberPageController extends CommonDataController {
bool hasNext = true;
@override
Future<LoadingState> customGetData() => MemberHttp.spaceArchive(
Future<LoadingState<SpaceArchiveData>> customGetData() =>
MemberHttp.spaceArchive(
type: ContributeType.video,
mid: mid,
aid: isLoadPrevious ? firstAid : lastAid,
aid: page == 1
? currAid
: isLoadPrevious
? firstAid
: lastAid,
order: order.value,
sort: isLoadPrevious ? 'asc' : null,
sort: page != 1 && isLoadPrevious ? 'asc' : null,
pn: null,
next: null,
seasonId: null,
seriesId: null,
includeCursor: currentPage == 0 ? true : null,
includeCursor: page == 1 ? true : null,
);
@override
Future<void> onRefresh() {
currentPage = 0;
hasPrev = true;
hasNext = true;
if (!hasPrev) {
return Future.value();
}
isLoadPrevious = true;
return queryData();
}
@override
Future<void> onReload() {
firstAid = null;
lastAid = null;
hasNext = true;
hasPrev = true;
isEnd = false;
page = 1;
scrollController.jumpToTop();
return super.onReload();
}
void queryBySort() {
order.value = order.value == 'pubdate' ? 'click' : 'pubdate';
onReload();

View File

@@ -1,8 +1,10 @@
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/video_card/video_card_h_member_video.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart';
@@ -49,31 +51,12 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
_controller = Get.put(
HorizontalMemberPageController(
mid: widget.mid,
lastAid: widget.videoDetailController.oid.value.toString(),
currAid: widget.videoDetailController.oid.value.toString(),
),
tag: widget.videoDetailController.heroTag,
);
_bvid = widget.videoDetailController.bvid;
_ownerMid = Accounts.main.mid;
if (_controller.hasPrev) {
_controller.scrollController.addListener(listener);
}
}
void listener() {
if (_controller.scrollController.position.pixels == 0) {
if (_controller.hasPrev) {
_controller
..isLoadPrevious = true
..onLoadMore();
}
}
}
@override
void dispose() {
_controller.scrollController.removeListener(listener);
super.dispose();
}
@override
@@ -104,10 +87,22 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
],
),
_buildUserInfo(theme, response),
const SizedBox(height: 5),
Expanded(
child: Obx(
() => _buildVideoList(theme, _controller.loadingState.value)),
child: refreshIndicator(
onRefresh: _controller.onRefresh,
child: CustomScrollView(
controller: _controller.scrollController,
// physics: PositionRetainedScrollPhysics(
// shouldRetain: _controller.hasPrev,
// parent: const ClampingScrollPhysics(),
// ),
slivers: [
_buildSliverHeader(theme),
Obx(() =>
_buildVideoList(theme, _controller.loadingState.value)),
],
),
),
),
],
),
@@ -176,33 +171,34 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
);
}
Widget _buildVideoList(ThemeData theme, LoadingState loadingState) {
Widget _buildVideoList(
ThemeData theme, LoadingState<List<SpaceArchiveItem>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success(:var response) => Material(
color: Colors.transparent,
child: CustomScrollView(
controller: _controller.scrollController,
physics: PositionRetainedScrollPhysics(
shouldRetain: _controller.hasPrev,
parent: const ClampingScrollPhysics(),
),
slivers: [
_buildSliverHeader(theme),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
sliver: SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == response.length - 1 && _controller.hasNext) {
_controller.onLoadMore();
}
final SpaceArchiveItem videoItem = response[index];
return VideoCardHMemberVideo(
key: ValueKey('${videoItem.param}'),
Loading() => SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
childCount: 10,
(context, index) {
return const VideoCardHSkeleton();
},
),
),
Success(:var response) => response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
sliver: SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == response.length - 1 && _controller.hasNext) {
_controller.onLoadMore();
}
final SpaceArchiveItem videoItem = response[index];
return Material(
color: Colors.transparent,
child: VideoCardHMemberVideo(
videoItem: videoItem,
bvid: _bvid,
onTap: () {
@@ -219,17 +215,15 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
setState(() {});
}
},
);
},
childCount: response.length,
),
),
);
},
childCount: response!.length,
),
),
],
),
),
Error(:var errMsg) => scrollErrorWidget(
controller: _controller.scrollController,
)
: HttpError(onReload: _controller.onReload),
Error(:var errMsg) => HttpError(
errMsg: errMsg,
onReload: _controller.onReload,
),

View File

@@ -10,7 +10,7 @@ import 'package:PiliPlus/pages/video/note/controller.dart';
import 'package:PiliPlus/pages/webview/view.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage.dart' show Accounts;
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -84,52 +84,73 @@ class _NoteListPageState extends CommonSlidePageState<NoteListPage> {
const SizedBox(width: 16),
],
),
body: enableSlide
? slideList(theme,
Obx(() => _buildBody(theme, _controller.loadingState.value)))
: Obx(() => _buildBody(theme, _controller.loadingState.value)),
bottomNavigationBar: Container(
padding: EdgeInsets.only(
left: 12,
right: 12,
top: 6,
bottom: MediaQuery.paddingOf(context).bottom + 6,
),
width: double.infinity,
decoration: BoxDecoration(
color: theme.colorScheme.onInverseSurface,
border: Border(
top: BorderSide(
width: 0.5,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
),
),
child: FilledButton.tonal(
style: FilledButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: EdgeInsets.zero,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
),
),
onPressed: () {
if (!Accounts.main.isLogin) {
SmartDialog.showToast('账号未登录');
return;
}
_key.currentState?.showBottomSheet(
(context) => WebviewPage(
oid: widget.oid,
title: widget.title,
url:
'https://www.bilibili.com/h5/note-app?oid=${widget.oid}&pagefrom=ugcvideo&is_stein_gate=${widget.isStein ? 1 : 0}',
body: enableSlide ? slideList(theme) : buildList(theme),
),
);
}
@override
Widget buildList(ThemeData theme) {
return refreshIndicator(
onRefresh: _controller.onRefresh,
child: Column(
children: [
Expanded(
child: CustomScrollView(
controller: _controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: const EdgeInsets.only(bottom: 80),
sliver: Obx(
() => _buildBody(theme, _controller.loadingState.value)),
),
);
},
child: const Text('开始记笔记'),
],
),
),
),
Container(
padding: EdgeInsets.only(
left: 12,
right: 12,
top: 6,
bottom: MediaQuery.paddingOf(context).bottom + 6,
),
width: double.infinity,
decoration: BoxDecoration(
color: theme.colorScheme.onInverseSurface,
border: Border(
top: BorderSide(
width: 0.5,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
),
),
child: FilledButton.tonal(
style: FilledButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: EdgeInsets.zero,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
),
),
onPressed: () {
if (!Accounts.main.isLogin) {
SmartDialog.showToast('账号未登录');
return;
}
_key.currentState?.showBottomSheet(
(context) => WebviewPage(
oid: widget.oid,
title: widget.title,
url:
'https://www.bilibili.com/h5/note-app?oid=${widget.oid}&pagefrom=ugcvideo&is_stein_gate=${widget.isStein ? 1 : 0}',
),
);
},
child: const Text('开始记笔记'),
),
),
],
),
);
}
@@ -137,59 +158,37 @@ class _NoteListPageState extends CommonSlidePageState<NoteListPage> {
Widget _buildBody(
ThemeData theme, LoadingState<List<dynamic>?> loadingState) {
return switch (loadingState) {
Loading() => CustomScrollView(
physics: const NeverScrollableScrollPhysics(),
slivers: [
SliverList.builder(
itemBuilder: (context, index) {
return const VideoReplySkeleton();
},
itemCount: 8,
)
],
Loading() => SliverToBoxAdapter(
child: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return const VideoReplySkeleton();
},
itemCount: 8,
),
),
Success(:var response) => response?.isNotEmpty == true
? refreshIndicator(
onRefresh: _controller.onRefresh,
child: CustomScrollView(
controller: _controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverList.separated(
itemBuilder: (context, index) {
if (index == response.length - 1) {
_controller.onLoadMore();
}
return _itemWidget(context, theme, response[index]);
},
itemCount: response!.length,
separatorBuilder: (context, index) => Divider(
height: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
),
SliverToBoxAdapter(
child: SizedBox(
height: MediaQuery.paddingOf(context).bottom + 80,
),
),
],
? SliverList.separated(
itemBuilder: (context, index) {
if (index == response.length - 1) {
_controller.onLoadMore();
}
return _itemWidget(context, theme, response[index]);
},
itemCount: response!.length,
separatorBuilder: (context, index) => Divider(
height: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
)
: errWidget(),
Error(:var errMsg) => errWidget(errMsg),
: HttpError(onReload: _controller.onReload),
Error(:var errMsg) => HttpError(
errMsg: errMsg,
onReload: _controller.onReload,
),
};
}
Widget errWidget([errMsg]) => CustomScrollView(
controller: _controller.scrollController,
slivers: [
HttpError(
errMsg: errMsg,
onReload: _controller.onReload,
)
],
);
}
Widget _itemWidget(BuildContext context, ThemeData theme, dynamic item) {

View File

@@ -387,17 +387,13 @@ class _VideoReplyReplyPanelState
ThemeData theme, LoadingState<List<ReplyInfo>?> loadingState, int index) {
return switch (loadingState) {
Loading() => IgnorePointer(
child: CustomScrollView(
child: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
slivers: [
SliverList.builder(
itemBuilder: (context, index) {
return const VideoReplySkeleton();
},
itemCount: 8,
)
],
itemBuilder: (context, index) {
return const VideoReplySkeleton();
},
itemCount: 8,
),
),
Success(:var response) => () {

View File

@@ -123,8 +123,11 @@ class _WhisperDetailPageState
hidePanel();
},
behavior: HitTestBehavior.opaque,
child: Obx(() =>
_buildBody(_whisperDetailController.loadingState.value)),
child: refreshIndicator(
onRefresh: _whisperDetailController.onRefresh,
child: Obx(() =>
_buildBody(_whisperDetailController.loadingState.value)),
),
),
),
if (_whisperDetailController.mid != null) ...[
@@ -142,66 +145,63 @@ class _WhisperDetailPageState
return switch (loadingState) {
Loading() => loadingWidget,
Success(:var response) => response?.isNotEmpty == true
? refreshIndicator(
onRefresh: _whisperDetailController.onRefresh,
child: ListView.separated(
shrinkWrap: true,
reverse: true,
itemCount: response!.length,
padding: const EdgeInsets.all(12),
physics: const AlwaysScrollableScrollPhysics(
parent: ClampingScrollPhysics(),
),
controller: _whisperDetailController.scrollController,
itemBuilder: (context, int index) {
if (index == response.length - 1) {
_whisperDetailController.onLoadMore();
}
final item = response[index];
return ChatItem(
item: item,
eInfos: _whisperDetailController.eInfos,
onLongPress: item.senderUid ==
_whisperDetailController.ownerMid
? () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding:
const EdgeInsets.symmetric(vertical: 12),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
onTap: () {
Get.back();
_whisperDetailController.sendMsg(
message: '${item.msgKey}',
onClearText: editController.clear,
msgType: 5,
index: index,
);
},
dense: true,
title: const Text(
'撤回',
style: TextStyle(fontSize: 14),
),
),
],
),
);
},
);
}
: null,
);
},
separatorBuilder: (BuildContext context, int index) =>
const SizedBox(height: 12),
? ListView.separated(
shrinkWrap: true,
reverse: true,
itemCount: response!.length,
padding: const EdgeInsets.all(12),
physics: const AlwaysScrollableScrollPhysics(
parent: ClampingScrollPhysics(),
),
controller: _whisperDetailController.scrollController,
itemBuilder: (context, int index) {
if (index == response.length - 1) {
_whisperDetailController.onLoadMore();
}
final item = response[index];
return ChatItem(
item: item,
eInfos: _whisperDetailController.eInfos,
onLongPress: item.senderUid ==
_whisperDetailController.ownerMid
? () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding:
const EdgeInsets.symmetric(vertical: 12),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
onTap: () {
Get.back();
_whisperDetailController.sendMsg(
message: '${item.msgKey}',
onClearText: editController.clear,
msgType: 5,
index: index,
);
},
dense: true,
title: const Text(
'撤回',
style: TextStyle(fontSize: 14),
),
),
],
),
);
},
);
}
: null,
);
},
separatorBuilder: (BuildContext context, int index) =>
const SizedBox(height: 12),
)
: scrollErrorWidget(
onReload: _whisperDetailController.onReload,