diff --git a/lib/models/common/search_type.dart b/lib/models/common/search_type.dart index d7d13aec..843e7954 100644 --- a/lib/models/common/search_type.dart +++ b/lib/models/common/search_type.dart @@ -28,7 +28,7 @@ extension SearchTypeExtension on SearchType { String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index]; } -// 搜索类型为视频、专栏及相簿时 +// 搜索类型为视频时 enum ArchiveFilterType { totalrank, click, @@ -44,3 +44,21 @@ extension ArchiveFilterTypeExtension on ArchiveFilterType { String get description => ['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index]; } + +// 搜索类型为专栏时 +enum ArticleFilterType { + // 综合排序 + totalrank, + // 最新发布 + pubdate, + // 最多点击 + click, + // 最多喜欢 + attention, + // 最多评论 + scores, +} + +extension ArticleFilterTypeExtension on ArticleFilterType { + String get description => ['综合排序', '最新发布', '最多点击', '最多喜欢', '最多评论'][index]; +} diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index ad2a7781..fd18d728 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -13,6 +13,7 @@ import '../../models/common/nav_bar_config.dart'; class MainController extends GetxController { List pages = []; + List pagesIds = []; RxList navigationBars = [].obs; late List defaultNavTabs; late List navBarSort; @@ -43,7 +44,8 @@ class MainController extends GetxController { SettingBoxKey.dynamicBadgeMode, defaultValue: DynamicBadgeMode.number.code)]; setNavBarConfig(); - if (dynamicBadgeType.value != DynamicBadgeMode.hidden) { + if (dynamicBadgeType.value != DynamicBadgeMode.hidden && + pagesIds.contains(2)) { getUnreadDynamic(); } enableGradientBg = @@ -104,6 +106,7 @@ class MainController extends GetxController { // 如果找不到匹配项,默认索引设置为0或其他合适的值 selectedIndex = defaultIndex != -1 ? defaultIndex : 0; pages = navigationBars.map((e) => e['page']).toList(); + pagesIds = navigationBars.map((e) => e['id']).toList(); } @override diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 3da667e8..4399bf30 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -22,10 +22,10 @@ class MainApp extends StatefulWidget { class _MainAppState extends State with SingleTickerProviderStateMixin { final MainController _mainController = Get.put(MainController()); - final HomeController _homeController = Get.put(HomeController()); - final RankController _rankController = Get.put(RankController()); - final DynamicsController _dynamicController = Get.put(DynamicsController()); - final MediaController _mediaController = Get.put(MediaController()); + late HomeController _homeController; + RankController? _rankController; + DynamicsController? _dynamicController; + MediaController? _mediaController; int? _lastSelectTime; //上次点击时间 Box setting = GStrorage.setting; @@ -38,6 +38,7 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { _mainController.pageController = PageController(initialPage: _mainController.selectedIndex); enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true); + controllerInit(); } void setIndex(int value) async { @@ -60,38 +61,51 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { } if (currentPage is RankPage) { - if (_rankController.flag) { + if (_rankController!.flag) { // 单击返回顶部 双击并刷新 if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { - _rankController.onRefresh(); + _rankController!.onRefresh(); } else { - _rankController.animateToTop(); + _rankController!.animateToTop(); } _lastSelectTime = DateTime.now().millisecondsSinceEpoch; } - _rankController.flag = true; + _rankController!.flag = true; } else { - _rankController.flag = false; + _rankController?.flag = false; } if (currentPage is DynamicsPage) { - if (_dynamicController.flag) { + if (_dynamicController!.flag) { // 单击返回顶部 双击并刷新 if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { - _dynamicController.onRefresh(); + _dynamicController!.onRefresh(); } else { - _dynamicController.animateToTop(); + _dynamicController!.animateToTop(); } _lastSelectTime = DateTime.now().millisecondsSinceEpoch; } - _dynamicController.flag = true; + _dynamicController!.flag = true; _mainController.clearUnread(); } else { - _dynamicController.flag = false; + _dynamicController?.flag = false; } if (currentPage is MediaPage) { - _mediaController.queryFavFolder(); + _mediaController!.queryFavFolder(); + } + } + + void controllerInit() { + _homeController = Get.put(HomeController()); + if (_mainController.pagesIds.contains(1)) { + _rankController = Get.put(RankController()); + } + if (_mainController.pagesIds.contains(2)) { + _dynamicController = Get.put(DynamicsController()); + } + if (_mainController.pagesIds.contains(3)) { + _mediaController = Get.put(MediaController()); } } diff --git a/lib/pages/search_panel/controller.dart b/lib/pages/search_panel/controller.dart index dc0b2bac..2d1aa228 100644 --- a/lib/pages/search_panel/controller.dart +++ b/lib/pages/search_panel/controller.dart @@ -24,7 +24,9 @@ class SearchPanelController extends GetxController { searchType: searchType!, keyword: keyword!, 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, tids: searchType!.type != 'video' ? null : tids.value, ); diff --git a/lib/pages/search_panel/view.dart b/lib/pages/search_panel/view.dart index c5824d70..fa669489 100644 --- a/lib/pages/search_panel/view.dart +++ b/lib/pages/search_panel/view.dart @@ -1,3 +1,5 @@ +// ignore_for_file: invalid_use_of_protected_member + import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -45,6 +47,11 @@ class _SearchPanelState extends State ), tag: widget.searchType!.type + widget.keyword!, ); + + /// 专栏默认排序 + if (widget.searchType == SearchType.article) { + _searchPanelController.order.value = 'totalrank'; + } scrollController = _searchPanelController.scrollController; scrollController.addListener(() async { if (scrollController.position.pixels >= @@ -84,7 +91,6 @@ class _SearchPanelState extends State case SearchType.video: return SearchVideoPanel( ctr: _searchPanelController, - // ignore: invalid_use_of_protected_member list: list.value, ); case SearchType.media_bangumi: @@ -94,7 +100,10 @@ class _SearchPanelState extends State case SearchType.live_room: return searchLivePanel(context, ctr, list); case SearchType.article: - return searchArticlePanel(context, ctr, list); + return SearchArticlePanel( + ctr: _searchPanelController, + list: list.value, + ); default: return const SizedBox(); } diff --git a/lib/pages/search_panel/widgets/article_panel.dart b/lib/pages/search_panel/widgets/article_panel.dart index dd53de66..be08ed56 100644 --- a/lib/pages/search_panel/widgets/article_panel.dart +++ b/lib/pages/search_panel/widgets/article_panel.dart @@ -1,106 +1,249 @@ import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.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/models/common/search_type.dart'; +import 'package:pilipala/pages/search_panel/index.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) { TextStyle textStyle = TextStyle( fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, color: Theme.of(context).colorScheme.outline); - return ListView.builder( - controller: ctr!.scrollController, - itemCount: list.length, - itemBuilder: (context, index) { - return InkWell( - onTap: () { - Get.toNamed('/read', parameters: { - 'title': list[index].subTitle, - 'id': list[index].id.toString(), - 'articleType': 'read' - }); - }, - child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 5, StyleString.safeSpace, 5), - 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, + return Padding( + padding: const EdgeInsets.only(top: 36), + child: list!.isNotEmpty + ? ListView.builder( + controller: ctr!.scrollController, + addAutomaticKeepAlives: false, + addRepaintBoundaries: false, + itemCount: list.length, + itemBuilder: (context, index) { + return InkWell( + onTap: () { + Get.toNamed('/read', parameters: { + 'title': list[index].subTitle, + 'id': list[index].id.toString(), + 'articleType': 'read' + }); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 5, StyleString.safeSpace, 5), + 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: [ - 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, + 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.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 filterList = [{}].obs; + Rx selectedType = ArticleFilterType.values.first.obs; + + @override + void onInit() { + List> list = ArticleFilterType.values + .map((type) => { + 'label': type.description, + 'type': type, + }) + .toList(); + filterList.value = list; + super.onInit(); + } +}