feat: 专栏分类搜索

This commit is contained in:
guozhigq
2024-10-04 23:07:34 +08:00
parent 943f6966e1
commit 7273df70b7
4 changed files with 263 additions and 91 deletions

View File

@ -28,7 +28,7 @@ extension SearchTypeExtension on SearchType {
String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index]; String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index];
} }
// 搜索类型为视频、专栏及相簿 // 搜索类型为视频时
enum ArchiveFilterType { enum ArchiveFilterType {
totalrank, totalrank,
click, click,
@ -44,3 +44,21 @@ extension ArchiveFilterTypeExtension on ArchiveFilterType {
String get description => String get description =>
['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index]; ['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index];
} }
// 搜索类型为专栏时
enum ArticleFilterType {
// 综合排序
totalrank,
// 最新发布
pubdate,
// 最多点击
click,
// 最多喜欢
attention,
// 最多评论
scores,
}
extension ArticleFilterTypeExtension on ArticleFilterType {
String get description => ['综合排序', '最新发布', '最多点击', '最多喜欢', '最多评论'][index];
}

View File

@ -24,7 +24,9 @@ class SearchPanelController extends GetxController {
searchType: searchType!, searchType: searchType!,
keyword: keyword!, keyword: keyword!,
page: page.value, page: page.value,
order: searchType!.type != 'video' ? null : order.value, order: !['video', 'article'].contains(searchType!.type)
? null
: (order.value == '' ? null : order.value),
duration: searchType!.type != 'video' ? null : duration.value, duration: searchType!.type != 'video' ? null : duration.value,
tids: searchType!.type != 'video' ? null : tids.value, tids: searchType!.type != 'video' ? null : tids.value,
); );

View File

