diff --git a/lib/pages/common/common_page.dart b/lib/pages/common/common_page.dart index 46f5f012c..40cae3e49 100644 --- a/lib/pages/common/common_page.dart +++ b/lib/pages/common/common_page.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/pages/home/controller.dart'; import 'package:PiliPlus/pages/main/controller.dart'; +import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; @@ -16,6 +17,11 @@ abstract class CommonPageState R get controller; StreamController? mainStream; StreamController? searchBarStream; + // late double _downScrollCount = 0.0; // 向下滚动计数器 + late double _upScrollCount = 0.0; // 向上滚动计数器 + double? _lastScrollPosition; // 记录上次滚动位置 + final _enableScrollThreshold = Pref.enableScrollThreshold; + late final double _scrollThreshold = Pref.scrollThreshold; // 滚动阈值 @override void initState() { @@ -30,15 +36,58 @@ abstract class CommonPageState } void listener() { - final ScrollDirection direction = - controller.scrollController.position.userScrollDirection; - if (direction == ScrollDirection.forward) { - mainStream?.add(true); - searchBarStream?.add(true); - } else if (direction == ScrollDirection.reverse) { - mainStream?.add(false); - searchBarStream?.add(false); + final scrollController = controller.scrollController; + final direction = scrollController.position.userScrollDirection; + + if (!_enableScrollThreshold) { + if (direction == ScrollDirection.forward) { + mainStream?.add(true); + searchBarStream?.add(true); + } else if (direction == ScrollDirection.reverse) { + mainStream?.add(false); + searchBarStream?.add(false); + } + return; } + + final double currentPosition = scrollController.position.pixels; + + // 初始化上次位置 + _lastScrollPosition ??= currentPosition; + + // 计算滚动距离 + final double scrollDelta = currentPosition - _lastScrollPosition!; + + if (direction == ScrollDirection.reverse) { + mainStream?.add(false); + searchBarStream?.add(false); // // 向下滚动,累加向下滚动距离,重置向上滚动计数器 + _upScrollCount = 0.0; // 重置向上滚动计数器 + // if (scrollDelta > 0) { + // _downScrollCount += scrollDelta; + // // _upScrollCount = 0.0; // 重置向上滚动计数器 + + // // 当累计向下滚动距离超过阈值时,隐藏顶底栏 + // if (_downScrollCount >= _scrollThreshold) { + // mainStream?.add(false); + // searchBarStream?.add(false); + // } + // } + } else if (direction == ScrollDirection.forward) { + // 向上滚动,累加向上滚动距离,重置向下滚动计数器 + if (scrollDelta < 0) { + _upScrollCount += (-scrollDelta); // 使用绝对值 + // _downScrollCount = 0.0; // 重置向下滚动计数器 + + // 当累计向上滚动距离超过阈值时,显示顶底栏 + if (_upScrollCount >= _scrollThreshold) { + mainStream?.add(true); + searchBarStream?.add(true); + } + } + } + + // 更新上次位置 + _lastScrollPosition = currentPosition; } @override diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index ea59cf93c..7ad23d3da 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -10,7 +10,6 @@ import 'package:PiliPlus/utils/feed_back.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:stream_transform/stream_transform.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -149,14 +148,11 @@ class _HomePageState extends State } Widget customAppBar(ThemeData theme) { + if (!_homeController.hideSearchBar) { + return searchBarAndUser(theme); + } return StreamBuilder( - stream: _homeController.hideSearchBar - ? _mainController.navSearchStreamDebounce - ? _homeController.searchBarStream?.stream.distinct().throttle( - const Duration(milliseconds: 500), - ) - : _homeController.searchBarStream?.stream.distinct() - : null, + stream: _homeController.searchBarStream?.stream.distinct(), initialData: true, builder: (BuildContext context, AsyncSnapshot snapshot) { return AnimatedOpacity( diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index c08818c55..6d76e0a04 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -52,7 +52,6 @@ class MainController extends GetxController final enableMYBar = Pref.enableMYBar; final useSideBar = Pref.useSideBar; final mainTabBarView = Pref.mainTabBarView; - late bool navSearchStreamDebounce = Pref.navSearchStreamDebounce; late final optTabletNav = Pref.optTabletNav; late bool directExitOnBack = Pref.directExitOnBack; diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 469a783d3..891266130 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -16,7 +16,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:stream_transform/stream_transform.dart'; class MainApp extends StatefulWidget { const MainApp({super.key}); @@ -91,6 +90,52 @@ class _MainAppState extends State Widget build(BuildContext context) { final theme = Theme.of(context); final bool isPortrait = context.orientation == Orientation.portrait; + final useBottomNav = isPortrait && !_mainController.useSideBar; + Widget? bottomNav = useBottomNav + ? _mainController.navigationBars.length > 1 + ? _mainController.enableMYBar + ? Obx( + () => NavigationBar( + onDestinationSelected: _mainController.setIndex, + selectedIndex: _mainController.selectedIndex.value, + destinations: _mainController.navigationBars + .map( + (e) => NavigationDestination( + label: e.label, + icon: _buildIcon(type: e), + selectedIcon: _buildIcon( + type: e, + selected: true, + ), + ), + ) + .toList(), + ), + ) + : Obx( + () => BottomNavigationBar( + currentIndex: _mainController.selectedIndex.value, + onTap: _mainController.setIndex, + iconSize: 16, + selectedFontSize: 12, + unselectedFontSize: 12, + type: BottomNavigationBarType.fixed, + items: _mainController.navigationBars + .map( + (e) => BottomNavigationBarItem( + label: e.label, + icon: _buildIcon(type: e), + activeIcon: _buildIcon( + type: e, + selected: true, + ), + ), + ) + .toList(), + ), + ) + : const SizedBox.shrink() + : null; return PopScope( canPop: false, onPopInvokedWithResult: (bool didPop, Object? result) { @@ -120,7 +165,7 @@ class _MainAppState extends State child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (_mainController.useSideBar || !isPortrait) ...[ + if (!useBottomNav) ...[ _mainController.navigationBars.length > 1 ? context.isTablet && _mainController.optTabletNav ? Column( @@ -228,74 +273,23 @@ class _MainAppState extends State ], ), ), - bottomNavigationBar: _mainController.useSideBar || !isPortrait - ? null - : StreamBuilder( - stream: _mainController.hideTabBar - ? _mainController.navSearchStreamDebounce - ? _mainController.bottomBarStream?.stream - .distinct() - .throttle(const Duration(milliseconds: 500)) - : _mainController.bottomBarStream?.stream.distinct() - : null, - initialData: true, - builder: (context, AsyncSnapshot snapshot) { - return AnimatedSlide( - curve: Curves.easeInOutCubicEmphasized, - duration: const Duration(milliseconds: 500), - offset: Offset(0, snapshot.data ? 0 : 1), - child: _mainController.enableMYBar - ? _mainController.navigationBars.length > 1 - ? Obx( - () => NavigationBar( - onDestinationSelected: - _mainController.setIndex, - selectedIndex: - _mainController.selectedIndex.value, - destinations: _mainController - .navigationBars - .map( - (e) => NavigationDestination( - label: e.label, - icon: _buildIcon(type: e), - selectedIcon: _buildIcon( - type: e, - selected: true, - ), - ), - ) - .toList(), - ), - ) - : const SizedBox.shrink() - : _mainController.navigationBars.length > 1 - ? Obx( - () => BottomNavigationBar( - currentIndex: - _mainController.selectedIndex.value, - onTap: _mainController.setIndex, - iconSize: 16, - selectedFontSize: 12, - unselectedFontSize: 12, - type: BottomNavigationBarType.fixed, - items: _mainController.navigationBars - .map( - (e) => BottomNavigationBarItem( - label: e.label, - icon: _buildIcon(type: e), - activeIcon: _buildIcon( - type: e, - selected: true, - ), - ), - ) - .toList(), - ), - ) - : const SizedBox.shrink(), - ); - }, - ), + bottomNavigationBar: useBottomNav + ? _mainController.hideTabBar + ? StreamBuilder( + stream: _mainController.bottomBarStream?.stream + .distinct(), + initialData: true, + builder: (context, AsyncSnapshot snapshot) { + return AnimatedSlide( + curve: Curves.easeInOutCubicEmphasized, + duration: const Duration(milliseconds: 500), + offset: Offset(0, snapshot.data ? 0 : 1), + child: bottomNav, + ); + }, + ) + : bottomNav + : null, ), ), ); diff --git a/lib/pages/setting/models/style_settings.dart b/lib/pages/setting/models/style_settings.dart index 19faf4633..e8a7c4808 100644 --- a/lib/pages/setting/models/style_settings.dart +++ b/lib/pages/setting/models/style_settings.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:math'; import 'package:PiliPlus/common/widgets/custom_toast.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; @@ -343,15 +344,61 @@ List get styleSettings => [ ), SettingsModel( settingsType: SettingsType.sw1tch, - title: '降低收起/展开顶/底栏频率', - leading: const Icon(Icons.vertical_distribute), - setKey: SettingBoxKey.navSearchStreamDebounce, - defaultVal: false, - onChanged: (value) { - try { - Get.find().navSearchStreamDebounce = value; - Get.forceAppUpdate(); - } catch (_) {} + title: '顶/底栏滚动阈值', + subtitle: '滚动多少像素后收起/展开顶底栏,默认50像素', + leading: const Icon(Icons.swipe_vertical), + defaultVal: true, + setKey: SettingBoxKey.enableScrollThreshold, + needReboot: true, + onTap: () { + String scrollThreshold = Pref.scrollThreshold.toString(); + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('滚动阈值'), + content: TextFormField( + autofocus: true, + initialValue: scrollThreshold, + keyboardType: const TextInputType.numberWithOptions( + decimal: true, + ), + onChanged: (value) { + scrollThreshold = value; + }, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')), + ], + decoration: const InputDecoration(suffixText: 'px'), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () { + Get.back(); + GStorage.setting.put( + SettingBoxKey.scrollThreshold, + max( + 10.0, + double.tryParse(scrollThreshold) ?? 50.0, + ), + ); + SmartDialog.showToast('重启生效'); + }, + child: const Text('确定'), + ), + ], + ); + }, + ); }, ), SettingsModel( diff --git a/lib/pages/setting/widgets/switch_item.dart b/lib/pages/setting/widgets/switch_item.dart index 4d383dc18..acc0eb1b6 100644 --- a/lib/pages/setting/widgets/switch_item.dart +++ b/lib/pages/setting/widgets/switch_item.dart @@ -131,9 +131,8 @@ class _SetSwitchItemState extends State { return ListTile( contentPadding: widget.contentPadding, enabled: widget.onTap != null ? val : true, - onTap: () => widget.onTap != null - ? widget.onTap?.call() - : switchChange(theme, null), + onTap: () => + widget.onTap != null ? widget.onTap!() : switchChange(theme, null), title: Text(widget.title!, style: titleStyle), subtitle: widget.subtitle != null ? Text(widget.subtitle!, style: subTitleStyle) diff --git a/lib/utils/storage_key.dart b/lib/utils/storage_key.dart index 0e935c16c..f7195881a 100644 --- a/lib/utils/storage_key.dart +++ b/lib/utils/storage_key.dart @@ -123,7 +123,6 @@ class SettingBoxKey { appFontWeight = 'appFontWeight', fastForBackwardDuration = 'fastForBackwardDuration', recordSearchHistory = 'recordSearchHistory', - navSearchStreamDebounce = 'navSearchStreamDebounce', showPgcTimeline = 'showPgcTimeline', pageTransition = 'pageTransition', optTabletNav = 'optTabletNav', @@ -194,6 +193,8 @@ class SettingBoxKey { enableMYBar = 'enableMYBar', hideSearchBar = 'hideSearchBar', hideTabBar = 'hideTabBar', + scrollThreshold = 'scrollThreshold', + enableScrollThreshold = 'enableScrollThreshold', tabBarSort = 'tabBarSort', dynamicBadgeMode = 'dynamicBadgeMode', msgBadgeMode = 'msgBadgeMode', diff --git a/lib/utils/storage_pref.dart b/lib/utils/storage_pref.dart index 66e25e44a..8a4d610ec 100644 --- a/lib/utils/storage_pref.dart +++ b/lib/utils/storage_pref.dart @@ -502,9 +502,6 @@ class Pref { static bool get recordSearchHistory => _setting.get(SettingBoxKey.recordSearchHistory, defaultValue: true); - static bool get navSearchStreamDebounce => - _setting.get(SettingBoxKey.navSearchStreamDebounce, defaultValue: false); - static String get webdavUri => _setting.get(SettingBoxKey.webdavUri, defaultValue: ''); @@ -607,6 +604,12 @@ class Pref { static bool get hideSearchBar => _setting.get(SettingBoxKey.hideSearchBar, defaultValue: true); + static bool get enableScrollThreshold => + _setting.get(SettingBoxKey.enableScrollThreshold, defaultValue: true); + + static double get scrollThreshold => + _setting.get(SettingBoxKey.scrollThreshold, defaultValue: 50.0); + static bool get enableSearchWord => _setting.get(SettingBoxKey.enableSearchWord, defaultValue: true);