opt: search page

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-17 10:32:42 +08:00
parent 5959288491
commit a99fc8fa72
5 changed files with 184 additions and 197 deletions

View File

@@ -1,4 +1,4 @@
import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_search_controller.dart'; import 'package:PiliPlus/pages/common/common_search_controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -48,19 +48,25 @@ abstract class CommonSearchPageState<S extends CommonSearchPage, R, T>
onSubmitted: (value) => controller.onRefresh(), onSubmitted: (value) => controller.onRefresh(),
), ),
), ),
body: Obx(() => _buildBody(controller.loadingState.value)), body: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: controller.scrollController,
slivers: [
Obx(() => _buildBody(controller.loadingState.value)),
],
),
); );
} }
Widget _buildBody(LoadingState<List<T>?> loadingState) { Widget _buildBody(LoadingState<List<T>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => errorWidget(), Loading() => HttpError(),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? buildList(loadingState.response!) ? buildList(loadingState.response!)
: errorWidget( : HttpError(
callback: controller.onReload, callback: controller.onReload,
), ),
Error() => errorWidget( Error() => HttpError(
errMsg: loadingState.errMsg, errMsg: loadingState.errMsg,
callback: controller.onReload, callback: controller.onReload,
), ),

View File

@@ -26,56 +26,50 @@ class _FavSearchPageState extends CommonSearchPageState<FavSearchPage,
@override @override
Widget buildList(List<FavDetailItemData> list) { Widget buildList(List<FavDetailItemData> list) {
return CustomScrollView( return SliverPadding(
physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only(
controller: controller.scrollController, bottom: MediaQuery.of(context).padding.bottom + 80,
slivers: [ ),
SliverPadding( sliver: 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

@@ -24,18 +24,19 @@ class _FollowSearchPageState extends CommonSearchPageState<FollowSearchPage,
@override @override
Widget buildList(List<FollowItemModel> list) { Widget buildList(List<FollowItemModel> list) {
return ListView.builder( return SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80, bottom: MediaQuery.of(context).padding.bottom + 80,
), ),
controller: controller.scrollController, sliver: SliverList.builder(
itemCount: list.length, itemCount: list.length,
itemBuilder: ((context, index) { itemBuilder: ((context, index) {
if (index == list.length - 1) { if (index == list.length - 1) {
controller.onLoadMore(); controller.onLoadMore();
} }
return FollowItem(item: list[index]); return FollowItem(item: list[index]);
}), }),
),
); );
} }
} }

View File

@@ -25,36 +25,30 @@ class _HistorySearchPageState
@override @override
Widget buildList(List<HisListItem> list) { Widget buildList(List<HisListItem> list) {
return CustomScrollView( return SliverPadding(
physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only(
controller: controller.scrollController, bottom: MediaQuery.of(context).padding.bottom + 80,
slivers: [ ),
SliverPadding( sliver: 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

@@ -28,125 +28,117 @@ class _LaterSearchPageState
@override @override
Widget buildList(List<HotVideoItemModel> list) { Widget buildList(List<HotVideoItemModel> list) {
return CustomScrollView( return SliverPadding(
physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only(
controller: controller.scrollController, bottom: MediaQuery.of(context).padding.bottom + 80,
slivers: [ ),
SliverPadding( sliver: 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(); children: [
} VideoCardH(
final item = list[index]; videoItem: item,
return Stack( 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( },
top: 5, ),
left: 12, Positioned(
bottom: 5, top: 5,
child: IgnorePointer( left: 12,
child: LayoutBuilder( bottom: 5,
builder: (context, constraints) => AnimatedOpacity( child: IgnorePointer(
opacity: item.checked == true ? 1 : 0, child: LayoutBuilder(
duration: const Duration(milliseconds: 200), builder: (context, constraints) => AnimatedOpacity(
child: Container( opacity: item.checked == true ? 1 : 0,
alignment: Alignment.center, duration: const Duration(milliseconds: 200),
height: constraints.maxHeight, child: Container(
width: constraints.maxHeight * alignment: Alignment.center,
StyleString.aspectRatio, height: constraints.maxHeight,
decoration: BoxDecoration( width:
borderRadius: BorderRadius.circular(10), constraints.maxHeight * StyleString.aspectRatio,
color: Colors.black.withOpacity(0.6), decoration: BoxDecoration(
), borderRadius: BorderRadius.circular(10),
child: SizedBox( color: Colors.black.withOpacity(0.6),
width: 34, ),
height: 34, child: SizedBox(
child: AnimatedScale( width: 34,
scale: item.checked == true ? 1 : 0, height: 34,
duration: const Duration(milliseconds: 250), child: AnimatedScale(
curve: Curves.easeInOut, scale: item.checked == true ? 1 : 0,
child: IconButton( duration: const Duration(milliseconds: 250),
tooltip: '取消选择', curve: Curves.easeInOut,
style: ButtonStyle( child: IconButton(
padding: WidgetStateProperty.all( tooltip: '取消选择',
EdgeInsets.zero), style: ButtonStyle(
backgroundColor: padding:
WidgetStateProperty.resolveWith( WidgetStateProperty.all(EdgeInsets.zero),
(states) { backgroundColor:
return Theme.of(context) WidgetStateProperty.resolveWith(
.colorScheme (states) {
.surface return Theme.of(context)
.withOpacity(0.8); .colorScheme
}, .surface
), .withOpacity(0.8);
), },
onPressed: null,
icon: Icon(
Icons.done_all_outlined,
color:
Theme.of(context).colorScheme.primary,
),
), ),
), ),
onPressed: null,
icon: Icon(
Icons.done_all_outlined,
color: Theme.of(context).colorScheme.primary,
),
), ),
), ),
), ),
), ),
), ),
), ),
Positioned( ),
right: 12, ),
bottom: 0, Positioned(
child: iconButton( right: 12,
tooltip: '移除', bottom: 0,
context: context, child: iconButton(
onPressed: () { tooltip: '移除',
controller.toViewDel( context: context,
context, onPressed: () {
index, controller.toViewDel(
item.aid, context,
); index,
}, item.aid,
icon: Icons.clear, );
iconColor: },
Theme.of(context).colorScheme.onSurfaceVariant, icon: Icons.clear,
bgColor: Colors.transparent, iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
), bgColor: Colors.transparent,
), ),
], ),
); ],
}, );
), },
),
), ),
], ),
); );
} }
} }