mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
opt: unify trending api & feat: search recommend (#694)
* opt: unify trending api * opt: disable icon * feat: search recommend * mod: recommend api
This commit is contained in:
committed by
GitHub
parent
3638d65008
commit
f0e3b776bb
@@ -1,5 +1,6 @@
|
||||
import 'package:PiliPlus/common/widgets/dialog.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/search/search_trending/trending_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/http/search.dart';
|
||||
@@ -8,9 +9,6 @@ import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
|
||||
|
||||
class SSearchController extends GetxController {
|
||||
late final historyOff =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><path d="m785-289-58-58q16-29 24.5-63t8.5-70q0-117-81.5-198.5T480-760q-35 0-68.5 8.5T348-726l-59-59q43-26 91.5-40.5T480-840q75 0 140.5 28.5t114 77q48.5 48.5 77 114T840-480q0 53-14.5 101T785-289ZM520-554l-80-80v-46h80v126ZM792-56 672-176q-42 26-90 41t-102 15q-138 0-240.5-91.5T122-440h82q14 104 92.5 172T480-200q37 0 70.5-8.5T614-234L288-560H120v-168l-64-64 56-56 736 736-56 56Z"/></svg>';
|
||||
|
||||
final searchFocusNode = FocusNode();
|
||||
final controller = TextEditingController();
|
||||
|
||||
@@ -21,8 +19,13 @@ class SSearchController extends GetxController {
|
||||
String hintText = '搜索';
|
||||
|
||||
late bool enableHotKey;
|
||||
late bool enableSearchRcmd;
|
||||
|
||||
Rx<LoadingState> loadingState = LoadingState.loading().obs;
|
||||
Rx<LoadingState<TrendingData>> loadingState =
|
||||
LoadingState<TrendingData>.loading().obs;
|
||||
|
||||
Rx<LoadingState<SearchKeywordData>> recommendData =
|
||||
LoadingState<SearchKeywordData>.loading().obs;
|
||||
|
||||
int initIndex = 0;
|
||||
|
||||
@@ -32,6 +35,8 @@ class SSearchController extends GetxController {
|
||||
late final searchSuggestion = GStorage.searchSuggestion;
|
||||
late final RxBool recordSearchHistory = GStorage.recordSearchHistory.obs;
|
||||
|
||||
late final digitOnlyRegExp = RegExp(r'^\d+$');
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
@@ -53,13 +58,20 @@ class SSearchController extends GetxController {
|
||||
enableHotKey =
|
||||
GStorage.setting.get(SettingBoxKey.enableHotKey, defaultValue: true);
|
||||
|
||||
enableSearchRcmd = GStorage.setting
|
||||
.get(SettingBoxKey.enableSearchRcmd, defaultValue: true);
|
||||
|
||||
if (enableHotKey) {
|
||||
queryHotSearchList();
|
||||
}
|
||||
|
||||
if (enableSearchRcmd) {
|
||||
queryRecommendList();
|
||||
}
|
||||
}
|
||||
|
||||
void validateUid() {
|
||||
showUidBtn.value = RegExp(r'^\d+$').hasMatch(controller.text);
|
||||
showUidBtn.value = digitOnlyRegExp.hasMatch(controller.text);
|
||||
}
|
||||
|
||||
void onChange(String value) {
|
||||
@@ -112,12 +124,11 @@ class SSearchController extends GetxController {
|
||||
|
||||
// 获取热搜关键词
|
||||
Future queryHotSearchList() async {
|
||||
dynamic result = await SearchHttp.hotSearchList();
|
||||
if (result['status']) {
|
||||
loadingState.value = LoadingState.success(result['data'].list);
|
||||
} else {
|
||||
loadingState.value = LoadingState.error(result['msg']);
|
||||
}
|
||||
loadingState.value = await SearchHttp.searchTrending(limit: 10);
|
||||
}
|
||||
|
||||
Future queryRecommendList() async {
|
||||
recommendData.value = await SearchHttp.searchRecommend();
|
||||
}
|
||||
|
||||
void onClickKeyword(String keyword) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:PiliPlus/common/widgets/disabled_icon.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/search/search_trending/trending_data.dart';
|
||||
import 'package:PiliPlus/models/search/suggest.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/hot_keyword.dart';
|
||||
@@ -42,11 +43,8 @@ class _SearchPageState extends State<SearchPage> {
|
||||
tooltip: 'UID搜索用户',
|
||||
icon: const Icon(Icons.person_outline, size: 22),
|
||||
onPressed: () {
|
||||
if (RegExp(r'^\d+$')
|
||||
.hasMatch(_searchController.controller.text)) {
|
||||
Get.toNamed(
|
||||
'/member?mid=${_searchController.controller.text}');
|
||||
}
|
||||
Get.toNamed(
|
||||
'/member?mid=${_searchController.controller.text}');
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
@@ -83,11 +81,9 @@ class _SearchPageState extends State<SearchPage> {
|
||||
// 搜索建议
|
||||
if (_searchController.searchSuggestion) _searchSuggest(),
|
||||
if (context.orientation == Orientation.portrait) ...[
|
||||
if (_searchController.enableHotKey)
|
||||
// 热搜
|
||||
hotSearch(),
|
||||
// 搜索历史
|
||||
_history()
|
||||
if (_searchController.enableHotKey) hotSearch(),
|
||||
_history(),
|
||||
if (_searchController.enableSearchRcmd) hotSearch(false)
|
||||
] else
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -95,6 +91,8 @@ class _SearchPageState extends State<SearchPage> {
|
||||
if (_searchController.enableHotKey)
|
||||
Expanded(child: hotSearch()),
|
||||
Expanded(child: _history()),
|
||||
if (_searchController.enableSearchRcmd)
|
||||
Expanded(child: hotSearch(false)),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -135,7 +133,15 @@ class _SearchPageState extends State<SearchPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget hotSearch() {
|
||||
Widget hotSearch([bool isHot = true]) {
|
||||
final text = Text(
|
||||
isHot ? '大家都在搜' : '搜索发现',
|
||||
strutStyle: const StrutStyle(leading: 0, height: 1),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(height: 1, fontWeight: FontWeight.bold),
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 25, 4, 25),
|
||||
child: Column(
|
||||
@@ -144,61 +150,54 @@ class _SearchPageState extends State<SearchPage> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'大家都在搜',
|
||||
strutStyle: StrutStyle(leading: 0, height: 1),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(height: 1, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
'/searchTrending',
|
||||
parameters: {'tag': _tag},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 6, horizontal: 10),
|
||||
child: Text.rich(
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
TextSpan(
|
||||
isHot
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '完整榜单',
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Icon(
|
||||
size: 16,
|
||||
Icons.keyboard_arrow_right,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
text,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 14),
|
||||
child: SizedBox(
|
||||
height: 34,
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
Get.toNamed(
|
||||
'/searchTrending',
|
||||
parameters: {'tag': _tag},
|
||||
);
|
||||
},
|
||||
label: Text(
|
||||
'完整榜单',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
icon: Icon(
|
||||
size: 16,
|
||||
Icons.keyboard_arrow_right,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
iconAlignment: IconAlignment.end,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
)
|
||||
: text,
|
||||
SizedBox(
|
||||
height: 34,
|
||||
child: TextButton.icon(
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
const EdgeInsets.only(
|
||||
left: 10, top: 6, bottom: 6, right: 10),
|
||||
),
|
||||
padding: const WidgetStatePropertyAll(
|
||||
EdgeInsets.symmetric(horizontal: 10, vertical: 6)),
|
||||
),
|
||||
onPressed: _searchController.queryHotSearchList,
|
||||
onPressed: isHot
|
||||
? _searchController.queryHotSearchList
|
||||
: _searchController.queryRecommendList,
|
||||
icon: Icon(
|
||||
Icons.refresh_outlined,
|
||||
size: 18,
|
||||
@@ -215,7 +214,11 @@ class _SearchPageState extends State<SearchPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
Obx(() => _buildHotKey(_searchController.loadingState.value)),
|
||||
Obx(() => _buildHotKey(
|
||||
isHot
|
||||
? _searchController.loadingState.value
|
||||
: _searchController.recommendData.value,
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -223,8 +226,7 @@ class _SearchPageState extends State<SearchPage> {
|
||||
|
||||
Widget _history() {
|
||||
return Obx(
|
||||
() => Container(
|
||||
width: double.infinity,
|
||||
() => Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
10,
|
||||
context.orientation == Orientation.landscape
|
||||
@@ -233,7 +235,7 @@ class _SearchPageState extends State<SearchPage> {
|
||||
? 0
|
||||
: 6,
|
||||
6,
|
||||
MediaQuery.of(context).padding.bottom + 50,
|
||||
25,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -262,25 +264,8 @@ class _SearchPageState extends State<SearchPage> {
|
||||
? '记录搜索'
|
||||
: '无痕搜索',
|
||||
icon: _searchController.recordSearchHistory.value
|
||||
? Icon(
|
||||
Icons.history,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant
|
||||
.withOpacity(0.8),
|
||||
)
|
||||
: SvgPicture.string(
|
||||
width: 22,
|
||||
height: 22,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.outline
|
||||
.withOpacity(0.8),
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
_searchController.historyOff,
|
||||
),
|
||||
? historyIcon
|
||||
: historyIcon.disable(),
|
||||
style: IconButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
@@ -349,13 +334,16 @@ class _SearchPageState extends State<SearchPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHotKey(LoadingState loadingState) {
|
||||
Icon get historyIcon => Icon(Icons.history,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.8));
|
||||
|
||||
Widget _buildHotKey(LoadingState<SearchKeywordData> loadingState) {
|
||||
return switch (loadingState) {
|
||||
Success() => (loadingState.response as List?)?.isNotEmpty == true
|
||||
Success() => loadingState.response.list?.isNotEmpty == true
|
||||
? LayoutBuilder(
|
||||
builder: (context, constraints) => HotKeyword(
|
||||
width: constraints.maxWidth,
|
||||
hotSearchList: loadingState.response,
|
||||
hotSearchList: loadingState.response.list!,
|
||||
onClick: _searchController.onClickKeyword,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import 'package:PiliPlus/models/search/search_trending/trending_list.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HotKeyword extends StatelessWidget {
|
||||
final double? width;
|
||||
final List? hotSearchList;
|
||||
final double width;
|
||||
final List<SearchKeywordList> hotSearchList;
|
||||
final Function? onClick;
|
||||
final bool showMore;
|
||||
const HotKeyword({
|
||||
this.width,
|
||||
this.hotSearchList,
|
||||
this.onClick,
|
||||
super.key,
|
||||
});
|
||||
required double width,
|
||||
required this.hotSearchList,
|
||||
this.onClick,
|
||||
this.showMore = true,
|
||||
}) : this.width = width / 2 - 4;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -19,22 +22,19 @@ class HotKeyword extends StatelessWidget {
|
||||
runSpacing: 0.4,
|
||||
spacing: 5.0,
|
||||
children: [
|
||||
for (var i in hotSearchList!)
|
||||
for (var i in hotSearchList)
|
||||
SizedBox(
|
||||
width: width! / 2 - 4,
|
||||
width: width,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(3)),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
onTap: () => onClick?.call(i.keyword),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 2,
|
||||
right: 10,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 2, right: 10),
|
||||
child: Tooltip(
|
||||
message: i.keyword!,
|
||||
message: i.keyword,
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
@@ -48,16 +48,14 @@ class HotKeyword extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (i.icon != null && i.icon != '') ...[
|
||||
const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
height: 15,
|
||||
if (!i.icon.isNullOrEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 4),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: (i.icon as String).http2https,
|
||||
height: 15.0,
|
||||
imageUrl: i.icon!.http2https,
|
||||
height: 15,
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user