From 11c5528c45c427d20372c5f538d95eb0f7b094fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=81=A5=E9=81=A5=E7=B4=AB=E8=8D=86?= <66421433+yaoyaozijing@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:29:33 +0800 Subject: [PATCH 01/14] Update pr.yaml From d41aea875869069d0d9611efd8df30e4c5aadc68 Mon Sep 17 00:00:00 2001 From: YYZJ Date: Thu, 13 Nov 2025 15:45:07 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E9=A1=B5=E5=B1=95=E5=BC=80=E4=B8=8E=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/info_page.dart | 28 +- lib/pages/info/source_sheet.dart | 642 ++++++++++++++++++++++--------- lib/request/query_manager.dart | 8 +- 3 files changed, 493 insertions(+), 185 deletions(-) diff --git a/lib/pages/info/info_page.dart b/lib/pages/info/info_page.dart index 4c66e23ec..d4d53f88c 100644 --- a/lib/pages/info/info_page.dart +++ b/lib/pages/info/info_page.dart @@ -359,22 +359,28 @@ class _InfoPageState extends State with TickerProviderStateMixin { onPressed: () async { showModalBottomSheet( isScrollControlled: true, - constraints: BoxConstraints( - maxHeight: (MediaQuery.sizeOf(context).height >= - LayoutBreakpoint.compact['height']!) - ? MediaQuery.of(context).size.height * 3 / 4 - : MediaQuery.of(context).size.height, - maxWidth: (MediaQuery.sizeOf(context).width >= - LayoutBreakpoint.medium['width']!) - ? MediaQuery.of(context).size.width * 9 / 16 - : MediaQuery.of(context).size.width, - ), clipBehavior: Clip.antiAlias, backgroundColor: Theme.of(context).scaffoldBackgroundColor, showDragHandle: true, context: context, builder: (context) { - return SourceSheet(tabController: sourceTabController, infoController: infoController); + final double minChildSize = 0.3; + return DraggableScrollableSheet( + initialChildSize: (MediaQuery.sizeOf(context).height >= LayoutBreakpoint.compact['height']!) + ? 0.75 + : 0.90, + minChildSize: minChildSize, + maxChildSize: 1.0, + expand: false, + builder: (context, scrollController) { + return SourceSheet( + tabController: sourceTabController, + infoController: infoController, + scrollController: scrollController, + tabGridHeight: minChildSize*0.7, + ); + }, + ); }, ); }, diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index e72fd815f..515bff456 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:kazumi/utils/utils.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:kazumi/pages/info/info_controller.dart'; @@ -18,17 +19,44 @@ class SourceSheet extends StatefulWidget { super.key, required this.tabController, required this.infoController, + required this.scrollController, + required this.tabGridHeight, }); final TabController tabController; final InfoController infoController; + final ScrollController scrollController; + final double tabGridHeight; @override State createState() => _SourceSheetState(); } -class _SourceSheetState extends State - with SingleTickerProviderStateMixin { +class _SourceSheetState extends State with SingleTickerProviderStateMixin { + bool expandedByScroll = false; //通过滚动展开 + var expandedByClick = 0; //通过点击展开 + bool _showOnlySuccess = false; + final tabBarHeight = 48.0; + void _maybeExpandTabGridOnListViewHeight(BoxConstraints constraints) { + final screenHeight = MediaQuery.of(context).size.height; + if (constraints.maxHeight >= screenHeight - tabBarHeight - 1 - 48 && !_showTabGrid && !expandedByScroll && expandedByClick !=2) { //48为小白条高度 手动点击按钮后不响应弹出 + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _showTabGrid = true; + expandedByScroll = true; + }); + }); + } else if (constraints.maxHeight < screenHeight * ( 1 - widget.tabGridHeight ) - tabBarHeight - 1 - 48 - 40 && _showTabGrid &&expandedByScroll && expandedByClick !=1) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _showTabGrid = false; + expandedByScroll = false; + }); + }); + } + } + final ScrollController _tabGridScrollController = ScrollController(); + bool _showTabGrid = false; final VideoPageController videoPageController = Modular.get(); final CollectController collectController = Modular.get(); @@ -46,10 +74,15 @@ class _SourceSheetState extends State queryManager = QueryManager(infoController: widget.infoController); queryManager?.queryAllSource(keyword); super.initState(); + widget.tabController.addListener(() { + if (!mounted) return; + setState(() {}); + }); } @override void dispose() { + _tabGridScrollController.dispose(); queryManager?.cancel(); queryManager = null; super.dispose(); @@ -187,196 +220,461 @@ class _SourceSheetState extends State child: Scaffold( body: Column( children: [ - Row( - children: [ - Expanded( - child: TabBar( - isScrollable: true, - tabAlignment: TabAlignment.center, - dividerHeight: 0, - controller: widget.tabController, - tabs: pluginsController.pluginList - .map( - (plugin) => Observer( - builder: (context) { - bool isSuccessButEmpty = false; - if (widget.infoController - .pluginSearchStatus[plugin.name] == - 'success') { - bool hasContent = false; - for (var searchResponse in widget - .infoController.pluginSearchResponseList) { - if (searchResponse.pluginName == - plugin.name && - searchResponse.data.isNotEmpty) { - hasContent = true; - break; - } - } - isSuccessButEmpty = !hasContent; - } - - return Tab( - child: Row( - children: [ - Text( - plugin.name, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .titleMedium! - .fontSize, - color: Theme.of(context) - .colorScheme - .onSurface), - ), - const SizedBox(width: 5.0), - Container( - width: 8.0, - height: 8.0, - decoration: BoxDecoration( - color: isSuccessButEmpty - ? Colors.orange - : (widget.infoController - .pluginSearchStatus[ - plugin.name] == - 'success' - ? Colors.green - : (widget.infoController + Listener( + onPointerSignal: (event) { + if (event is PointerScrollEvent) { + if (!_showTabGrid && (event.scrollDelta.dy.abs() > 8)) { + setState(() { + _showTabGrid = true; + }); + } else if (_showTabGrid && (event.scrollDelta.dy.abs() > 8)) { + // 仅当面板内部无需滚动时才允许收起 + final maxScroll = _tabGridScrollController.hasClients + ? _tabGridScrollController.position.maxScrollExtent + : 0.0; + if (maxScroll == 0.0) { + setState(() { + _showTabGrid = false; + }); + } + } + } + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + height: _showTabGrid ? MediaQuery.of(context).size.height * widget.tabGridHeight + tabBarHeight - 12 : tabBarHeight, + child: Stack( + children: [ + // TabBar(收起时显示) + AnimatedOpacity( + opacity: _showTabGrid ? 0 : 1, + duration: const Duration(milliseconds: 200), + child: Row( + children: [ + Expanded( + child: TabBar( + isScrollable: true, + tabAlignment: TabAlignment.center, + dividerHeight: 0, + controller: widget.tabController, + tabs: pluginsController.pluginList + .map( + (plugin) => Observer( + builder: (context) => Tab( + child: Row( + children: [ + Text( + plugin.name, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .titleMedium! + .fontSize, + color: Theme.of(context) + .colorScheme + .onSurface), + ), + const SizedBox(width: 5.0), + Container( + width: 8.0, + height: 8.0, + decoration: BoxDecoration( + color: widget.infoController .pluginSearchStatus[ plugin.name] == - 'pending') - ? Colors.grey - : Colors.red), - shape: BoxShape.circle, + 'success' + ? Colors.green + : (widget.infoController + .pluginSearchStatus[ + plugin.name] == + 'pending') + ? Theme.of(context).colorScheme.onSurfaceVariant + : (widget.infoController + .pluginSearchStatus[ + plugin.name] == + 'noresult') + ? Colors.orange + : Colors.red, + shape: BoxShape.circle, + ), + ), + ], + ), ), ), - ], - ), - ); + ) + .toList(), + ), + ), + IconButton( + onPressed: () { + expandedByClick = 1; + setState(() { + _showTabGrid = true; + }); }, + icon: const Icon(Icons.keyboard_arrow_down), + tooltip: '展开', ), - ) - .toList(), - ), - ), - IconButton( - onPressed: () { - int currentIndex = widget.tabController.index; - launchUrl( - Uri.parse(pluginsController - .pluginList[currentIndex].searchURL - .replaceFirst('@keyword', keyword)), - mode: LaunchMode.externalApplication, - ); - }, - icon: const Icon(Icons.open_in_browser_rounded), + SizedBox(width: 2), + ], + ), + ), + // 展开时显示的按钮网格 + AnimatedOpacity( + opacity: _showTabGrid ? 1 : 0, + duration: const Duration(milliseconds: 200), + child: _showTabGrid + ? Container( + color: Theme.of(context).scaffoldBackgroundColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children:[ + Row( + children:[ + Row( + children: [ + SizedBox(width : 16), + Tooltip( + message:"点击打开规则管理", + child: TextButton( + onPressed: () { + Modular.to.pushNamed('/settings/plugin/'); + }, + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + minimumSize: const Size(0, 0), + ), + child: Text( + '番源', + style: TextStyle( + fontSize: 18, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ) + ), + ] + ), + Spacer(), + Row( + children: [ + IconButton( + onPressed: () { + setState(() { + _showOnlySuccess = !_showOnlySuccess; + }); + }, + icon: Icon(_showOnlySuccess ? Icons.filter_alt : Icons.filter_alt_outlined,), + tooltip: '筛选有结果项', + ), + IconButton( + onPressed: () { + expandedByClick = 2; + setState(() { + _showTabGrid = false; + });}, + icon: const Icon(Icons.keyboard_arrow_up), + tooltip: '收起', + ), + SizedBox(width: 2), + ] + ), + ] + ), + Expanded( + child: ScrollConfiguration( + behavior: const ScrollBehavior().copyWith(scrollbars: false), + child:SingleChildScrollView( + controller: _tabGridScrollController, + physics: const ClampingScrollPhysics(), + padding: const EdgeInsets.fromLTRB(16, 2, 16, 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Observer( + builder: (_) { + // 根据筛选条件生成要显示的插件列表 + final visiblePluginsWithIndex = pluginsController.pluginList + .asMap() + .entries + .where((entry) { + final plugin = entry.value; + final status = widget.infoController.pluginSearchStatus[plugin.name]; + if (_showOnlySuccess) return status == 'success'; + return true; + }) + .toList(); // entry.key = 原始索引 + + return Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.start, + children: visiblePluginsWithIndex.map((entry){ + final originalIndex = entry.key; + final plugin = entry.value; + final status = widget.infoController.pluginSearchStatus[plugin.name]; + + return MenuAnchor( + menuChildren: [ + TextButton.icon( + onPressed: (){ + queryManager?.querySource(keyword, plugin.name); + }, + icon: const Icon(Icons.refresh), + label: const Text("重新检索") + ), + TextButton.icon( + onPressed: () { + showAliasSearchDialog(pluginsController.pluginList[widget.tabController.index].name); + }, + icon: const Icon(Icons.saved_search_rounded), + label: const Text("别名检索"), + ), + TextButton.icon( + onPressed: (){ + showCustomSearchDialog(pluginsController.pluginList[widget.tabController.index].name); + }, + icon: const Icon(Icons.search_rounded), + label: const Text("手动检索"), + ), + TextButton.icon( + onPressed: () { + launchUrl( + Uri.parse(pluginsController + .pluginList[widget.tabController.index].searchURL + .replaceFirst('@keyword', keyword)), + mode: LaunchMode.externalApplication, + ); + }, + icon: const Icon(Icons.open_in_browser_rounded), + label: const Text('打开网页'), + ), + ], + builder:(context, controller, child){ + return GestureDetector( + onSecondaryTap: () { + widget.tabController.index = originalIndex; + controller.open(); + }, + onLongPress: () { + widget.tabController.index = originalIndex; + controller.open(); + }, + child: child, + ); + }, + child: ActionChip( + label: Text( + plugin.name, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15, + color: widget.tabController.index == originalIndex + ? Theme.of(context).colorScheme.onPrimary + : null, + ), + ), + backgroundColor: widget.tabController.index == originalIndex + ? status == 'success' + ? Theme.of(context).colorScheme.secondary + : Color.lerp( + Theme.of(context).colorScheme.secondary, + status == 'pending' + ? Theme.of(context).colorScheme.onSurfaceVariant + : status =='noresult' + ? Colors.orange + : Colors.red, + 0.3, + ) + : status == 'success' + ? null + : Color.lerp( + null, + status == 'pending' + ? Theme.of(context).colorScheme.onSurfaceVariant + : status =='noresult' + ? Colors.orange + : Colors.red, + 0.075, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(9), + side: BorderSide( + color: status == 'success' + ? Color.lerp(Theme.of(context).colorScheme.outlineVariant,Theme.of(context).colorScheme.secondary,0.15)! + // ? Theme.of(context).colorScheme.outlineVariant + : Color.lerp( + Theme.of(context).colorScheme.outlineVariant, + status == 'pending' + ? Theme.of(context).colorScheme.onSurfaceVariant + : status == 'noresult' + ? Colors.orange + : Colors.red, + 0.15 + )!, + ), + ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onPressed: () { + widget.tabController.index = originalIndex; + // if (!expandedByScroll) { + // _showTabGrid = false; + // } + }, + ), + ); + }).toList(), + ); + } + ) + ], + ) + ), + ), + ), + ] + ), + ) + : const SizedBox.shrink(), + ), + ], ), - const SizedBox(width: 4), - ], + ), ), const Divider(height: 1), Expanded( - child: Observer( - builder: (context) => TabBarView( - controller: widget.tabController, - children: List.generate(pluginsController.pluginList.length, - (pluginIndex) { - var plugin = pluginsController.pluginList[pluginIndex]; - var cardList = []; - for (var searchResponse - in widget.infoController.pluginSearchResponseList) { - if (searchResponse.pluginName == plugin.name) { - for (var searchItem in searchResponse.data) { - cardList.add( - Card( - elevation: 0, - margin: const EdgeInsets.only( - left: 10, right: 10, top: 10), - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () async { - KazumiDialog.showLoading( - msg: '获取中', - barrierDismissible: Utils.isDesktop(), - onDismiss: () { - videoPageController.cancelQueryRoads(); + child: LayoutBuilder( + builder: (context, constraints) { + _maybeExpandTabGridOnListViewHeight(constraints); + return Observer( + builder: (context) => TabBarView( + controller: widget.tabController, + children: List.generate(pluginsController.pluginList.length, + (pluginIndex) { + var plugin = pluginsController.pluginList[pluginIndex]; + var cardList = []; + for (var searchResponse + in widget.infoController.pluginSearchResponseList) { + if (searchResponse.pluginName == plugin.name) { + for (var searchItem in searchResponse.data) { + cardList.add( + Card( + elevation: 0, + margin: const EdgeInsets.only( + left: 10, right: 10, top: 10), + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () async { + KazumiDialog.showLoading(msg: '获取中'); + videoPageController.bangumiItem = + widget.infoController.bangumiItem; + videoPageController.currentPlugin = plugin; + videoPageController.title = searchItem.name; + videoPageController.src = searchItem.src; + try { + await videoPageController.queryRoads( + searchItem.src, plugin.name); + KazumiDialog.dismiss(); + Modular.to.pushNamed('/video/'); + } catch (e) { + KazumiLogger() + .log(Level.error, e.toString()); + KazumiDialog.dismiss(); + } }, - ); - videoPageController.bangumiItem = - widget.infoController.bangumiItem; - videoPageController.currentPlugin = plugin; - videoPageController.title = searchItem.name; - videoPageController.src = searchItem.src; - try { - await videoPageController.queryRoads( - searchItem.src, plugin.name); - KazumiDialog.dismiss(); - Modular.to.pushNamed('/video/'); - } catch (_) { - KazumiLogger() - .log(Level.warning, "获取视频播放列表失败"); - KazumiDialog.dismiss(); - } - }, - child: Padding( - padding: const EdgeInsets.all(20), - child: Text(searchItem.name), + child: Padding( + padding: const EdgeInsets.all(20), + child: Text(searchItem.name), + ), + ), ), - ), - ), - ); + ); + } + } } - } - } - return widget.infoController - .pluginSearchStatus[plugin.name] == - 'pending' - ? const Center(child: CircularProgressIndicator()) - : (widget.infoController + return widget.infoController .pluginSearchStatus[plugin.name] == - 'error' - ? GeneralErrorWidget( - errMsg: '${plugin.name} 检索失败 重试或左右滑动以切换到其他视频来源', - actions: [ - GeneralErrorButton( - onPressed: () { - queryManager?.querySource( - keyword, plugin.name); - }, - text: '重试', + 'pending' + ? SingleChildScrollView( + controller: widget.scrollController, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height * (1 - widget.tabGridHeight) * 0.75, + ), + child: const Center( + child: CircularProgressIndicator(), ), - ], + ), ) - : cardList.isEmpty - ? GeneralErrorWidget( - errMsg: - '${plugin.name} 无结果 使用别名或左右滑动以切换到其他视频来源', - actions: [ - GeneralErrorButton( - onPressed: () { - showAliasSearchDialog( - plugin.name, - ); - }, - text: '别名检索', + : (widget.infoController + .pluginSearchStatus[plugin.name] == + 'error' + ? SingleChildScrollView( + controller: widget.scrollController, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height * (1 - widget.tabGridHeight) * 0.75, ), - GeneralErrorButton( - onPressed: () { - showCustomSearchDialog( - plugin.name, - ); - }, - text: '手动检索', + child: Center( + child: GeneralErrorWidget( + errMsg: '${plugin.name} 检索失败 重试或左右滑动以切换到其他视频来源', + actions: [ + GeneralErrorButton( + onPressed: () { + queryManager?.querySource(keyword, plugin.name); + }, + text: '重试', + ), + ], + ), ), - ], + ), ) - : ListView(children: cardList)); - }), - ), + + : (widget.infoController + .pluginSearchStatus[plugin.name] == + 'noresult' + ? SingleChildScrollView( + controller: widget.scrollController, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height * (1 - widget.tabGridHeight) * 0.75, + ), + child: Center( + child: GeneralErrorWidget( + errMsg: '${plugin.name} 无结果 使用别名或左右滑动以切换到其他视频来源', + actions: [ + GeneralErrorButton( + onPressed: () { + showAliasSearchDialog(plugin.name); + }, + text: '别名检索', + ), + GeneralErrorButton( + onPressed: () { + showCustomSearchDialog(plugin.name); + }, + text: '手动检索', + ), + ], + ), + ), + ), + ) + : ListView( + controller: widget.scrollController, + children: cardList, + ) + ) + ); + }), + ), + ); + }, ), - ) + ), ], ), ), diff --git a/lib/request/query_manager.dart b/lib/request/query_manager.dart index c99a23d34..cb38348f6 100644 --- a/lib/request/query_manager.dart +++ b/lib/request/query_manager.dart @@ -33,9 +33,11 @@ class QueryManager { return; } - infoController.pluginSearchStatus[plugin.name] = 'success'; if (result.data.isNotEmpty) { + infoController.pluginSearchStatus[plugin.name] = 'success'; pluginsController.validityTracker.markSearchValid(plugin.name); + } else { + infoController.pluginSearchStatus[plugin.name] = 'noresult'; } infoController.pluginSearchResponseList.add(result); }).catchError((error) { @@ -65,9 +67,11 @@ class QueryManager { return; } - infoController.pluginSearchStatus[plugin.name] = 'success'; if (result.data.isNotEmpty) { + infoController.pluginSearchStatus[plugin.name] = 'success'; pluginsController.validityTracker.markSearchValid(plugin.name); + } else { + infoController.pluginSearchStatus[plugin.name] = 'noresult'; } _controller?.add(result); }).catchError((error) { From 07c8f1301f447e18db67ff115ec7f70beb36f3c7 Mon Sep 17 00:00:00 2001 From: YYZJ Date: Thu, 13 Nov 2025 16:04:38 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E8=BF=98=E5=8E=9F=E6=9D=A5=E8=87=AA?= =?UTF-8?q?=E4=B8=8A=E6=B8=B8=E7=9A=84=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/source_sheet.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index 515bff456..a46f4d21b 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -567,8 +567,14 @@ class _SourceSheetState extends State with SingleTickerProviderStat child: InkWell( borderRadius: BorderRadius.circular(12), onTap: () async { - KazumiDialog.showLoading(msg: '获取中'); - videoPageController.bangumiItem = + KazumiDialog.showLoading( + msg: '获取中', + barrierDismissible: Utils.isDesktop(), + onDismiss: () { + videoPageController.cancelQueryRoads(); + }, + ); + videoPageController.bangumiItem = widget.infoController.bangumiItem; videoPageController.currentPlugin = plugin; videoPageController.title = searchItem.name; @@ -578,9 +584,9 @@ class _SourceSheetState extends State with SingleTickerProviderStat searchItem.src, plugin.name); KazumiDialog.dismiss(); Modular.to.pushNamed('/video/'); - } catch (e) { + } catch (_) { KazumiLogger() - .log(Level.error, e.toString()); + .log(Level.warning, "获取视频播放列表失败"); KazumiDialog.dismiss(); } }, From 9927cefe9b97ceb451566a7e72185e204a4c4c6e Mon Sep 17 00:00:00 2001 From: YYZJ Date: Thu, 13 Nov 2025 21:16:33 +0800 Subject: [PATCH 04/14] =?UTF-8?q?=E5=B0=86=E9=80=89=E4=B8=AD=E7=95=AA?= =?UTF-8?q?=E6=BA=90=E7=9A=84=E6=96=87=E6=9C=AC=E5=90=8C=E6=A0=B7=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E6=9F=93=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/source_sheet.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index a46f4d21b..abb092f0c 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -472,7 +472,17 @@ class _SourceSheetState extends State with SingleTickerProviderStat style: TextStyle( fontSize: 15, color: widget.tabController.index == originalIndex - ? Theme.of(context).colorScheme.onPrimary + ? status == 'success' + ? Theme.of(context).colorScheme.onPrimary + : Color.lerp( + Theme.of(context).colorScheme.onPrimary, + status == 'pending' + ? Theme.of(context).colorScheme.onSurfaceVariant + : status =='noresult' + ? Colors.orange + : Colors.red, + 0.15, + ) : null, ), ), From 7c06945c0f1869585e7e6b84a8195a07c932b095 Mon Sep 17 00:00:00 2001 From: YYZJ Date: Thu, 13 Nov 2025 23:09:17 +0800 Subject: [PATCH 05/14] =?UTF-8?q?=E6=8F=90=E9=AB=98=E6=B5=85=E8=89=B2?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E4=B8=8B=E7=9A=84=E6=9F=93=E8=89=B2=E6=B5=93?= =?UTF-8?q?=E5=BA=A6=EF=BC=8C=E5=87=8F=E8=BD=BB=E8=84=8F=E6=84=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/source_sheet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index abb092f0c..88edb5697 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -496,7 +496,7 @@ class _SourceSheetState extends State with SingleTickerProviderStat : status =='noresult' ? Colors.orange : Colors.red, - 0.3, + Theme.of(context).brightness == Brightness.dark ? 0.3 : 0.6, ) : status == 'success' ? null From 61e40af11603da05a0471f8ff5eb40d3767cce36 Mon Sep 17 00:00:00 2001 From: YYZJ Date: Thu, 13 Nov 2025 23:12:25 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E6=9C=AA=E5=B1=95?= =?UTF-8?q?=E5=BC=80=E6=97=B6=E7=9A=84TabBar=E4=B8=8A=E7=9A=84=E5=B0=8F?= =?UTF-8?q?=E5=9C=86=E7=82=B9=EF=BC=8C=E7=BC=A9=E7=AA=84=E9=97=B4=E8=B7=9D?= =?UTF-8?q?=EF=BC=8C=E5=AF=B9=E6=8C=87=E7=A4=BA=E6=9D=A1=E3=80=81=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E8=BF=9B=E8=A1=8C=E6=9F=93=E8=89=B2=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E5=8A=A0=E7=B2=97=E6=96=87=E6=9C=AC=E4=BD=BF=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E9=A2=9C=E8=89=B2=E6=9B=B4=E6=98=93=E5=AF=9F=E8=A7=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/source_sheet.dart | 104 +++++++++++++++++-------------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index 88edb5697..e8ed4330d 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -252,59 +252,67 @@ class _SourceSheetState extends State with SingleTickerProviderStat child: Row( children: [ Expanded( - child: TabBar( - isScrollable: true, - tabAlignment: TabAlignment.center, - dividerHeight: 0, - controller: widget.tabController, - tabs: pluginsController.pluginList - .map( - (plugin) => Observer( - builder: (context) => Tab( - child: Row( - children: [ - Text( - plugin.name, - overflow: TextOverflow.ellipsis, - style: TextStyle( + child: Observer( + builder: (context) => TabBar( + isScrollable: true, + tabAlignment: TabAlignment.center, + dividerHeight: 0, + controller: widget.tabController, + labelPadding: const EdgeInsets.symmetric(horizontal: 10.0), + indicatorColor: (() { + final list = pluginsController.pluginList; + final idx = widget.tabController.index; + if (idx < 0 || idx >= list.length) { + return Theme.of(context).colorScheme.secondary; + } + final status = widget.infoController.pluginSearchStatus[list[idx].name]; + return status == 'success' + ? Theme.of(context).colorScheme.onSurface + : Color.lerp( + Theme.of(context).colorScheme.secondary, + status == 'pending' + ? Theme.of(context).colorScheme.onSurfaceVariant + : status == 'noresult' + ? Colors.orange + : Colors.red, + Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8, + )!; + })(), + tabs: pluginsController.pluginList + .asMap() + .entries + .map((entry) { + final plugin = entry.value; + final status = widget.infoController.pluginSearchStatus[plugin.name]; + return Tab( + child: Row( + children: [ + Text( + plugin.name, + overflow: TextOverflow.ellipsis, + style: TextStyle( fontSize: Theme.of(context) .textTheme .titleMedium! .fontSize, - color: Theme.of(context) - .colorScheme - .onSurface), - ), - const SizedBox(width: 5.0), - Container( - width: 8.0, - height: 8.0, - decoration: BoxDecoration( - color: widget.infoController - .pluginSearchStatus[ - plugin.name] == - 'success' - ? Colors.green - : (widget.infoController - .pluginSearchStatus[ - plugin.name] == - 'pending') - ? Theme.of(context).colorScheme.onSurfaceVariant - : (widget.infoController - .pluginSearchStatus[ - plugin.name] == - 'noresult') - ? Colors.orange - : Colors.red, - shape: BoxShape.circle, + fontWeight: FontWeight.bold, + color: status == 'success' + ? Theme.of(context).colorScheme.onSurface + : Color.lerp( + Theme.of(context).colorScheme.onSurface, + status == 'pending' + ? Theme.of(context).colorScheme.onSurfaceVariant + : status == 'noresult' + ? Colors.orange + : Colors.red, + Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8,) + ), ), - ), - ], - ), - ), - ), - ) - .toList(), + ], + ), + ); + }).toList(), + ), ), ), IconButton( From cbf29049470f6d64db36c923206787439bf59426 Mon Sep 17 00:00:00 2001 From: YYZJ Date: Fri, 14 Nov 2025 13:25:35 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=E5=B0=86=E5=B1=95=E5=BC=80=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E6=94=B9=E4=B8=BA=E6=82=AC=E6=B5=AE=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?TabBar=E8=83=BD=E6=AD=A3=E7=A1=AE=E5=B1=85=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/source_sheet.dart | 46 +++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index e8ed4330d..b3b914179 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -249,9 +249,11 @@ class _SourceSheetState extends State with SingleTickerProviderStat AnimatedOpacity( opacity: _showTabGrid ? 0 : 1, duration: const Duration(milliseconds: 200), - child: Row( + child: SizedBox( + height: tabBarHeight, + child: Stack( children: [ - Expanded( + Positioned.fill( child: Observer( builder: (context) => TabBar( isScrollable: true, @@ -285,9 +287,7 @@ class _SourceSheetState extends State with SingleTickerProviderStat final plugin = entry.value; final status = widget.infoController.pluginSearchStatus[plugin.name]; return Tab( - child: Row( - children: [ - Text( + child: Text( plugin.name, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -307,15 +307,42 @@ class _SourceSheetState extends State with SingleTickerProviderStat : Colors.red, Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8,) ), - ), - ], ), ); }).toList(), ), + ), + ), + // Fading background behind the expand button to increase contrast + Positioned( + right: 0, + top: 0, + bottom: 0, + width: 50, + child: IgnorePointer( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + // left: gradual fade (transparent -> semi), right: fully opaque + colors: [ + Theme.of(context).colorScheme.surface.withAlpha(0), + Theme.of(context).colorScheme.surface.withAlpha(230), + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.surface, + ], + stops: const [0.0, 0.3, 0.5, 1.0], ), ), - IconButton( + ), + ), + ), + Positioned( + right: 2, + top: 0, + bottom: 0, + child: IconButton( onPressed: () { expandedByClick = 1; setState(() { @@ -325,8 +352,9 @@ class _SourceSheetState extends State with SingleTickerProviderStat icon: const Icon(Icons.keyboard_arrow_down), tooltip: '展开', ), - SizedBox(width: 2), + ), ], + ), ), ), // 展开时显示的按钮网格 From 3d9e220a0f4166c056f58581297ebac32563a3b3 Mon Sep 17 00:00:00 2001 From: YYZJ Date: Fri, 14 Nov 2025 13:26:55 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E6=9F=93=E8=89=B2=E6=95=88=E6=9E=9C?= =?UTF-8?q?=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/source_sheet.dart | 120 +++++++++++++++---------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index b3b914179..b14269878 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -252,41 +252,41 @@ class _SourceSheetState extends State with SingleTickerProviderStat child: SizedBox( height: tabBarHeight, child: Stack( - children: [ + children: [ Positioned.fill( - child: Observer( - builder: (context) => TabBar( - isScrollable: true, - tabAlignment: TabAlignment.center, - dividerHeight: 0, - controller: widget.tabController, - labelPadding: const EdgeInsets.symmetric(horizontal: 10.0), - indicatorColor: (() { - final list = pluginsController.pluginList; - final idx = widget.tabController.index; - if (idx < 0 || idx >= list.length) { - return Theme.of(context).colorScheme.secondary; - } - final status = widget.infoController.pluginSearchStatus[list[idx].name]; - return status == 'success' - ? Theme.of(context).colorScheme.onSurface - : Color.lerp( - Theme.of(context).colorScheme.secondary, - status == 'pending' - ? Theme.of(context).colorScheme.onSurfaceVariant - : status == 'noresult' - ? Colors.orange - : Colors.red, - Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8, - )!; - })(), - tabs: pluginsController.pluginList - .asMap() - .entries - .map((entry) { - final plugin = entry.value; - final status = widget.infoController.pluginSearchStatus[plugin.name]; - return Tab( + child: Observer( + builder: (context) => TabBar( + isScrollable: true, + tabAlignment: TabAlignment.center, + dividerHeight: 0, + controller: widget.tabController, + labelPadding: const EdgeInsets.symmetric(horizontal: 10.0), + indicatorColor: (() { + final list = pluginsController.pluginList; + final idx = widget.tabController.index; + if (idx < 0 || idx >= list.length) { + return Theme.of(context).colorScheme.secondary; + } + final status = widget.infoController.pluginSearchStatus[list[idx].name]; + return status == 'success' + ? Theme.of(context).colorScheme.onSurface + : Color.lerp( + Theme.of(context).colorScheme.secondary, + status == 'pending' + ? Colors.blueGrey + : status == 'noresult' + ? Colors.orange + : Colors.red, + Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8, + )!; + })(), + tabs: pluginsController.pluginList + .asMap() + .entries + .map((entry) { + final plugin = entry.value; + final status = widget.infoController.pluginSearchStatus[plugin.name]; + return Tab( child: Text( plugin.name, overflow: TextOverflow.ellipsis, @@ -301,16 +301,16 @@ class _SourceSheetState extends State with SingleTickerProviderStat : Color.lerp( Theme.of(context).colorScheme.onSurface, status == 'pending' - ? Theme.of(context).colorScheme.onSurfaceVariant + ? Colors.blueGrey : status == 'noresult' ? Colors.orange : Colors.red, Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8,) ), - ), - ); - }).toList(), - ), + ), + ); + }).toList(), + ), ), ), // Fading background behind the expand button to increase contrast @@ -333,8 +333,8 @@ class _SourceSheetState extends State with SingleTickerProviderStat Theme.of(context).colorScheme.surface, ], stops: const [0.0, 0.3, 0.5, 1.0], - ), - ), + ), + ), ), ), ), @@ -343,17 +343,17 @@ class _SourceSheetState extends State with SingleTickerProviderStat top: 0, bottom: 0, child: IconButton( - onPressed: () { - expandedByClick = 1; - setState(() { - _showTabGrid = true; - }); - }, - icon: const Icon(Icons.keyboard_arrow_down), - tooltip: '展开', - ), + onPressed: () { + expandedByClick = 1; + setState(() { + _showTabGrid = true; + }); + }, + icon: const Icon(Icons.keyboard_arrow_down), + tooltip: '展开', + ), ), - ], + ], ), ), ), @@ -509,11 +509,11 @@ class _SourceSheetState extends State with SingleTickerProviderStat fontSize: 15, color: widget.tabController.index == originalIndex ? status == 'success' - ? Theme.of(context).colorScheme.onPrimary + ? Theme.of(context).colorScheme.surface : Color.lerp( - Theme.of(context).colorScheme.onPrimary, + Theme.of(context).colorScheme.surface, status == 'pending' - ? Theme.of(context).colorScheme.onSurfaceVariant + ? Colors.blueGrey : status =='noresult' ? Colors.orange : Colors.red, @@ -524,22 +524,22 @@ class _SourceSheetState extends State with SingleTickerProviderStat ), backgroundColor: widget.tabController.index == originalIndex ? status == 'success' - ? Theme.of(context).colorScheme.secondary + ? Theme.of(context).colorScheme.onSurface : Color.lerp( - Theme.of(context).colorScheme.secondary, + Theme.of(context).colorScheme.onSurface, status == 'pending' - ? Theme.of(context).colorScheme.onSurfaceVariant + ? Colors.blueGrey : status =='noresult' ? Colors.orange : Colors.red, - Theme.of(context).brightness == Brightness.dark ? 0.3 : 0.6, + Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8, ) : status == 'success' ? null : Color.lerp( null, status == 'pending' - ? Theme.of(context).colorScheme.onSurfaceVariant + ? Colors.blueGrey : status =='noresult' ? Colors.orange : Colors.red, @@ -554,7 +554,7 @@ class _SourceSheetState extends State with SingleTickerProviderStat : Color.lerp( Theme.of(context).colorScheme.outlineVariant, status == 'pending' - ? Theme.of(context).colorScheme.onSurfaceVariant + ? Colors.blueGrey : status == 'noresult' ? Colors.orange : Colors.red, From fc3eab1f35718dcfa8411196181637e851860362 Mon Sep 17 00:00:00 2001 From: YYZJ Date: Fri, 14 Nov 2025 13:47:15 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/source_sheet.dart | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index b14269878..8112bf087 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -455,27 +455,31 @@ class _SourceSheetState extends State with SingleTickerProviderStat return MenuAnchor( menuChildren: [ TextButton.icon( + style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface), onPressed: (){ queryManager?.querySource(keyword, plugin.name); }, - icon: const Icon(Icons.refresh), - label: const Text("重新检索") + icon: Icon(Icons.refresh), + label: Text("重新检索") ), TextButton.icon( + style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface), onPressed: () { showAliasSearchDialog(pluginsController.pluginList[widget.tabController.index].name); }, - icon: const Icon(Icons.saved_search_rounded), - label: const Text("别名检索"), + icon: Icon(Icons.saved_search_rounded), + label: Text("别名检索"), ), TextButton.icon( + style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface), onPressed: (){ showCustomSearchDialog(pluginsController.pluginList[widget.tabController.index].name); }, - icon: const Icon(Icons.search_rounded), - label: const Text("手动检索"), + icon: Icon(Icons.search_rounded), + label: Text("手动检索"), ), TextButton.icon( + style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface), onPressed: () { launchUrl( Uri.parse(pluginsController @@ -484,8 +488,16 @@ class _SourceSheetState extends State with SingleTickerProviderStat mode: LaunchMode.externalApplication, ); }, - icon: const Icon(Icons.open_in_browser_rounded), - label: const Text('打开网页'), + icon: Icon(Icons.open_in_browser_rounded), + label: Text('打开网页'), + ), + TextButton.icon( + style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface), + onPressed: () { + Modular.to.pushNamed('/settings/plugin/'); + }, + icon: Icon(Icons.extension), + label: Text('规则管理'), ), ], builder:(context, controller, child){ From 8714398669354dec1aa477b3c903334843ac0d83 Mon Sep 17 00:00:00 2001 From: YYZJ Date: Fri, 14 Nov 2025 15:31:23 +0800 Subject: [PATCH 10/14] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=EF=BC=8C=E5=AE=9E=E7=8E=B0=E9=95=BF=E6=8C=89?= =?UTF-8?q?=E7=95=AA=E6=BA=90=E8=BF=9B=E8=A1=8C=E6=8B=96=E5=8A=A8=E6=8E=92?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/source_sheet.dart | 302 ++++++++++++++++++------------- 1 file changed, 176 insertions(+), 126 deletions(-) diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index 8112bf087..6241867ce 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -61,6 +61,7 @@ class _SourceSheetState extends State with SingleTickerProviderStat Modular.get(); final CollectController collectController = Modular.get(); final PluginsController pluginsController = Modular.get(); + final Map _menuControllers = {}; late String keyword; /// Concurrent query manager @@ -452,136 +453,185 @@ class _SourceSheetState extends State with SingleTickerProviderStat final plugin = entry.value; final status = widget.infoController.pluginSearchStatus[plugin.name]; - return MenuAnchor( - menuChildren: [ - TextButton.icon( - style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface), - onPressed: (){ - queryManager?.querySource(keyword, plugin.name); - }, - icon: Icon(Icons.refresh), - label: Text("重新检索") - ), - TextButton.icon( - style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface), - onPressed: () { - showAliasSearchDialog(pluginsController.pluginList[widget.tabController.index].name); - }, - icon: Icon(Icons.saved_search_rounded), - label: Text("别名检索"), - ), - TextButton.icon( - style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface), - onPressed: (){ - showCustomSearchDialog(pluginsController.pluginList[widget.tabController.index].name); - }, - icon: Icon(Icons.search_rounded), - label: Text("手动检索"), - ), - TextButton.icon( - style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface), - onPressed: () { - launchUrl( - Uri.parse(pluginsController - .pluginList[widget.tabController.index].searchURL - .replaceFirst('@keyword', keyword)), - mode: LaunchMode.externalApplication, - ); - }, - icon: Icon(Icons.open_in_browser_rounded), - label: Text('打开网页'), - ), - TextButton.icon( - style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface), - onPressed: () { - Modular.to.pushNamed('/settings/plugin/'); - }, - icon: Icon(Icons.extension), - label: Text('规则管理'), - ), - ], - builder:(context, controller, child){ - return GestureDetector( - onSecondaryTap: () { - widget.tabController.index = originalIndex; - controller.open(); - }, - onLongPress: () { - widget.tabController.index = originalIndex; + return DragTarget( + onAcceptWithDetails: (details) async { + final fromIndex = details.data; + // 如果拖放到自身,则弹出菜单而不是进行排序 + if (fromIndex == originalIndex) { + widget.tabController.index = originalIndex; + final controller = _menuControllers[originalIndex]; + if (controller != null) { controller.open(); + return; + } + } + final targetIndex = originalIndex; + setState(() { + final item = pluginsController.pluginList.removeAt(fromIndex); + pluginsController.pluginList.insert(targetIndex, item); + // menu controllers keyed by indices may now be stale; clear so they'll be re-cached + _menuControllers.clear(); + }); + // Persist the new plugin order so it survives restarts + pluginsController.savePlugins(); + widget.tabController.index = targetIndex; + }, + builder: (context, candidateData, rejectedData) { + return MenuAnchor( + menuChildren: [ + MenuItemButton( + onPressed: () { + queryManager?.querySource(keyword, plugin.name); + }, + child: Row( + children: [ + Icon(Icons.refresh), + SizedBox(width: 8), + Text('重新检索'), + ], + ), + ), + MenuItemButton( + onPressed: () { + showAliasSearchDialog(pluginsController.pluginList[widget.tabController.index].name); + }, + child: Row( + children: [ + Icon(Icons.saved_search_rounded), + SizedBox(width: 8), + Text('别名检索'), + ], + ), + ), + MenuItemButton( + onPressed: () { + showCustomSearchDialog(pluginsController.pluginList[widget.tabController.index].name); + }, + child: Row( + children: [ + Icon(Icons.search_rounded), + SizedBox(width: 8), + Text('手动检索'), + ], + ), + ), + MenuItemButton( + onPressed: () { + launchUrl( + Uri.parse(pluginsController.pluginList[widget.tabController.index].searchURL.replaceFirst('@keyword', keyword)), + mode: LaunchMode.externalApplication, + ); + }, + child: Row( + children: [ + Icon(Icons.open_in_browser_rounded), + SizedBox(width: 8), + Text('打开网页'), + ], + ), + ), + MenuItemButton( + onPressed: () { + Modular.to.pushNamed('/settings/plugin/'); + }, + child: Row( + children: [ + Icon(Icons.extension), + SizedBox(width: 8), + Text('规则管理'), + ], + ), + ), + ], + builder: (context, controller, child) { + // cache controller so we can open the same menu on drop + _menuControllers[originalIndex] = controller; + return GestureDetector( + onSecondaryTap: () { + widget.tabController.index = originalIndex; + controller.open(); + }, + onLongPress: () { + widget.tabController.index = originalIndex; + controller.open(); + }, + child: child, + ); }, - child: child, + child: LongPressDraggable( + data: originalIndex, + feedback: Material( + color: Colors.transparent, + child: ActionChip( + label: Text( + plugin.name, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 15), + ), + ), + ), + childWhenDragging: Opacity( + opacity: 0.4, + child: ActionChip( + label: Text( + plugin.name, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 15), + ), + ), + ), + child: ActionChip( + label: Text( + plugin.name, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15, + color: widget.tabController.index == originalIndex + ? status == 'success' + ? Theme.of(context).colorScheme.surface + : Color.lerp( + Theme.of(context).colorScheme.surface, + status == 'pending' ? Colors.blueGrey : status == 'noresult' ? Colors.orange : Colors.red, + 0.15, + ) + : null, + ), + ), + backgroundColor: widget.tabController.index == originalIndex + ? status == 'success' + ? Theme.of(context).colorScheme.onSurface + : Color.lerp( + Theme.of(context).colorScheme.onSurface, + status == 'pending' ? Colors.blueGrey : status == 'noresult' ? Colors.orange : Colors.red, + Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8, + ) + : status == 'success' + ? null + : Color.lerp( + null, + status == 'pending' ? Colors.blueGrey : status == 'noresult' ? Colors.orange : Colors.red, + 0.075, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(9), + side: BorderSide( + color: status == 'success' + ? Color.lerp(Theme.of(context).colorScheme.outlineVariant, Theme.of(context).colorScheme.secondary, 0.15)! + : Color.lerp( + Theme.of(context).colorScheme.outlineVariant, + status == 'pending' ? Colors.blueGrey : status == 'noresult' ? Colors.orange : Colors.red, + 0.15, + )!, + ), + ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onPressed: () { + widget.tabController.index = originalIndex; + }, + ), + ), ); }, - child: ActionChip( - label: Text( - plugin.name, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 15, - color: widget.tabController.index == originalIndex - ? status == 'success' - ? Theme.of(context).colorScheme.surface - : Color.lerp( - Theme.of(context).colorScheme.surface, - status == 'pending' - ? Colors.blueGrey - : status =='noresult' - ? Colors.orange - : Colors.red, - 0.15, - ) - : null, - ), - ), - backgroundColor: widget.tabController.index == originalIndex - ? status == 'success' - ? Theme.of(context).colorScheme.onSurface - : Color.lerp( - Theme.of(context).colorScheme.onSurface, - status == 'pending' - ? Colors.blueGrey - : status =='noresult' - ? Colors.orange - : Colors.red, - Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8, - ) - : status == 'success' - ? null - : Color.lerp( - null, - status == 'pending' - ? Colors.blueGrey - : status =='noresult' - ? Colors.orange - : Colors.red, - 0.075, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(9), - side: BorderSide( - color: status == 'success' - ? Color.lerp(Theme.of(context).colorScheme.outlineVariant,Theme.of(context).colorScheme.secondary,0.15)! - // ? Theme.of(context).colorScheme.outlineVariant - : Color.lerp( - Theme.of(context).colorScheme.outlineVariant, - status == 'pending' - ? Colors.blueGrey - : status == 'noresult' - ? Colors.orange - : Colors.red, - 0.15 - )!, - ), - ), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onPressed: () { - widget.tabController.index = originalIndex; - // if (!expandedByScroll) { - // _showTabGrid = false; - // } - }, - ), ); }).toList(), ); From c1fb883eae859aaf0dd32ca256c31c472850ef50 Mon Sep 17 00:00:00 2001 From: YYZJ Date: Fri, 14 Nov 2025 16:06:18 +0800 Subject: [PATCH 11/14] =?UTF-8?q?=E4=B8=BA=E6=9C=80=E5=90=8E=E4=B8=80?= =?UTF-8?q?=E4=B8=AATab=E6=B7=BB=E5=8A=A0=E5=8F=B3=E8=BE=B9=E8=B7=9D?= =?UTF-8?q?=E4=BB=A5=E9=98=B2=E6=AD=A2=E8=A2=AB=E6=8C=89=E9=92=AE=E6=8C=A1?= =?UTF-8?q?=E4=BD=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/source_sheet.dart | 43 ++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index 6241867ce..d7cf89c01 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -285,28 +285,33 @@ class _SourceSheetState extends State with SingleTickerProviderStat .asMap() .entries .map((entry) { + final index = entry.key; final plugin = entry.value; final status = widget.infoController.pluginSearchStatus[plugin.name]; + final isLast = index == pluginsController.pluginList.length - 1; return Tab( - child: Text( - plugin.name, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .titleMedium! - .fontSize, - fontWeight: FontWeight.bold, - color: status == 'success' - ? Theme.of(context).colorScheme.onSurface - : Color.lerp( - Theme.of(context).colorScheme.onSurface, - status == 'pending' - ? Colors.blueGrey - : status == 'noresult' - ? Colors.orange - : Colors.red, - Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8,) + child: Padding( + padding: isLast ? const EdgeInsets.only(right: 40) : EdgeInsets.zero, + child: Text( + plugin.name, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .titleMedium! + .fontSize, + fontWeight: FontWeight.bold, + color: status == 'success' + ? Theme.of(context).colorScheme.onSurface + : Color.lerp( + Theme.of(context).colorScheme.onSurface, + status == 'pending' + ? Colors.blueGrey + : status == 'noresult' + ? Colors.orange + : Colors.red, + Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8,) + ), ), ), ); From 103c022e8f100a08ebb641d17ba521671147360e Mon Sep 17 00:00:00 2001 From: YYZJ Date: Fri, 14 Nov 2025 17:54:35 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E7=AB=AF=E9=97=B4=E8=B7=9D=E6=9C=AA=E5=BA=94=E7=94=A8=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/source_sheet.dart | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index d7cf89c01..78add5db6 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -556,10 +556,6 @@ class _SourceSheetState extends State with SingleTickerProviderStat widget.tabController.index = originalIndex; controller.open(); }, - onLongPress: () { - widget.tabController.index = originalIndex; - controller.open(); - }, child: child, ); }, @@ -571,8 +567,33 @@ class _SourceSheetState extends State with SingleTickerProviderStat label: Text( plugin.name, overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 15), + style: TextStyle( + fontSize: 15, + color: status == 'success' + ? Theme.of(context).colorScheme.onSurface + : Color.lerp( + Theme.of(context).colorScheme.onSurface, + status == 'pending' + ? Colors.blueGrey + : status == 'noresult' + ? Colors.orange + : Colors.red, + Theme.of(context).brightness == Brightness.dark ? 0.5 : 0.8,) + ), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(9), + side: BorderSide( + color: status == 'success' + ? Color.lerp(Theme.of(context).colorScheme.outlineVariant, Theme.of(context).colorScheme.secondary, 0.15)! + : Color.lerp( + Theme.of(context).colorScheme.outlineVariant, + status == 'pending' ? Colors.blueGrey : status == 'noresult' ? Colors.orange : Colors.red, + 0.15, + )!, + ), ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), ), childWhenDragging: Opacity( @@ -583,6 +604,7 @@ class _SourceSheetState extends State with SingleTickerProviderStat overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 15), ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), ), child: ActionChip( From 2e8ee5e0424fae7ac8ee3e198498ca87fbd29f7d Mon Sep 17 00:00:00 2001 From: YYZJ Date: Sat, 15 Nov 2025 22:43:01 +0800 Subject: [PATCH 13/14] =?UTF-8?q?=E9=81=BF=E5=85=8D=E9=BC=A0=E6=A0=87?= =?UTF-8?q?=E6=BB=9A=E8=BD=AE=E9=80=A0=E6=88=90=E6=8A=96=E5=8A=A8=E3=80=81?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A7=A6=E9=A1=B6=E5=B1=95=E5=BC=80=E8=A1=8C?= =?UTF-8?q?=E4=B8=BA=E3=80=81=E6=B7=BB=E5=8A=A0=E9=94=AE=E7=9B=98=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE=EF=BC=8C=E5=85=81=E8=AE=B8=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=AE=BE=E5=AE=9A=E6=98=AF=E5=90=A6=E9=BB=98=E8=AE=A4=E5=B1=95?= =?UTF-8?q?=E5=BC=80=E7=95=AA=E6=BA=90=E9=80=89=E6=8B=A9=E5=99=A8=E3=80=81?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E9=94=81=E5=AE=9A=E3=80=81=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E7=AD=9B=E9=80=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/info_page.dart | 2 +- lib/pages/info/source_sheet.dart | 166 +++++++++++++++----- lib/pages/settings/theme_settings_page.dart | 60 ++++++- lib/utils/storage.dart | 3 + 4 files changed, 184 insertions(+), 47 deletions(-) diff --git a/lib/pages/info/info_page.dart b/lib/pages/info/info_page.dart index d4d53f88c..9c1a39460 100644 --- a/lib/pages/info/info_page.dart +++ b/lib/pages/info/info_page.dart @@ -377,7 +377,7 @@ class _InfoPageState extends State with TickerProviderStateMixin { tabController: sourceTabController, infoController: infoController, scrollController: scrollController, - tabGridHeight: minChildSize*0.7, + tabGridHeight: minChildSize*0.8, ); }, ); diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index 78add5db6..b3681ce22 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'dart:async'; import 'package:kazumi/utils/utils.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -13,6 +14,8 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:kazumi/request/query_manager.dart'; import 'package:kazumi/pages/collect/collect_controller.dart'; import 'package:kazumi/bean/widget/error_widget.dart'; +import 'package:kazumi/utils/storage.dart'; +import 'package:flutter/services.dart'; class SourceSheet extends StatefulWidget { const SourceSheet({ @@ -33,30 +36,45 @@ class SourceSheet extends StatefulWidget { } class _SourceSheetState extends State with SingleTickerProviderStateMixin { - bool expandedByScroll = false; //通过滚动展开 - var expandedByClick = 0; //通过点击展开 bool _showOnlySuccess = false; final tabBarHeight = 48.0; + bool expandedByDrag = false; + final defaultShowSelector = GStorage.setting.get(SettingBoxKey.defaultShowSelector, defaultValue: false); + final autoLock = GStorage.setting.get(SettingBoxKey.autoLockSourceSheet, defaultValue: true); + final autoShowSuccess = GStorage.setting.get(SettingBoxKey.autoshowSuccessed, defaultValue: false); void _maybeExpandTabGridOnListViewHeight(BoxConstraints constraints) { final screenHeight = MediaQuery.of(context).size.height; - if (constraints.maxHeight >= screenHeight - tabBarHeight - 1 - 48 && !_showTabGrid && !expandedByScroll && expandedByClick !=2) { //48为小白条高度 手动点击按钮后不响应弹出 - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - _showTabGrid = true; - expandedByScroll = true; + final dragHandleHeight = 48.0; + final bufferHeight = 40; //40像素作为下拉时的缓冲高度,防止误触回弹 + final hideHeight = screenHeight - tabBarHeight - dragHandleHeight - 1; + final showHeight = screenHeight * ( 1 - widget.tabGridHeight ) - tabBarHeight - dragHandleHeight - 1; + if (constraints.maxHeight >= hideHeight && !_showTabGrid) { + if (!_isLocked && !expandedByDrag) { //当面板触顶且未锁定、未通过拖拽展开时自动展开面板。防止点击番源按钮收起面板后立刻展开面板。 + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _showTabGrid = true; + if(autoShowSuccess){_showOnlySuccess = true;} + }); }); - }); - } else if (constraints.maxHeight < screenHeight * ( 1 - widget.tabGridHeight ) - tabBarHeight - 1 - 48 - 40 && _showTabGrid &&expandedByScroll && expandedByClick !=1) { - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - _showTabGrid = false; - expandedByScroll = false; + expandedByDrag = true; + } + } else if (constraints.maxHeight < showHeight - bufferHeight) { + if (!_isLocked && expandedByDrag) { //禁用点击按钮且解锁时面板高度触发的自动收起,防止用户点击解锁按钮后就立刻收起。 + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _showTabGrid = false; + }); }); - }); + } + if (!_showTabGrid){ + expandedByDrag = false; //重置拖拽展开状态,允许后续触顶时展开面板, + } } } final ScrollController _tabGridScrollController = ScrollController(); bool _showTabGrid = false; + Timer? _scrollWaitTimer; + bool _isLocked = false; final VideoPageController videoPageController = Modular.get(); final CollectController collectController = Modular.get(); @@ -75,6 +93,11 @@ class _SourceSheetState extends State with SingleTickerProviderStat queryManager = QueryManager(infoController: widget.infoController); queryManager?.queryAllSource(keyword); super.initState(); + if (defaultShowSelector == true) { + _showTabGrid = true; + if(autoShowSuccess){_showOnlySuccess = true;} + if(autoLock){ _isLocked = true; } + } widget.tabController.addListener(() { if (!mounted) return; setState(() {}); @@ -84,6 +107,7 @@ class _SourceSheetState extends State with SingleTickerProviderStat @override void dispose() { _tabGridScrollController.dispose(); + _scrollWaitTimer?.cancel(); queryManager?.cancel(); queryManager = null; super.dispose(); @@ -223,27 +247,34 @@ class _SourceSheetState extends State with SingleTickerProviderStat children: [ Listener( onPointerSignal: (event) { - if (event is PointerScrollEvent) { - if (!_showTabGrid && (event.scrollDelta.dy.abs() > 8)) { - setState(() { - _showTabGrid = true; - }); - } else if (_showTabGrid && (event.scrollDelta.dy.abs() > 8)) { - // 仅当面板内部无需滚动时才允许收起 - final maxScroll = _tabGridScrollController.hasClients - ? _tabGridScrollController.position.maxScrollExtent - : 0.0; - if (maxScroll == 0.0) { + if (event is PointerScrollEvent) { + if (event.scrollDelta.dy.abs() < 8) return; + if (_scrollWaitTimer?.isActive ?? false) return; + _scrollWaitTimer?.cancel(); + _scrollWaitTimer = Timer(Duration(milliseconds: 750), () {}); //防抖 + + if (!_showTabGrid) { setState(() { - _showTabGrid = false; + _showTabGrid = true; + if(autoShowSuccess){_showOnlySuccess = true;} + }); + } else { + // 仅当面板内部无需滚动时才允许收起 + final maxScroll = _tabGridScrollController.hasClients + ? _tabGridScrollController.position.maxScrollExtent + : 0.0; + if (maxScroll == 0.0) { + setState(() { + _showTabGrid = false; + }); + } } } - } - }, + }, child: AnimatedContainer( duration: const Duration(milliseconds: 250), - height: _showTabGrid ? MediaQuery.of(context).size.height * widget.tabGridHeight + tabBarHeight - 12 : tabBarHeight, + height: _showTabGrid ? MediaQuery.of(context).size.height * widget.tabGridHeight : tabBarHeight, child: Stack( children: [ // TabBar(收起时显示) @@ -350,9 +381,10 @@ class _SourceSheetState extends State with SingleTickerProviderStat bottom: 0, child: IconButton( onPressed: () { - expandedByClick = 1; setState(() { _showTabGrid = true; + if(autoLock){ _isLocked = true;} + if(autoShowSuccess){_showOnlySuccess = true;} }); }, icon: const Icon(Icons.keyboard_arrow_down), @@ -413,7 +445,16 @@ class _SourceSheetState extends State with SingleTickerProviderStat ), IconButton( onPressed: () { - expandedByClick = 2; + setState(() { + expandedByDrag = false; //避免触顶展开>锁上>拉回>解锁时自动收起 + _isLocked = !_isLocked; + }); + }, + icon: Icon(_isLocked ? Icons.lock : Icons.lock_open), + tooltip: '锁定/解锁', + ), + IconButton( + onPressed: () { setState(() { _showTabGrid = false; });}, @@ -654,6 +695,11 @@ class _SourceSheetState extends State with SingleTickerProviderStat materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, onPressed: () { widget.tabController.index = originalIndex; + if (!_isLocked) { + setState(() { + _showTabGrid = false; + }); + } }, ), ), @@ -684,12 +730,52 @@ class _SourceSheetState extends State with SingleTickerProviderStat builder: (context, constraints) { _maybeExpandTabGridOnListViewHeight(constraints); return Observer( - builder: (context) => TabBarView( - controller: widget.tabController, - children: List.generate(pluginsController.pluginList.length, - (pluginIndex) { - var plugin = pluginsController.pluginList[pluginIndex]; - var cardList = []; + builder: (context) { + return Focus( + autofocus: true, + onKeyEvent: (FocusNode node, KeyEvent event) { + if (event is KeyDownEvent || event is KeyRepeatEvent) { + if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { + final cur = widget.tabController.index; + if (cur > 0) { + widget.tabController.animateTo(cur - 1); + } + return KeyEventResult.handled; + } + if (event.logicalKey == LogicalKeyboardKey.arrowRight) { + final cur = widget.tabController.index; + if (cur < widget.tabController.length - 1) { + widget.tabController.animateTo(cur + 1); + } + return KeyEventResult.handled; + } + } + if (event is KeyDownEvent) { + if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + if(!_showTabGrid){ + setState(() { + _showTabGrid = true; + if(autoLock){ _isLocked = true;} + if(autoShowSuccess){_showOnlySuccess = true;} + }); + } + return KeyEventResult.handled; + } + if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + setState(() { + _showTabGrid = false; + }); + return KeyEventResult.handled; + } + } + return KeyEventResult.ignored; + }, + child: TabBarView( + controller: widget.tabController, + children: List.generate(pluginsController.pluginList.length, + (pluginIndex) { + var plugin = pluginsController.pluginList[pluginIndex]; + var cardList = []; for (var searchResponse in widget.infoController.pluginSearchResponseList) { if (searchResponse.pluginName == plugin.name) { @@ -810,8 +896,10 @@ class _SourceSheetState extends State with SingleTickerProviderStat ) ) ); - }), - ), + }), // end List.generate + ), // end TabBarView + ); // end Focus (returned from builder) + }, // end Observer builder ); }, ), diff --git a/lib/pages/settings/theme_settings_page.dart b/lib/pages/settings/theme_settings_page.dart index 17c01bc6f..3f570df74 100644 --- a/lib/pages/settings/theme_settings_page.dart +++ b/lib/pages/settings/theme_settings_page.dart @@ -28,6 +28,9 @@ class _ThemeSettingsPageState extends State { late dynamic defaultThemeColor; late bool oledEnhance; late bool useDynamicColor; + late bool defaultShowSelector; + late bool autoLockSourceSheet; + late bool autoshowSuccessed; late bool showWindowButton; final PopularController popularController = Modular.get(); late final ThemeProvider themeProvider; @@ -41,6 +44,9 @@ class _ThemeSettingsPageState extends State { defaultThemeColor = setting.get(SettingBoxKey.themeColor, defaultValue: 'default'); oledEnhance = setting.get(SettingBoxKey.oledEnhance, defaultValue: false); + defaultShowSelector = setting.get(SettingBoxKey.defaultShowSelector, defaultValue: false); + autoLockSourceSheet = setting.get(SettingBoxKey.autoLockSourceSheet, defaultValue: true); + autoshowSuccessed = setting.get(SettingBoxKey.autoshowSuccessed, defaultValue: false); useDynamicColor = setting.get(SettingBoxKey.useDynamicColor, defaultValue: false); showWindowButton = @@ -256,6 +262,17 @@ class _ThemeSettingsPageState extends State { ], ), ), + SettingsTile.switchTile( + onToggle: (value) async { + oledEnhance = value ?? !oledEnhance; + await setting.put(SettingBoxKey.oledEnhance, oledEnhance); + updateOledEnhance(); + setState(() {}); + }, + title: const Text('OLED优化'), + description: const Text('深色模式下使用纯黑背景'), + initialValue: oledEnhance, + ), SettingsTile.navigation( enabled: !useDynamicColor, onPressed: (_) async { @@ -316,24 +333,53 @@ class _ThemeSettingsPageState extends State { setState(() {}); }, title: const Text('动态配色'), + description: const Text('仅支持安卓12及以上和桌面平台'), initialValue: useDynamicColor, ), ], - bottomInfo: const Text('动态配色仅支持安卓12及以上和桌面平台'), ), SettingsSection( + title: const Text('番源选择器'), tiles: [ SettingsTile.switchTile( onToggle: (value) async { - oledEnhance = value ?? !oledEnhance; - await setting.put(SettingBoxKey.oledEnhance, oledEnhance); - updateOledEnhance(); + defaultShowSelector = value ?? !defaultShowSelector; + if (defaultShowSelector) { + autoLockSourceSheet = true; + await setting.put(SettingBoxKey.autoLockSourceSheet, autoLockSourceSheet); + } + await setting.put(SettingBoxKey.defaultShowSelector, defaultShowSelector); setState(() {}); }, - title: const Text('OLED优化'), - description: const Text('深色模式下使用纯黑背景'), - initialValue: oledEnhance, + title: const Text('默认展开'), + initialValue: defaultShowSelector, + ), + SettingsTile.switchTile( + enabled: !defaultShowSelector, + onToggle: (value) async { + autoLockSourceSheet = value ?? !autoLockSourceSheet; + await setting.put(SettingBoxKey.autoLockSourceSheet, autoLockSourceSheet); + setState(() {}); + }, + title: const Text('自动锁定'), + description: const Text('点击按钮展开时自动锁定面板'), + initialValue: autoLockSourceSheet, ), + SettingsTile.switchTile( + onToggle: (value) async { + autoshowSuccessed = value ?? !autoshowSuccessed; + await setting.put(SettingBoxKey.autoshowSuccessed, autoshowSuccessed); + setState(() {}); + }, + title: const Text('自动筛选'), + description: const Text('自动筛选有结果项'), + initialValue: autoshowSuccessed, + ), + ], + ), + SettingsSection( + tiles: [ + ], ), if (Utils.isDesktop()) diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 1ecb51ee7..6f4a1c230 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -238,6 +238,9 @@ class SettingBoxKey { playResume = 'playResume', showPlayerError = 'showPlayerError', oledEnhance = 'oledEnhance', + defaultShowSelector = 'defaultShowSelector', + autoLockSourceSheet = 'autoLockSourceSheet', + autoshowSuccessed = 'autoshowSuccessed', displayMode = 'displayMode', enableGitProxy = 'enableGitProxy', enableSystemProxy = 'enableSystemProxy', From cf1d54467942fbced2cfe26e6fd0cbec48a9b4b4 Mon Sep 17 00:00:00 2001 From: YYZJ Date: Sat, 15 Nov 2025 23:17:50 +0800 Subject: [PATCH 14/14] =?UTF-8?q?=E5=B0=86=E5=B1=95=E5=BC=80/=E6=94=B6?= =?UTF-8?q?=E8=B5=B7=E7=9A=84=E5=BF=AB=E6=8D=B7=E9=94=AE=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E4=B8=BA/=EF=BC=8C=E9=81=BF=E5=85=8D=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E4=B8=8A=E4=B8=8B=E9=94=AE=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=BB=93=E6=9E=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/info/source_sheet.dart | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/lib/pages/info/source_sheet.dart b/lib/pages/info/source_sheet.dart index b3681ce22..2c5fdaf4b 100644 --- a/lib/pages/info/source_sheet.dart +++ b/lib/pages/info/source_sheet.dart @@ -437,21 +437,21 @@ class _SourceSheetState extends State with SingleTickerProviderStat IconButton( onPressed: () { setState(() { - _showOnlySuccess = !_showOnlySuccess; + expandedByDrag = false; //避免触顶展开>锁上>拉回>解锁时自动收起 + _isLocked = !_isLocked; }); }, - icon: Icon(_showOnlySuccess ? Icons.filter_alt : Icons.filter_alt_outlined,), - tooltip: '筛选有结果项', + icon: Icon(_isLocked ? Icons.lock : Icons.lock_open), + tooltip: '锁定/解锁', ), IconButton( onPressed: () { setState(() { - expandedByDrag = false; //避免触顶展开>锁上>拉回>解锁时自动收起 - _isLocked = !_isLocked; + _showOnlySuccess = !_showOnlySuccess; }); }, - icon: Icon(_isLocked ? Icons.lock : Icons.lock_open), - tooltip: '锁定/解锁', + icon: Icon(_showOnlySuccess ? Icons.filter_alt : Icons.filter_alt_outlined,), + tooltip: '筛选有结果项', ), IconButton( onPressed: () { @@ -751,20 +751,14 @@ class _SourceSheetState extends State with SingleTickerProviderStat } } if (event is KeyDownEvent) { - if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + if (event.logicalKey == LogicalKeyboardKey.slash) { if(!_showTabGrid){ setState(() { - _showTabGrid = true; if(autoLock){ _isLocked = true;} if(autoShowSuccess){_showOnlySuccess = true;} }); } - return KeyEventResult.handled; - } - if (event.logicalKey == LogicalKeyboardKey.arrowUp) { - setState(() { - _showTabGrid = false; - }); + setState(() {_showTabGrid = !_showTabGrid;}); return KeyEventResult.handled; } }