@ -1,3 +1,5 @@
// ignore_for_file: invalid_use_of_protected_member
import 'package:easy_debounce/easy_throttle.dart'; import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -45,6 +47,11 @@ class _SearchPanelState extends State<SearchPanel>
), ),
tag: widget.searchType!.type + widget.keyword!, tag: widget.searchType!.type + widget.keyword!,
); );
/// 专栏默认排序
if (widget.searchType == SearchType.article) {
_searchPanelController.order.value = 'totalrank';
}
scrollController = _searchPanelController.scrollController; scrollController = _searchPanelController.scrollController;
scrollController.addListener(() async { scrollController.addListener(() async {
if (scrollController.position.pixels >= if (scrollController.position.pixels >=
@ -84,7 +91,6 @@ class _SearchPanelState extends State<SearchPanel>
case SearchType.video: case SearchType.video:
return SearchVideoPanel( return SearchVideoPanel(
ctr: _searchPanelController, ctr: _searchPanelController,
// ignore: invalid_use_of_protected_member
list: list.value, list: list.value,
); );
case SearchType.media_bangumi: case SearchType.media_bangumi:
@ -94,7 +100,10 @@ class _SearchPanelState extends State<SearchPanel>
case SearchType.live_room: case SearchType.live_room:
return searchLivePanel(context, ctr, list); return searchLivePanel(context, ctr, list);
case SearchType.article: case SearchType.article:
return searchArticlePanel(context, ctr, list); return SearchArticlePanel(
ctr: _searchPanelController,
list: list.value,
);
default: default:
return const SizedBox(); return const SizedBox();
} }

View File

@ -1,106 +1,249 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/pages/search_panel/index.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
class SearchArticlePanel extends StatelessWidget {
SearchArticlePanel({
required this.ctr,
this.list,
Key? key,
}) : super(key: key);
final SearchPanelController ctr;
final List? list;
final ArticlePanelController controller = Get.put(ArticlePanelController());
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.topCenter,
children: [
searchArticlePanel(context, ctr, list),
Container(
width: double.infinity,
height: 36,
padding: const EdgeInsets.only(left: 8, top: 0, right: 8),
child: Row(
children: [
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Obx(
() => Wrap(
// spacing: ,
children: [
for (var i in controller.filterList) ...[
CustomFilterChip(
label: i['label'],
type: i['type'],
selectedType: controller.selectedType.value,
callFn: (bool selected) async {
controller.selectedType.value = i['type'];
ctr.order.value =
i['type'].toString().split('.').last;
SmartDialog.showLoading(msg: 'loading');
await ctr.onRefresh();
SmartDialog.dismiss();
},
),
]
],
),
),
),
),
],
),
),
],
);
}
}
Widget searchArticlePanel(BuildContext context, ctr, list) { Widget searchArticlePanel(BuildContext context, ctr, list) {
TextStyle textStyle = TextStyle( TextStyle textStyle = TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.outline); color: Theme.of(context).colorScheme.outline);
return ListView.builder( return Padding(
controller: ctr!.scrollController, padding: const EdgeInsets.only(top: 36),
itemCount: list.length, child: list!.isNotEmpty
itemBuilder: (context, index) { ? ListView.builder(
return InkWell( controller: ctr!.scrollController,
onTap: () { addAutomaticKeepAlives: false,
Get.toNamed('/read', parameters: { addRepaintBoundaries: false,
'title': list[index].subTitle, itemCount: list.length,
'id': list[index].id.toString(), itemBuilder: (context, index) {
'articleType': 'read' return InkWell(
}); onTap: () {
}, Get.toNamed('/read', parameters: {
child: Padding( 'title': list[index].subTitle,
padding: const EdgeInsets.fromLTRB( 'id': list[index].id.toString(),
StyleString.safeSpace, 5, StyleString.safeSpace, 5), 'articleType': 'read'
child: LayoutBuilder(builder: (context, boxConstraints) { });
final double width = (boxConstraints.maxWidth - },
StyleString.cardSpace * child: Padding(
6 / padding: const EdgeInsets.fromLTRB(
MediaQuery.textScalerOf(context).scale(1.0)) / StyleString.safeSpace, 5, StyleString.safeSpace, 5),
2; child: LayoutBuilder(builder: (context, boxConstraints) {
return Container( final double width = (boxConstraints.maxWidth -
constraints: const BoxConstraints(minHeight: 88), StyleString.cardSpace *
height: width / StyleString.aspectRatio, 6 /
child: Row( MediaQuery.textScalerOf(context).scale(1.0)) /
crossAxisAlignment: CrossAxisAlignment.start, 2;
children: <Widget>[ return Container(
if (list[index].imageUrls != null && constraints: const BoxConstraints(minHeight: 88),
list[index].imageUrls.isNotEmpty) height: width / StyleString.aspectRatio,
AspectRatio( child: Row(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return NetworkImgLayer(
width: maxWidth,
height: maxHeight,
src: list[index].imageUrls.first,
);
}),
),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
RichText( if (list[index].imageUrls != null &&
maxLines: 2, list[index].imageUrls.isNotEmpty)
text: TextSpan( AspectRatio(
children: [ aspectRatio: StyleString.aspectRatio,
for (var i in list[index].title) ...[ child: LayoutBuilder(
TextSpan( builder: (context, boxConstraints) {
text: i['text'], double maxWidth = boxConstraints.maxWidth;
style: TextStyle( double maxHeight = boxConstraints.maxHeight;
fontWeight: FontWeight.w500, return NetworkImgLayer(
letterSpacing: 0.3, width: maxWidth,
color: i['type'] == 'em' height: maxHeight,
? Theme.of(context) src: list[index].imageUrls.first,
.colorScheme );
.primary }),
: Theme.of(context) ),
.colorScheme Expanded(
.onSurface, child: Padding(
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
maxLines: 2,
text: TextSpan(
children: [
for (var i in list[index].title) ...[
TextSpan(
text: i['text'],
style: TextStyle(
fontWeight: FontWeight.w500,
letterSpacing: 0.3,
color: i['type'] == 'em'
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.onSurface,
),
),
]
],
), ),
), ),
] const Spacer(),
], Text(
Utils.dateFormat(list[index].pubTime,
formatType: 'detail'),
style: textStyle),
Row(
children: [
Text('${list[index].view}浏览',
style: textStyle),
Text('', style: textStyle),
Text('${list[index].reply}评论',
style: textStyle),
],
),
],
),
), ),
), ),
const Spacer(),
Text(
Utils.dateFormat(list[index].pubTime,
formatType: 'detail'),
style: textStyle),
Row(
children: [
Text('${list[index].view}浏览', style: textStyle),
Text('', style: textStyle),
Text('${list[index].reply}评论', style: textStyle),
],
),
], ],
), ),
), );
), }),
], ),
), );
); },
}), )
), : CustomScrollView(
); slivers: [
}, HttpError(
errMsg: '没有数据',
isShowBtn: false,
fn: () => {},
)
],
),
); );
} }
class CustomFilterChip extends StatelessWidget {
const CustomFilterChip({
this.label,
this.type,
this.selectedType,
this.callFn,
Key? key,
}) : super(key: key);
final String? label;
final ArticleFilterType? type;
final ArticleFilterType? selectedType;
final Function? callFn;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 34,
child: FilterChip(
padding: const EdgeInsets.only(left: 11, right: 11),
labelPadding: EdgeInsets.zero,
label: Text(
label!,
style: const TextStyle(fontSize: 13),
),
labelStyle: TextStyle(
color: type == selectedType
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline),
selected: type == selectedType,
showCheckmark: false,
shape: ContinuousRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
selectedColor: Colors.transparent,
// backgroundColor:
// Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5),
backgroundColor: Colors.transparent,
side: BorderSide.none,
onSelected: (bool selected) => callFn!(selected),
),
);
}
}
class ArticlePanelController extends GetxController {
RxList<Map> filterList = [{}].obs;
Rx<ArticleFilterType> selectedType = ArticleFilterType.values.first.obs;
@override
void onInit() {
List<Map<String, dynamic>> list = ArticleFilterType.values
.map((type) => {
'label': type.description,
'type': type,
})
.toList();
filterList.value = list;
super.onInit();
}
}