diff --git a/lib/http/api.dart b/lib/http/api.dart index fdf94efe..fb5476ee 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -170,6 +170,9 @@ class Api { // 删除某条历史记录 static const String delHistory = '/x/v2/history/delete'; + // 搜索历史记录 + static const String searchHistory = '/x/web-goblin/history/search'; + // 热搜 static const String hotSearchList = 'https://s.search.bilibili.com/main/hotword'; diff --git a/lib/http/user.dart b/lib/http/user.dart index 705fbec3..e0175b2f 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -274,4 +274,22 @@ class UserHttp { return {'status': false, 'msg': res.data['message']}; } } + + // 搜索历史记录 + static Future searchHistory( + {required int pn, required String keyword}) async { + var res = await Request().get( + Api.searchHistory, + data: { + 'pn': pn, + 'keyword': keyword, + 'business': 'all', + }, + ); + if (res.data['code'] == 0) { + return {'status': true, 'data': HistoryData.fromJson(res.data['data'])}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } } diff --git a/lib/models/user/history.dart b/lib/models/user/history.dart index 0bcf434d..5c7c9278 100644 --- a/lib/models/user/history.dart +++ b/lib/models/user/history.dart @@ -3,17 +3,23 @@ class HistoryData { this.cursor, this.tab, this.list, + this.page, }); Cursor? cursor; List? tab; List? list; + Map? page; HistoryData.fromJson(Map json) { - cursor = Cursor.fromJson(json['cursor']); - tab = json['tab'].map((e) => HisTabItem.fromJson(e)).toList(); - list = - json['list'].map((e) => HisListItem.fromJson(e)).toList(); + cursor = json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null; + tab = json['tab'] != null + ? json['tab'].map((e) => HisTabItem.fromJson(e)).toList() + : []; + list = json['list'] != null + ? json['list'].map((e) => HisListItem.fromJson(e)).toList() + : []; + page = json['page']; } } diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index 8fb4300e..cc6be6ca 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -76,13 +76,10 @@ class _HistoryPageState extends State { style: Theme.of(context).textTheme.titleMedium, ), actions: [ - // TextButton( - // onPressed: () { - // _historyController.enableMultiple.value = true; - // setState(() {}); - // }, - // child: const Text('多选'), - // ), + IconButton( + onPressed: () => Get.toNamed('/historySearch'), + icon: const Icon(Icons.search_outlined), + ), PopupMenuButton( onSelected: (String type) { // 处理菜单项选择的逻辑 diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index 11bd4d23..6fa87207 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -12,13 +12,14 @@ import 'package:pilipala/models/common/business_type.dart'; import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/pages/history/index.dart'; +import 'package:pilipala/pages/history_search/index.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/utils.dart'; class HistoryItem extends StatelessWidget { final dynamic videoItem; - final HistoryController? ctr; + final dynamic ctr; final Function? onChoose; final Function? onUpdateMultiple; const HistoryItem({ @@ -132,6 +133,9 @@ class HistoryItem extends StatelessWidget { } }, onLongPress: () { + if (ctr is HistorySearchController) { + return; + } if (!ctr!.enableMultiple.value) { feedBack(); ctr!.enableMultiple.value = true; @@ -268,7 +272,7 @@ class HistoryItem extends StatelessWidget { class VideoContent extends StatelessWidget { final dynamic videoItem; - final HistoryController? ctr; + final dynamic ctr; const VideoContent({super.key, required this.videoItem, this.ctr}); @override diff --git a/lib/pages/history_search/controller.dart b/lib/pages/history_search/controller.dart new file mode 100644 index 00000000..90ac7a02 --- /dev/null +++ b/lib/pages/history_search/controller.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/http/user.dart'; +import 'package:pilipala/models/user/history.dart'; + +class HistorySearchController extends GetxController { + final ScrollController scrollController = ScrollController(); + Rx controller = TextEditingController().obs; + final FocusNode searchFocusNode = FocusNode(); + RxString searchKeyWord = ''.obs; + String hintText = '搜索'; + RxString loadingStatus = 'init'.obs; + RxString loadingText = '加载中...'.obs; + bool hasRequest = false; + late int mid; + RxString uname = ''.obs; + int pn = 1; + int count = 0; + RxList historyList = [].obs; + RxBool enableMultiple = false.obs; + + // 清空搜索 + 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 = 'loading'; + if (hasRequest) { + pn = 1; + searchHistories(); + } + } + + // 搜索视频 + Future searchHistories({type = 'init'}) async { + if (type == 'onLoad' && loadingText.value == '没有更多了') { + return; + } + var res = await UserHttp.searchHistory( + pn: pn, + keyword: controller.value.text, + ); + if (res['status']) { + if (type == 'init' && pn == 1) { + historyList.value = res['data'].list; + } else { + historyList.addAll(res['data'].list); + } + count = res['data'].page['total']; + if (historyList.length == count) { + loadingText.value = '没有更多了'; + } + pn += 1; + hasRequest = true; + } + loadingStatus.value = 'finish'; + return res; + } + + onLoad() { + searchHistories(type: 'onLoad'); + } + + Future delHistory(kid, business) async { + String resKid = 'archive_$kid'; + if (business == 'live') { + resKid = 'live_$kid'; + } else if (business.contains('article')) { + resKid = 'article_$kid'; + } + + var res = await UserHttp.delHistory(resKid); + if (res['status']) { + historyList.removeWhere((e) => e.kid == kid); + SmartDialog.showToast(res['msg']); + } + loadingStatus.value = 'finish'; + } +} diff --git a/lib/pages/history_search/index.dart b/lib/pages/history_search/index.dart new file mode 100644 index 00000000..a9db082b --- /dev/null +++ b/lib/pages/history_search/index.dart @@ -0,0 +1,4 @@ +library history_search; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/history_search/view.dart b/lib/pages/history_search/view.dart new file mode 100644 index 00000000..809e6d67 --- /dev/null +++ b/lib/pages/history_search/view.dart @@ -0,0 +1,174 @@ +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/http_error.dart'; +import 'package:pilipala/common/widgets/no_data.dart'; +import 'package:pilipala/pages/history/widgets/item.dart'; + +import 'controller.dart'; + +class HistorySearchPage extends StatefulWidget { + const HistorySearchPage({super.key}); + + @override + State createState() => _HistorySearchPageState(); +} + +class _HistorySearchPageState extends State { + final HistorySearchController _historySearchCtr = + Get.put(HistorySearchController()); + late ScrollController scrollController; + + @override + void initState() { + super.initState(); + scrollController = _historySearchCtr.scrollController; + scrollController.addListener( + () { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 300) { + EasyThrottle.throttle('history', const Duration(seconds: 1), () { + _historySearchCtr.onLoad(); + }); + } + }, + ); + } + + @override + void dispose() { + scrollController.removeListener(() {}); + scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + titleSpacing: 0, + actions: [ + IconButton( + onPressed: () => _historySearchCtr.submit(), + icon: const Icon(Icons.search_outlined, size: 22)), + const SizedBox(width: 10) + ], + title: Obx( + () => TextField( + autofocus: true, + focusNode: _historySearchCtr.searchFocusNode, + controller: _historySearchCtr.controller.value, + textInputAction: TextInputAction.search, + onChanged: (value) => _historySearchCtr.onChange(value), + decoration: InputDecoration( + hintText: _historySearchCtr.hintText, + border: InputBorder.none, + suffixIcon: IconButton( + icon: Icon( + Icons.clear, + size: 22, + color: Theme.of(context).colorScheme.outline, + ), + onPressed: () => _historySearchCtr.onClear(), + ), + ), + onSubmitted: (String value) => _historySearchCtr.submit(), + ), + ), + ), + body: Obx( + () => Column( + children: _historySearchCtr.loadingStatus.value == 'init' + ? [const SizedBox()] + : [ + Expanded( + child: FutureBuilder( + future: _historySearchCtr.searchHistories(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + return Obx( + () => _historySearchCtr.historyList.isNotEmpty + ? ListView.builder( + controller: scrollController, + itemCount: + _historySearchCtr.historyList.length + + 1, + itemBuilder: (context, index) { + if (index == + _historySearchCtr + .historyList.length) { + return Container( + height: MediaQuery.of(context) + .padding + .bottom + + 60, + padding: EdgeInsets.only( + bottom: MediaQuery.of(context) + .padding + .bottom), + child: Center( + child: Obx( + () => Text( + _historySearchCtr + .loadingText.value, + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline, + fontSize: 13), + ), + ), + ), + ); + } else { + return HistoryItem( + videoItem: _historySearchCtr + .historyList[index], + ctr: _historySearchCtr, + onChoose: null, + onUpdateMultiple: () => null, + ); + ; + } + }, + ) + : _historySearchCtr.loadingStatus.value == + 'loading' + ? const SizedBox(child: Text('加载中...')) + : const CustomScrollView( + slivers: [ + NoData(), + ], + ), + ); + } else { + return CustomScrollView( + slivers: [ + HttpError( + errMsg: data['msg'], + fn: () => setState(() {}), + ) + ], + ); + } + } else { + // 骨架屏 + return ListView.builder( + itemCount: 10, + itemBuilder: (context, index) { + return const VideoCardHSkeleton(); + }, + ); + } + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 4b90fcd1..536eba89 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -12,6 +12,7 @@ import 'package:pilipala/pages/fav/index.dart'; import 'package:pilipala/pages/favDetail/index.dart'; import 'package:pilipala/pages/follow/index.dart'; import 'package:pilipala/pages/history/index.dart'; +import 'package:pilipala/pages/history_search/index.dart'; import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/hot/index.dart'; import 'package:pilipala/pages/html/index.dart'; @@ -112,6 +113,9 @@ class Routes { CustomGetPage(name: '/about', page: () => const AboutPage()), // CustomGetPage(name: '/htmlRender', page: () => const HtmlRenderPage()), + // 历史记录搜索 + CustomGetPage( + name: '/historySearch', page: () => const HistorySearchPage()), ]; }