mod: 历史记录搜索
This commit is contained in:
@ -303,4 +303,7 @@ class Api {
|
|||||||
static const String onlineTotal = '/x/player/online/total';
|
static const String onlineTotal = '/x/player/online/total';
|
||||||
|
|
||||||
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
||||||
|
|
||||||
|
// 搜索历史记录
|
||||||
|
static const String searchHistory = '/x/web-goblin/history/search';
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
import 'package:pilipala/models/bangumi/info.dart';
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
@ -11,18 +13,26 @@ class SearchHttp {
|
|||||||
static Box setting = GStrorage.setting;
|
static Box setting = GStrorage.setting;
|
||||||
static Future hotSearchList() async {
|
static Future hotSearchList() async {
|
||||||
var res = await Request().get(Api.hotSearchList);
|
var res = await Request().get(Api.hotSearchList);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data is String) {
|
||||||
|
Map<String, dynamic> resultMap = json.decode(res.data);
|
||||||
|
if (resultMap['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': HotSearchModel.fromJson(resultMap),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (res.data is Map<String, dynamic> && res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': HotSearchModel.fromJson(res.data),
|
'data': HotSearchModel.fromJson(res.data),
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': '请求错误 🙅',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': '请求错误 🙅',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取搜索建议
|
// 获取搜索建议
|
||||||
|
@ -248,4 +248,22 @@ class UserHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
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']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,23 @@ class HistoryData {
|
|||||||
this.cursor,
|
this.cursor,
|
||||||
this.tab,
|
this.tab,
|
||||||
this.list,
|
this.list,
|
||||||
|
this.page,
|
||||||
});
|
});
|
||||||
|
|
||||||
Cursor? cursor;
|
Cursor? cursor;
|
||||||
List<HisTabItem>? tab;
|
List<HisTabItem>? tab;
|
||||||
List<HisListItem>? list;
|
List<HisListItem>? list;
|
||||||
|
Map? page;
|
||||||
|
|
||||||
HistoryData.fromJson(Map<String, dynamic> json) {
|
HistoryData.fromJson(Map<String, dynamic> json) {
|
||||||
cursor = Cursor.fromJson(json['cursor']);
|
cursor = json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null;
|
||||||
tab = json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList();
|
tab = json['tab'] != null
|
||||||
list =
|
? json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList()
|
||||||
json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList();
|
: [];
|
||||||
|
list = json['list'] != null
|
||||||
|
? json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList()
|
||||||
|
: [];
|
||||||
|
page = json['page'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +86,10 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
// },
|
// },
|
||||||
// child: const Text('多选'),
|
// child: const Text('多选'),
|
||||||
// ),
|
// ),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed('/historySearch'),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
),
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
onSelected: (String type) {
|
onSelected: (String type) {
|
||||||
// 处理菜单项选择的逻辑
|
// 处理菜单项选择的逻辑
|
||||||
|
@ -12,13 +12,14 @@ import 'package:pilipala/models/common/business_type.dart';
|
|||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
import 'package:pilipala/pages/history/index.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/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class HistoryItem extends StatelessWidget {
|
class HistoryItem extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
final HistoryController? ctr;
|
final dynamic ctr;
|
||||||
final Function? onChoose;
|
final Function? onChoose;
|
||||||
final Function? onUpdateMultiple;
|
final Function? onUpdateMultiple;
|
||||||
const HistoryItem({
|
const HistoryItem({
|
||||||
@ -132,6 +133,9 @@ class HistoryItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
|
if (ctr is HistorySearchController) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!ctr!.enableMultiple.value) {
|
if (!ctr!.enableMultiple.value) {
|
||||||
feedBack();
|
feedBack();
|
||||||
ctr!.enableMultiple.value = true;
|
ctr!.enableMultiple.value = true;
|
||||||
@ -272,7 +276,7 @@ class HistoryItem extends StatelessWidget {
|
|||||||
|
|
||||||
class VideoContent extends StatelessWidget {
|
class VideoContent extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
final HistoryController? ctr;
|
final dynamic? ctr;
|
||||||
const VideoContent({super.key, required this.videoItem, this.ctr});
|
const VideoContent({super.key, required this.videoItem, this.ctr});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
91
lib/pages/history_search/controller.dart
Normal file
91
lib/pages/history_search/controller.dart
Normal file
@ -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<TextEditingController> 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<HisListItem> historyList = <HisListItem>[].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';
|
||||||
|
}
|
||||||
|
}
|
4
lib/pages/history_search/index.dart
Normal file
4
lib/pages/history_search/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library history_search;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
174
lib/pages/history_search/view.dart
Normal file
174
lib/pages/history_search/view.dart
Normal file
@ -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<HistorySearchPage> createState() => _HistorySearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistorySearchPageState extends State<HistorySearchPage> {
|
||||||
|
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: <Widget>[
|
||||||
|
NoData(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,8 @@ import 'package:pilipala/pages/setting/index.dart';
|
|||||||
import 'package:pilipala/pages/media/index.dart';
|
import 'package:pilipala/pages/media/index.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
|
import '../pages/history_search/index.dart';
|
||||||
|
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
bool iosTransition =
|
bool iosTransition =
|
||||||
setting.get(SettingBoxKey.iosTransition, defaultValue: false);
|
setting.get(SettingBoxKey.iosTransition, defaultValue: false);
|
||||||
@ -112,6 +114,9 @@ class Routes {
|
|||||||
CustomGetPage(name: '/about', page: () => const AboutPage()),
|
CustomGetPage(name: '/about', page: () => const AboutPage()),
|
||||||
//
|
//
|
||||||
CustomGetPage(name: '/htmlRender', page: () => const HtmlRenderPage()),
|
CustomGetPage(name: '/htmlRender', page: () => const HtmlRenderPage()),
|
||||||
|
// 历史记录搜索
|
||||||
|
CustomGetPage(
|
||||||
|
name: '/historySearch', page: () => const HistorySearchPage()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user