diff --git a/lib/http/api.dart b/lib/http/api.dart index fb5476ee..50106dae 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -129,12 +129,14 @@ class Api { static const String userFavFolder = '/x/v3/fav/folder/created/list'; /// 收藏夹 详情 - /// media_id int 收藏夹id + /// media_id 当前收藏夹id 搜索全部时为默认收藏夹id /// pn int 当前页 /// ps int pageSize /// keyword String 搜索词 /// order String 排序方式 view 最多播放 mtime 最近收藏 pubtime 最近投稿 /// tid int 分区id + /// platform web + /// type 0 当前收藏夹 1 全部收藏夹 // https://api.bilibili.com/x/v3/fav/resource/list?media_id=76614671&pn=1&ps=20&keyword=&order=mtime&type=0&tid=0 static const String userFavFolderDetail = '/x/v3/fav/resource/list'; diff --git a/lib/http/user.dart b/lib/http/user.dart index e0175b2f..1ab465e0 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -71,14 +71,15 @@ class UserHttp { required int pn, required int ps, String keyword = '', - String order = 'mtime'}) async { + String order = 'mtime', + int type = 0}) async { var res = await Request().get(Api.userFavFolderDetail, data: { 'media_id': mediaId, 'pn': pn, 'ps': ps, 'keyword': keyword, 'order': order, - 'type': 0, + 'type': type, 'tid': 0, 'platform': 'web' }); diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 1196efc9..b980914a 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -44,6 +44,14 @@ class _FavPageState extends State { '我的收藏', style: Theme.of(context).textTheme.titleMedium, ), + actions: [ + IconButton( + onPressed: () => Get.toNamed( + '/favSearch?searchType=1&mediaId=${_favController.favFolderData.value.list!.first.id}'), + icon: const Icon(Icons.search_outlined), + ), + const SizedBox(width: 6), + ], ), body: FutureBuilder( future: _futureBuilderFuture, diff --git a/lib/pages/favDetail/view.dart b/lib/pages/favDetail/view.dart index cda6c2b7..fedc85fd 100644 --- a/lib/pages/favDetail/view.dart +++ b/lib/pages/favDetail/view.dart @@ -92,13 +92,18 @@ class _FavDetailPageState extends State { ); }, ), - // actions: [ - // IconButton( - // onPressed: () {}, - // icon: const Icon(Icons.more_vert), - // ), - // const SizedBox(width: 4) - // ], + actions: [ + IconButton( + onPressed: () => Get.toNamed( + '/favSearch?searchType=0&mediaId=${Get.parameters['mediaId']!}'), + icon: const Icon(Icons.search_outlined), + ), + // IconButton( + // onPressed: () {}, + // icon: const Icon(Icons.more_vert), + // ), + const SizedBox(width: 6), + ], flexibleSpace: FlexibleSpaceBar( background: Container( decoration: BoxDecoration( diff --git a/lib/pages/fav_search/controller.dart b/lib/pages/fav_search/controller.dart new file mode 100644 index 00000000..642fea6b --- /dev/null +++ b/lib/pages/fav_search/controller.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/http/user.dart'; +import 'package:pilipala/models/user/fav_detail.dart'; + +class FavSearchController extends GetxController { + final ScrollController scrollController = ScrollController(); + Rx controller = TextEditingController().obs; + final FocusNode searchFocusNode = FocusNode(); + RxString searchKeyWord = ''.obs; // 搜索词 + String hintText = '请输入已收藏视频名称'; // 默认 + RxBool loadingStatus = false.obs; // 加载状态 + RxString loadingText = '加载中...'.obs; // 加载提示 + bool hasMore = false; + late int searchType; + late int mediaId; + + int currentPage = 1; // 当前页 + int count = 0; // 总数 + RxList favList = [].obs; + + @override + void onInit() { + super.onInit(); + searchType = int.parse(Get.parameters['searchType']!); + mediaId = int.parse(Get.parameters['mediaId']!); + } + + // 清空搜索 + void onClear() { + if (searchKeyWord.value.isNotEmpty && controller.value.text != '') { + controller.value.clear(); + searchKeyWord.value = ''; + } else { + Get.back(); + } + } + + void onChange(value) { + searchKeyWord.value = value; + } + + // 提交搜索内容 + void submit() { + loadingStatus.value = true; + currentPage = 1; + searchFav(); + } + + // 搜索收藏夹视频 + Future searchFav({type = 'init'}) async { + var res = await await UserHttp.userFavFolderDetail( + pn: currentPage, + ps: 20, + mediaId: mediaId, + keyword: searchKeyWord.value, + type: searchType, + ); + if (res['status']) { + if (currentPage == 1 && type == 'init') { + favList.value = res['data'].medias; + } else if (type == 'onLoad') { + favList.addAll(res['data'].medias); + } + hasMore = res['data'].hasMore; + } + currentPage += 1; + loadingStatus.value = false; + } + + onLoad() { + if (!hasMore) return; + searchFav(type: 'onLoad'); + } +} diff --git a/lib/pages/fav_search/index.dart b/lib/pages/fav_search/index.dart new file mode 100644 index 00000000..b811585f --- /dev/null +++ b/lib/pages/fav_search/index.dart @@ -0,0 +1,4 @@ +library fav_search; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/fav_search/view.dart b/lib/pages/fav_search/view.dart new file mode 100644 index 00000000..83c2440b --- /dev/null +++ b/lib/pages/fav_search/view.dart @@ -0,0 +1,116 @@ +import 'package:easy_debounce/easy_throttle.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/skeleton/video_card_h.dart'; +import 'package:pilipala/common/widgets/no_data.dart'; +import 'package:pilipala/pages/favDetail/widget/fav_video_card.dart'; + +import 'controller.dart'; + +class FavSearchPage extends StatefulWidget { + final int? sourceType; + final int? mediaId; + const FavSearchPage({super.key, this.sourceType, this.mediaId}); + + @override + State createState() => _FavSearchPageState(); +} + +class _FavSearchPageState extends State { + final FavSearchController _favSearchCtr = Get.put(FavSearchController()); + late ScrollController scrollController; + + @override + void initState() { + super.initState(); + + scrollController = _favSearchCtr.scrollController; + scrollController.addListener( + () { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 300) { + EasyThrottle.throttle('fav', const Duration(seconds: 1), () { + _favSearchCtr.onLoad(); + }); + } + }, + ); + } + + @override + void dispose() { + scrollController.removeListener(() {}); + scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + titleSpacing: 0, + actions: [ + IconButton( + onPressed: () => _favSearchCtr.submit(), + icon: const Icon(Icons.search_outlined, size: 22)), + const SizedBox(width: 10) + ], + title: Obx( + () => TextField( + autofocus: true, + focusNode: _favSearchCtr.searchFocusNode, + controller: _favSearchCtr.controller.value, + textInputAction: TextInputAction.search, + onChanged: (value) => _favSearchCtr.onChange(value), + decoration: InputDecoration( + hintText: _favSearchCtr.hintText, + border: InputBorder.none, + suffixIcon: IconButton( + icon: Icon( + Icons.clear, + size: 22, + color: Theme.of(context).colorScheme.outline, + ), + onPressed: () => _favSearchCtr.onClear(), + ), + ), + onSubmitted: (String value) => _favSearchCtr.submit(), + ), + ), + ), + body: Obx( + () => _favSearchCtr.loadingStatus.value && _favSearchCtr.favList.isEmpty + ? ListView.builder( + itemCount: 10, + itemBuilder: (context, index) { + return const VideoCardHSkeleton(); + }, + ) + : _favSearchCtr.favList.isNotEmpty + ? ListView.builder( + controller: scrollController, + itemCount: _favSearchCtr.favList.length + 1, + itemBuilder: (context, index) { + if (index == _favSearchCtr.favList.length) { + return Container( + height: MediaQuery.of(context).padding.bottom + 60, + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom), + ); + } else { + return FavVideoCardH( + videoItem: _favSearchCtr.favList[index], + callFn: () => null, + ); + } + }, + ) + : const CustomScrollView( + slivers: [ + NoData(), + ], + ), + ), + ); + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 7b701b92..544217f9 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -10,6 +10,7 @@ import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/fan/index.dart'; import 'package:pilipala/pages/fav/index.dart'; import 'package:pilipala/pages/favDetail/index.dart'; +import 'package:pilipala/pages/fav_search/index.dart'; import 'package:pilipala/pages/follow/index.dart'; import 'package:pilipala/pages/history/index.dart'; import 'package:pilipala/pages/history_search/index.dart'; @@ -119,6 +120,8 @@ class Routes { name: '/historySearch', page: () => const HistorySearchPage()), CustomGetPage(name: '/playSpeedSet', page: () => const PlaySpeedPage()), + // 收藏搜索 + CustomGetPage(name: '/favSearch', page: () => const FavSearchPage()), ]; }