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

View File

@@ -46,7 +46,12 @@ class _BlackListPageState extends State<BlackListPage> {
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
controller: _blackListController.scrollController, controller: _blackListController.scrollController,
slivers: [ 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(), physics: const AlwaysScrollableScrollPhysics(),
controller: controller.scrollController, controller: controller.scrollController,
slivers: [ 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(), physics: const AlwaysScrollableScrollPhysics(),
controller: controller.scrollController, controller: controller.scrollController,
slivers: [ 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) { return switch (loadingState) {
Loading() => DynamicsTabPage.dynSkeleton(dynamicsWaterfallFlow), Loading() => DynamicsTabPage.dynSkeleton(dynamicsWaterfallFlow),
Success(:var response) => response?.isNotEmpty == true Success(:var response) => response?.isNotEmpty == true
? SliverPadding( ? dynamicsWaterfallFlow
padding: EdgeInsets.only( ? SliverWaterfallFlow.extent(
bottom: MediaQuery.paddingOf(context).bottom + 80, maxCrossAxisExtent: Grid.smallCardWidth * 2,
), crossAxisSpacing: StyleString.cardSpace / 2,
sliver: dynamicsWaterfallFlow lastChildLayoutTypeBuilder: (index) {
? SliverWaterfallFlow.extent( if (index == response.length - 1) {
maxCrossAxisExtent: Grid.smallCardWidth * 2, controller.onLoadMore();
crossAxisSpacing: StyleString.cardSpace / 2, }
lastChildLayoutTypeBuilder: (index) { return index == response.length
if (index == response.length - 1) { ? LastChildLayoutType.foot
controller.onLoadMore(); : LastChildLayoutType.none;
} },
return index == response.length children: [
? LastChildLayoutType.foot for (int index = 0; index < response!.length; index++)
: LastChildLayoutType.none; DynamicPanel(
}, item: response[index],
children: [ onRemove: (idStr) => controller.onRemove(index, idStr),
for (int index = 0; index < response!.length; index++) onBlock: () => controller.onBlock(index),
DynamicPanel( )
item: response[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) => onRemove: (idStr) =>
controller.onRemove(index, idStr), controller.onRemove(index, idStr),
onBlock: () => controller.onBlock(index), onBlock: () => controller.onBlock(index),
) );
], },
) itemCount: response!.length,
: 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(),
],
), ),
) const SliverFillRemaining(),
],
)
: HttpError( : HttpError(
onReload: controller.onReload, onReload: controller.onReload,
), ),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,64 +26,59 @@ class _LaterSearchPageState
@override @override
Widget buildList(List<HotVideoItemModel> list) { Widget buildList(List<HotVideoItemModel> list) {
return SliverPadding( return SliverGrid(
padding: EdgeInsets.only( gridDelegate: Grid.videoCardHDelegate(context, minHeight: 110),
bottom: MediaQuery.of(context).padding.bottom + 80, delegate: SliverChildBuilderDelegate(
), childCount: list.length,
sliver: SliverGrid( (context, index) {
gridDelegate: Grid.videoCardHDelegate(context, minHeight: 110), if (index == list.length - 1) {
delegate: SliverChildBuilderDelegate( controller.onLoadMore();
childCount: list.length, }
(context, index) { final item = list[index];
if (index == list.length - 1) { return Stack(
controller.onLoadMore(); clipBehavior: Clip.none,
} children: [
final item = list[index]; VideoCardH(
return Stack( videoItem: item,
clipBehavior: Clip.none, source: 'later',
children: [ onViewLater: (cid) {
VideoCardH( PageUtils.toVideoPage(
videoItem: item, 'bvid=${item.bvid}&cid=$cid',
source: 'later', arguments: {
onViewLater: (cid) { 'videoItem': item,
PageUtils.toVideoPage( 'oid': item.aid,
'bvid=${item.bvid}&cid=$cid', 'heroTag': Utils.makeHeroTag(item.bvid),
arguments: { 'sourceType': 'watchLater',
'videoItem': item, 'count': controller.count,
'oid': item.aid, 'favTitle': '稍后再看',
'heroTag': Utils.makeHeroTag(item.bvid), 'mediaId': controller.mid,
'sourceType': 'watchLater', 'desc': false,
'count': controller.count, 'isContinuePlaying': index != 0,
'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( return refreshIndicator(
onRefresh: _controller.onRefresh, onRefresh: _controller.onRefresh,
child: CustomScrollView( 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( child: Obx(
() => _buildItem(theme, _controller.second.value, false)), () => _buildItem(theme, _controller.second.value, false)),
), ),
SliverToBoxAdapter(
child: SizedBox(
height: 80 + MediaQuery.of(context).padding.bottom,
),
),
], ],
) )
: HttpError( : HttpError(

View File

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

View File

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

View File

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

View File

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

View File

@@ -155,7 +155,7 @@ class _MediaListPanelState
widget.loadMoreMedia(); widget.loadMoreMedia();
} }
return SizedBox( return SizedBox(
key: ValueKey('${item.aid}'), // key: ValueKey('${item.aid}'),
height: 98, height: 98,
child: InkWell( child: InkWell(
onTap: () async { 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/member/info.dart';
import 'package:PiliPlus/models/space_archive/data.dart'; import 'package:PiliPlus/models/space_archive/data.dart';
import 'package:PiliPlus/models/space_archive/item.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'; import 'package:get/get.dart';
class HorizontalMemberPageController extends CommonDataController { class HorizontalMemberPageController
HorizontalMemberPageController({this.mid, required this.lastAid}); extends CommonListController<SpaceArchiveData, SpaceArchiveItem> {
HorizontalMemberPageController({this.mid, required this.currAid});
dynamic mid; dynamic mid;
int currentPage = 0;
Rx<LoadingState<MemberInfoModel>> userState = Rx<LoadingState<MemberInfoModel>> userState =
LoadingState<MemberInfoModel>.loading().obs; LoadingState<MemberInfoModel>.loading().obs;
RxMap userStat = {}.obs; RxMap userStat = {}.obs;
@@ -54,27 +54,29 @@ class HorizontalMemberPageController extends CommonDataController {
bool customHandleResponse(bool isRefresh, Success response) { bool customHandleResponse(bool isRefresh, Success response) {
SpaceArchiveData data = response.response; SpaceArchiveData data = response.response;
count.value = data.count ?? -1; count.value = data.count ?? -1;
if (currentPage == 0 || isLoadPrevious) { if (isRefresh) {
hasPrev = data.hasPrev ?? false;
}
if (currentPage == 0 || !isLoadPrevious) {
hasNext = data.hasNext ?? false;
}
if (currentPage != 0 && loadingState.value is Success) {
data.item ??= <SpaceArchiveItem>[];
if (isLoadPrevious) { if (isLoadPrevious) {
data.item!.addAll((loadingState.value as Success).response); hasPrev = data.hasPrev ?? false;
} else { } 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; firstAid = data.item?.firstOrNull?.param;
lastAid = data.item?.lastOrNull?.param; lastAid = data.item?.lastOrNull?.param;
loadingState.value = Success(data.item); loadingState.value = Success(data.item);
isLoadPrevious = false; isLoadPrevious = false;
page++;
return true; return true;
} }
String? currAid;
String? firstAid; String? firstAid;
String? lastAid; String? lastAid;
RxString order = 'pubdate'.obs; RxString order = 'pubdate'.obs;
@@ -84,27 +86,45 @@ class HorizontalMemberPageController extends CommonDataController {
bool hasNext = true; bool hasNext = true;
@override @override
Future<LoadingState> customGetData() => MemberHttp.spaceArchive( Future<LoadingState<SpaceArchiveData>> customGetData() =>
MemberHttp.spaceArchive(
type: ContributeType.video, type: ContributeType.video,
mid: mid, mid: mid,
aid: isLoadPrevious ? firstAid : lastAid, aid: page == 1
? currAid
: isLoadPrevious
? firstAid
: lastAid,
order: order.value, order: order.value,
sort: isLoadPrevious ? 'asc' : null, sort: page != 1 && isLoadPrevious ? 'asc' : null,
pn: null, pn: null,
next: null, next: null,
seasonId: null, seasonId: null,
seriesId: null, seriesId: null,
includeCursor: currentPage == 0 ? true : null, includeCursor: page == 1 ? true : null,
); );
@override @override
Future<void> onRefresh() { Future<void> onRefresh() {
currentPage = 0; if (!hasPrev) {
hasPrev = true; return Future.value();
hasNext = true; }
isLoadPrevious = true;
return queryData(); 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() { void queryBySort() {
order.value = order.value == 'pubdate' ? 'click' : 'pubdate'; order.value = order.value == 'pubdate' ? 'click' : 'pubdate';
onReload(); 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/button/icon_button.dart';
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.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/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/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/common/widgets/video_card/video_card_h_member_video.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart';
@@ -49,31 +51,12 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
_controller = Get.put( _controller = Get.put(
HorizontalMemberPageController( HorizontalMemberPageController(
mid: widget.mid, mid: widget.mid,
lastAid: widget.videoDetailController.oid.value.toString(), currAid: widget.videoDetailController.oid.value.toString(),
), ),
tag: widget.videoDetailController.heroTag, tag: widget.videoDetailController.heroTag,
); );
_bvid = widget.videoDetailController.bvid; _bvid = widget.videoDetailController.bvid;
_ownerMid = Accounts.main.mid; _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 @override
@@ -104,10 +87,22 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
], ],
), ),
_buildUserInfo(theme, response), _buildUserInfo(theme, response),
const SizedBox(height: 5),
Expanded( Expanded(
child: Obx( child: refreshIndicator(
() => _buildVideoList(theme, _controller.loadingState.value)), 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) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => SliverGrid(
Success(:var response) => Material( gridDelegate: Grid.videoCardHDelegate(context),
color: Colors.transparent, delegate: SliverChildBuilderDelegate(
child: CustomScrollView( childCount: 10,
controller: _controller.scrollController, (context, index) {
physics: PositionRetainedScrollPhysics( return const VideoCardHSkeleton();
shouldRetain: _controller.hasPrev, },
parent: const ClampingScrollPhysics(), ),
), ),
slivers: [ Success(:var response) => response?.isNotEmpty == true
_buildSliverHeader(theme), ? SliverPadding(
SliverPadding( padding: EdgeInsets.only(
padding: EdgeInsets.only( bottom: MediaQuery.of(context).padding.bottom + 80,
bottom: MediaQuery.of(context).padding.bottom + 80, ),
), sliver: SliverGrid(
sliver: SliverGrid( gridDelegate: Grid.videoCardHDelegate(context),
gridDelegate: Grid.videoCardHDelegate(context), delegate: SliverChildBuilderDelegate(
delegate: SliverChildBuilderDelegate( (context, index) {
(context, index) { if (index == response.length - 1 && _controller.hasNext) {
if (index == response.length - 1 && _controller.hasNext) { _controller.onLoadMore();
_controller.onLoadMore(); }
} final SpaceArchiveItem videoItem = response[index];
final SpaceArchiveItem videoItem = response[index]; return Material(
return VideoCardHMemberVideo( color: Colors.transparent,
key: ValueKey('${videoItem.param}'), child: VideoCardHMemberVideo(
videoItem: videoItem, videoItem: videoItem,
bvid: _bvid, bvid: _bvid,
onTap: () { onTap: () {
@@ -219,17 +215,15 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
setState(() {}); setState(() {});
} }
}, },
); ),
}, );
childCount: response.length, },
), childCount: response!.length,
), ),
), ),
], )
), : HttpError(onReload: _controller.onReload),
), Error(:var errMsg) => HttpError(
Error(:var errMsg) => scrollErrorWidget(
controller: _controller.scrollController,
errMsg: errMsg, errMsg: errMsg,
onReload: _controller.onReload, 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/pages/webview/view.dart';
import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/extension.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/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -84,52 +84,73 @@ class _NoteListPageState extends CommonSlidePageState<NoteListPage> {
const SizedBox(width: 16), const SizedBox(width: 16),
], ],
), ),
body: enableSlide body: enableSlide ? slideList(theme) : buildList(theme),
? slideList(theme, ),
Obx(() => _buildBody(theme, _controller.loadingState.value))) );
: Obx(() => _buildBody(theme, _controller.loadingState.value)), }
bottomNavigationBar: Container(
padding: EdgeInsets.only( @override
left: 12, Widget buildList(ThemeData theme) {
right: 12, return refreshIndicator(
top: 6, onRefresh: _controller.onRefresh,
bottom: MediaQuery.paddingOf(context).bottom + 6, child: Column(
), children: [
width: double.infinity, Expanded(
decoration: BoxDecoration( child: CustomScrollView(
color: theme.colorScheme.onInverseSurface, controller: _controller.scrollController,
border: Border( physics: const AlwaysScrollableScrollPhysics(),
top: BorderSide( slivers: [
width: 0.5, SliverPadding(
color: theme.colorScheme.outline.withValues(alpha: 0.1), padding: const EdgeInsets.only(bottom: 80),
), sliver: Obx(
), () => _buildBody(theme, _controller.loadingState.value)),
),
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('开始记笔记'),
), ),
), 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( Widget _buildBody(
ThemeData theme, LoadingState<List<dynamic>?> loadingState) { ThemeData theme, LoadingState<List<dynamic>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => CustomScrollView( Loading() => SliverToBoxAdapter(
physics: const NeverScrollableScrollPhysics(), child: ListView.builder(
slivers: [ shrinkWrap: true,
SliverList.builder( physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
return const VideoReplySkeleton(); return const VideoReplySkeleton();
}, },
itemCount: 8, itemCount: 8,
) ),
],
), ),
Success(:var response) => response?.isNotEmpty == true Success(:var response) => response?.isNotEmpty == true
? refreshIndicator( ? SliverList.separated(
onRefresh: _controller.onRefresh, itemBuilder: (context, index) {
child: CustomScrollView( if (index == response.length - 1) {
controller: _controller.scrollController, _controller.onLoadMore();
physics: const AlwaysScrollableScrollPhysics(), }
slivers: [ return _itemWidget(context, theme, response[index]);
SliverList.separated( },
itemBuilder: (context, index) { itemCount: response!.length,
if (index == response.length - 1) { separatorBuilder: (context, index) => Divider(
_controller.onLoadMore(); height: 1,
} color: theme.colorScheme.outline.withValues(alpha: 0.1),
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,
),
),
],
), ),
) )
: errWidget(), : HttpError(onReload: _controller.onReload),
Error(:var errMsg) => errWidget(errMsg), 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) { Widget _itemWidget(BuildContext context, ThemeData theme, dynamic item) {

View File

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

View File

@@ -123,8 +123,11 @@ class _WhisperDetailPageState
hidePanel(); hidePanel();
}, },
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: Obx(() => child: refreshIndicator(
_buildBody(_whisperDetailController.loadingState.value)), onRefresh: _whisperDetailController.onRefresh,
child: Obx(() =>
_buildBody(_whisperDetailController.loadingState.value)),
),
), ),
), ),
if (_whisperDetailController.mid != null) ...[ if (_whisperDetailController.mid != null) ...[
@@ -142,66 +145,63 @@ class _WhisperDetailPageState
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => loadingWidget,
Success(:var response) => response?.isNotEmpty == true Success(:var response) => response?.isNotEmpty == true
? refreshIndicator( ? ListView.separated(
onRefresh: _whisperDetailController.onRefresh, shrinkWrap: true,
child: ListView.separated( reverse: true,
shrinkWrap: true, itemCount: response!.length,
reverse: true, padding: const EdgeInsets.all(12),
itemCount: response!.length, physics: const AlwaysScrollableScrollPhysics(
padding: const EdgeInsets.all(12), parent: ClampingScrollPhysics(),
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),
), ),
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( : scrollErrorWidget(
onReload: _whisperDetailController.onReload, onReload: _whisperDetailController.onReload,