diff --git a/lib/http/search.dart b/lib/http/search.dart index 95e8de9c0..dfd3f8db7 100644 --- a/lib/http/search.dart +++ b/lib/http/search.dart @@ -78,6 +78,7 @@ class SearchHttp { int? tids, int? orderSort, int? userType, + int? categoryId, }) async { var reqData = { 'search_type': searchType.type, @@ -90,6 +91,7 @@ class SearchHttp { if (tids != null) 'tids': tids, if (orderSort != null) 'order_sort': orderSort, if (userType != null) 'user_type': userType, + if (categoryId != null) 'category_id': categoryId, }; var res = await Request().get(Api.searchByType, data: reqData); if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) { diff --git a/lib/pages/search_panel/controller.dart b/lib/pages/search_panel/controller.dart index dc98d32d7..51695cc55 100644 --- a/lib/pages/search_panel/controller.dart +++ b/lib/pages/search_panel/controller.dart @@ -17,6 +17,7 @@ class SearchPanelController extends CommonController { int? tids; int? orderSort; int? userType; + int? categoryId; @override void onInit() { @@ -75,5 +76,6 @@ class SearchPanelController extends CommonController { tids: tids, orderSort: orderSort, userType: userType, + categoryId: categoryId, ); } diff --git a/lib/pages/search_panel/widgets/article_panel.dart b/lib/pages/search_panel/widgets/article_panel.dart index 9ddc81107..89e9a2633 100644 --- a/lib/pages/search_panel/widgets/article_panel.dart +++ b/lib/pages/search_panel/widgets/article_panel.dart @@ -1,4 +1,9 @@ +import 'package:PiliPalaX/pages/search/widgets/search_text.dart'; +import 'package:PiliPalaX/pages/search_panel/controller.dart'; +import 'package:PiliPalaX/pages/video/detail/reply/view.dart' + show MySliverPersistentHeaderDelegate; import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/common/constants.dart'; import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; @@ -6,12 +11,67 @@ import 'package:PiliPalaX/utils/utils.dart'; import '../../../utils/grid.dart'; -Widget searchArticlePanel(BuildContext context, ctr, list) { +Widget searchArticlePanel(BuildContext context, searchPanelCtr, list) { TextStyle textStyle = TextStyle( fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, color: Theme.of(context).colorScheme.outline); - return CustomScrollView(controller: ctr.scrollController, slivers: [ - SliverGrid( + final ctr = Get.put(ArticlePanelController()); + + return CustomScrollView( + controller: searchPanelCtr.scrollController, + slivers: [ + SliverPersistentHeader( + pinned: false, + floating: true, + delegate: MySliverPersistentHeaderDelegate( + child: Container( + height: 40, + color: Theme.of(context).colorScheme.surface, + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + children: [ + Obx( + () => Text( + '排序: ${ctr.orderFiltersList[ctr.currentOrderFilterval.value]['label']}', + maxLines: 1, + style: + TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), + const Spacer(), + Obx( + () => Text( + '分区: ${ctr.zoneFiltersList[ctr.currentZoneFilterval.value]['label']}', + maxLines: 1, + style: + TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), + const Spacer(), + SizedBox( + width: 32, + height: 32, + child: IconButton( + tooltip: '筛选', + style: ButtonStyle( + padding: WidgetStateProperty.all(EdgeInsets.zero), + ), + onPressed: () { + ctr.onShowFilterDialog(context, searchPanelCtr); + }, + icon: Icon( + Icons.filter_list_outlined, + size: 18, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ], + ), + ), + ), + ), + SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace, @@ -32,89 +92,211 @@ Widget searchArticlePanel(BuildContext context, ctr, list) { child: Padding( padding: const EdgeInsets.symmetric( horizontal: StyleString.safeSpace), - child: LayoutBuilder(builder: (context, boxConstraints) { - final double width = (boxConstraints.maxWidth - - StyleString.cardSpace * - 6 / - MediaQuery.textScalerOf(context).scale(1.0)) / - 2; - return Container( - constraints: const BoxConstraints(minHeight: 88), - height: width / StyleString.aspectRatio, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (list[index].imageUrls != null && - list[index].imageUrls.isNotEmpty) - AspectRatio( - 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, - children: [ - RichText( - maxLines: 2, - text: TextSpan( - children: [ - for (var i in list[index].title) ...[ - TextSpan( - text: i['text'], - style: TextStyle( - fontWeight: FontWeight.w400, - letterSpacing: 0.3, - color: i['type'] == 'em' - ? Theme.of(context) - .colorScheme - .primary - : Theme.of(context) - .colorScheme - .onSurface, + child: LayoutBuilder( + builder: (context, boxConstraints) { + final double width = (boxConstraints.maxWidth - + StyleString.cardSpace * + 6 / + MediaQuery.textScalerOf(context).scale(1.0)) / + 2; + return Container( + constraints: const BoxConstraints(minHeight: 88), + height: width / StyleString.aspectRatio, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (list[index].imageUrls != null && + list[index].imageUrls.isNotEmpty) + AspectRatio( + 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, + children: [ + RichText( + maxLines: 2, + text: TextSpan( + children: [ + for (var i in list[index].title) ...[ + TextSpan( + text: i['text'], + style: TextStyle( + fontWeight: FontWeight.w400, + 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), - ], - ), - ], + ], + ), ), ), - ), - ], - ), - ); - }), + ], + ), + ); + }, + ), ), ); }, childCount: list.length, - )) - ]); + ), + ), + ], + ); +} + +class ArticlePanelController extends GetxController { + List orderFiltersList = [ + {'label': '综合排序', 'value': 0, 'order': 'totalrank'}, + {'label': '最新发布', 'value': 1, 'order': 'pubdate'}, + {'label': '最多点击', 'value': 2, 'order': 'click'}, + {'label': '最多喜欢', 'value': 3, 'order': 'attention'}, + {'label': '最多评论', 'value': 4, 'order': 'scores'}, + ]; + List zoneFiltersList = [ + {'label': '全部分区', 'value': 0, 'categoryId': 0}, + {'label': '动画', 'value': 1, 'categoryId': 2}, + {'label': '游戏', 'value': 2, 'categoryId': 1}, + {'label': '影视', 'value': 3, 'categoryId': 28}, + {'label': '生活', 'value': 4, 'categoryId': 3}, + {'label': '兴趣', 'value': 5, 'categoryId': 29}, + {'label': '轻小说', 'value': 6, 'categoryId': 16}, + {'label': '科技', 'value': 7, 'categoryId': 17}, + {'label': '笔记', 'value': 8, 'categoryId': 41}, + ]; + RxInt currentOrderFilterval = 0.obs; + RxInt currentZoneFilterval = 0.obs; + + onShowFilterDialog( + BuildContext context, + SearchPanelController searchPanelCtr, + ) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (_) => SingleChildScrollView( + child: Container( + width: double.infinity, + padding: EdgeInsets.only( + top: 20, + left: 16, + right: 16, + bottom: 80 + MediaQuery.of(context).padding.bottom, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + const Text('排序', style: TextStyle(fontSize: 16)), + const SizedBox(height: 10), + Wrap( + spacing: 8, + runSpacing: 8, + children: orderFiltersList + .map( + (item) => SearchText( + searchText: item['label'], + onSelect: (_) async { + Get.back(); + currentOrderFilterval.value = item['value']; + SmartDialog.dismiss(); + SmartDialog.showToast("「${item['label']}」的筛选结果"); + SearchPanelController ctr = + Get.find( + tag: 'article${searchPanelCtr.keyword!}'); + ctr.order.value = item['order']; + SmartDialog.showLoading(msg: 'loading'); + await ctr.onRefresh(); + SmartDialog.dismiss(); + }, + onLongSelect: (_) {}, + bgColor: item['value'] == currentOrderFilterval.value + ? Theme.of(context).colorScheme.primaryContainer + : null, + textColor: item['value'] == currentOrderFilterval.value + ? Theme.of(context).colorScheme.onPrimaryContainer + : null, + ), + ) + .toList(), + ), + const SizedBox(height: 20), + const Text('分区', style: TextStyle(fontSize: 16)), + const SizedBox(height: 10), + Wrap( + spacing: 8, + runSpacing: 8, + children: zoneFiltersList + .map( + (item) => SearchText( + searchText: item['label'], + onSelect: (_) async { + Get.back(); + currentZoneFilterval.value = item['value']; + SmartDialog.dismiss(); + SmartDialog.showToast("「${item['label']}」的筛选结果"); + SearchPanelController ctr = + Get.find( + tag: 'article${searchPanelCtr.keyword!}'); + ctr.categoryId = item['categoryId']; + SmartDialog.showLoading(msg: 'loading'); + await ctr.onRefresh(); + SmartDialog.dismiss(); + }, + onLongSelect: (_) {}, + bgColor: item['value'] == currentZoneFilterval.value + ? Theme.of(context).colorScheme.primaryContainer + : null, + textColor: item['value'] == currentZoneFilterval.value + ? Theme.of(context).colorScheme.onPrimaryContainer + : null, + ), + ) + .toList(), + ), + ], + ), + ), + ), + ); + } }