diff --git a/lib/common/widgets/flutter/vertical_tabs.dart b/lib/common/widgets/flutter/vertical_tabs.dart index bcee9a96a..70ad39945 100644 --- a/lib/common/widgets/flutter/vertical_tabs.dart +++ b/lib/common/widgets/flutter/vertical_tabs.dart @@ -1653,7 +1653,11 @@ class _VerticalTabBarState extends State { tabCenter + paddingTop - viewportWidth / 2.0 + - (_mainCtr.useBottomNav && (_mainCtr.barOffset?.value ?? 0) == 0 + (_mainCtr.useBottomNav && + switch (_mainCtr.barHideType) { + .instant => _mainCtr.showBottomBar?.value ?? true, + .sync => (_mainCtr.barOffset?.value ?? 0) == 0, + } ? 80.0 : 0.0), minExtent, diff --git a/lib/models/common/bar_hide_type.dart b/lib/models/common/bar_hide_type.dart new file mode 100644 index 000000000..243083928 --- /dev/null +++ b/lib/models/common/bar_hide_type.dart @@ -0,0 +1,11 @@ +import 'package:PiliPlus/models/common/enum_with_label.dart'; + +enum BarHideType with EnumWithLabel { + instant('即时'), + sync('同步') + ; + + @override + final String label; + const BarHideType(this.label); +} diff --git a/lib/pages/common/common_page.dart b/lib/pages/common/common_page.dart index 8bc7269b7..ccc333005 100644 --- a/lib/pages/common/common_page.dart +++ b/lib/pages/common/common_page.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart' show StyleString; import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/home/controller.dart'; import 'package:PiliPlus/pages/main/controller.dart'; import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/material.dart'; @@ -11,25 +12,52 @@ abstract class CommonPageState< > extends State { R get controller; - final _mainController = Get.find(); RxDouble? _barOffset; + RxBool? _showTopBar; + RxBool? _showBottomBar; + final _mainController = Get.find(); @override void initState() { super.initState(); _barOffset = _mainController.barOffset; + _showBottomBar = _mainController.showBottomBar; + try { + _showTopBar = Get.find().showTopBar; + } catch (_) {} } Widget onBuild(Widget child) { if (_barOffset != null) { return NotificationListener( - onNotification: onNotification, + onNotification: onNotificationType2, + child: child, + ); + } + if (_showTopBar != null || _showBottomBar != null) { + return NotificationListener( + onNotification: onNotificationType1, child: child, ); } return child; } + bool onNotificationType1(UserScrollNotification notification) { + if (!_mainController.useBottomNav) return false; + if (notification.metrics.axis == .horizontal) return false; + switch (notification.direction) { + case .forward: + _showTopBar?.value = true; + _showBottomBar?.value = true; + case .reverse: + _showTopBar?.value = false; + _showBottomBar?.value = false; + case _: + } + return false; + } + void _updateOffset(double scrollDelta) { _barOffset!.value = clampDouble( _barOffset!.value + scrollDelta, @@ -38,7 +66,7 @@ abstract class CommonPageState< ); } - bool onNotification(ScrollNotification notification) { + bool onNotificationType2(ScrollNotification notification) { if (!_mainController.useBottomNav) return false; if (notification.metrics.axis == .horizontal) return false; diff --git a/lib/pages/dynamics_tab/view.dart b/lib/pages/dynamics_tab/view.dart index 89d1ce4e4..a398b24ad 100644 --- a/lib/pages/dynamics_tab/view.dart +++ b/lib/pages/dynamics_tab/view.dart @@ -46,11 +46,19 @@ class _DynamicsTabPageState _mainController.selectedIndex.value == 0; @override - bool onNotification(ScrollNotification notification) { + bool onNotificationType1(UserScrollNotification notification) { if (checkPage) { return false; } - return super.onNotification(notification); + return super.onNotificationType1(notification); + } + + @override + bool onNotificationType2(ScrollNotification notification) { + if (checkPage) { + return false; + } + return super.onNotificationType2(notification); } @override diff --git a/lib/pages/home/controller.dart b/lib/pages/home/controller.dart index 680667df2..e3ee49e6f 100644 --- a/lib/pages/home/controller.dart +++ b/lib/pages/home/controller.dart @@ -5,6 +5,7 @@ import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/models/common/home_tab_type.dart'; import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/main/controller.dart'; import 'package:PiliPlus/services/account_service.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; @@ -19,7 +20,8 @@ class HomeController extends GetxController late List tabs; late TabController tabController; - final bool hideTopBar = !Pref.useSideBar && Pref.hideTopBar; + RxBool? showTopBar; + late final bool hideTopBar; bool enableSearchWord = Pref.enableSearchWord; late final RxString defaultSearch = ''.obs; @@ -36,6 +38,17 @@ class HomeController extends GetxController void onInit() { super.onInit(); + hideTopBar = !Pref.useSideBar && Pref.hideTopBar; + if (hideTopBar) { + final mainCtr = Get.find(); + switch (mainCtr.barHideType) { + case .instant: + showTopBar = RxBool(true); + case .sync: + mainCtr.barOffset ??= RxDouble(0.0); + } + } + if (enableSearchWord) { lateCheckSearchAt = DateTime.now().millisecondsSinceEpoch; querySearchDefault(); diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 6fe9d8607..f821e4f5a 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -31,38 +31,46 @@ class _HomePageState extends State Widget build(BuildContext context) { super.build(context); final theme = Theme.of(context); + Widget tabBar; + if (_homeController.tabs.length > 1) { + tabBar = Padding( + padding: const EdgeInsets.only(top: 4), + child: SizedBox( + height: 42, + width: double.infinity, + child: TabBar( + controller: _homeController.tabController, + tabs: _homeController.tabs.map((e) => Tab(text: e.label)).toList(), + isScrollable: true, + dividerColor: Colors.transparent, + dividerHeight: 0, + splashBorderRadius: StyleString.mdRadius, + tabAlignment: TabAlignment.center, + onTap: (_) { + feedBack(); + if (!_homeController.tabController.indexIsChanging) { + _homeController.animateToTop(); + } + }, + ), + ), + ); + if (_homeController.hideTopBar && + _mainController.barHideType == .instant) { + tabBar = Material( + color: theme.colorScheme.surface, + child: tabBar, + ); + } + } else { + tabBar = const SizedBox(height: 6); + } return Column( children: [ if (!_mainController.useSideBar && MediaQuery.sizeOf(context).isPortrait) customAppBar(theme), - if (_homeController.tabs.length > 1) - Padding( - padding: const EdgeInsets.only(top: 4), - child: SizedBox( - height: 42, - width: double.infinity, - child: TabBar( - controller: _homeController.tabController, - tabs: _homeController.tabs - .map((e) => Tab(text: e.label)) - .toList(), - isScrollable: true, - dividerColor: Colors.transparent, - dividerHeight: 0, - splashBorderRadius: StyleString.mdRadius, - tabAlignment: TabAlignment.center, - onTap: (_) { - feedBack(); - if (!_homeController.tabController.indexIsChanging) { - _homeController.animateToTop(); - } - }, - ), - ), - ) - else - const SizedBox(height: 6), + tabBar, Expanded( child: tabBarView( controller: _homeController.tabController, @@ -85,26 +93,43 @@ class _HomePageState extends State ], ); if (_homeController.hideTopBar) { - return Obx( - () { - final barOffset = _mainController.barOffset!.value; - return CustomHeightWidget( - offset: Offset(0, -barOffset), - height: StyleString.topBarHeight - barOffset, - child: Padding( + if (_mainController.barOffset case final barOffset?) { + return Obx( + () { + final offset = barOffset.value; + return CustomHeightWidget( + offset: Offset(0, -offset), + height: StyleString.topBarHeight - offset, + child: Padding( + padding: padding, + child: child, + ), + ); + }, + ); + } + if (_homeController.showTopBar case final showTopBar?) { + return Obx(() { + final showSearchBar = showTopBar.value; + return AnimatedOpacity( + opacity: showSearchBar ? 1 : 0, + duration: const Duration(milliseconds: 300), + child: AnimatedContainer( + curve: Curves.easeInOutCubicEmphasized, + duration: const Duration(milliseconds: 500), + height: showSearchBar ? StyleString.topBarHeight : 0, padding: padding, child: child, ), ); - }, - ); - } else { - return Container( - height: StyleString.topBarHeight, - padding: padding, - child: child, - ); + }); + } } + return Container( + height: StyleString.topBarHeight, + padding: padding, + child: child, + ); } Widget searchBar(ThemeData theme) { diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index 09ae2d41c..675b274bb 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -31,7 +31,9 @@ class MainController extends GetxController List navigationBars = []; RxDouble? barOffset; + RxBool? showBottomBar; late final bool hideBottomBar; + late final barHideType = Pref.barHideType; late double navHeight = 80.0; bool useBottomNav = false; late dynamic controller; @@ -86,8 +88,13 @@ class MainController extends GetxController hideBottomBar = !useSideBar && navigationBars.length > 1 && Pref.hideBottomBar; - if (hideBottomBar || homeController.hideTopBar) { - barOffset = RxDouble(0.0); + if (hideBottomBar) { + switch (barHideType) { + case .instant: + showBottomBar = RxBool(true); + case .sync: + barOffset ??= RxDouble(0.0); + } } dynamicBadgeMode = Pref.dynamicBadgeMode; @@ -318,6 +325,12 @@ class MainController extends GetxController } } + void setSearchBar() { + if (hasHome) { + homeController.showTopBar?.value = true; + } + } + @override void onClose() { barOffset?.close(); diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 5146d459c..e03d2cb7d 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -57,7 +57,9 @@ class _MainAppState extends PopScopeState void didChangeDependencies() { super.didChangeDependencies(); _padding = MediaQuery.viewPaddingOf(context); - _mainController.navHeight = 80.0 + _padding.bottom; + if (_mainController.hideBottomBar && _mainController.barHideType == .sync) { + _mainController.navHeight = 80.0 + _padding.bottom; + } final brightness = Theme.brightnessOf(context); NetworkImgLayer.reduce = NetworkImgLayer.reduceLuxColor != null && brightness.isDark; @@ -253,7 +255,9 @@ class _MainAppState extends PopScopeState if (_mainController.selectedIndex.value != 0) { _mainController ..setIndex(0) - ..barOffset?.value = 0.0; + ..barOffset?.value = 0.0 + ..showBottomBar?.value = true + ..setSearchBar(); } else { _onBack(); } @@ -300,14 +304,26 @@ class _MainAppState extends PopScopeState ) : null; if (bottomNav != null && _mainController.hideBottomBar) { - return Obx( - () => CustomHeightWidget( - height: - _mainController.navHeight * - (1 - _mainController.barOffset!.value / StyleString.topBarHeight), - child: bottomNav, - ), - ); + if (_mainController.barOffset case final barOffset?) { + return Obx( + () => CustomHeightWidget( + height: + _mainController.navHeight * + (1 - barOffset.value / StyleString.topBarHeight), + child: bottomNav, + ), + ); + } + if (_mainController.showBottomBar case final showBottomBar?) { + return Obx( + () => AnimatedSlide( + curve: Curves.easeInOutCubicEmphasized, + duration: const Duration(milliseconds: 500), + offset: Offset(0, showBottomBar.value ? 0 : 1), + child: bottomNav, + ), + ); + } } return bottomNav; } diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index fc718fc7f..bb1d7b626 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -45,11 +45,19 @@ class _MediaPageState extends CommonPageState _mainController.selectedIndex.value == 0; @override - bool onNotification(ScrollNotification notification) { + bool onNotificationType1(UserScrollNotification notification) { if (checkPage) { return false; } - return super.onNotification(notification); + return super.onNotificationType1(notification); + } + + @override + bool onNotificationType2(ScrollNotification notification) { + if (checkPage) { + return false; + } + return super.onNotificationType2(notification); } @override diff --git a/lib/pages/setting/models/style_settings.dart b/lib/pages/setting/models/style_settings.dart index 084223723..c8c6decee 100644 --- a/lib/pages/setting/models/style_settings.dart +++ b/lib/pages/setting/models/style_settings.dart @@ -8,6 +8,7 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/scale_app.dart'; import 'package:PiliPlus/common/widgets/stateful_builder.dart'; import 'package:PiliPlus/main.dart'; +import 'package:PiliPlus/models/common/bar_hide_type.dart'; import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart'; import 'package:PiliPlus/models/common/dynamic/up_panel_position.dart'; import 'package:PiliPlus/models/common/home_tab_type.dart'; @@ -178,6 +179,12 @@ List get styleSettings => [ getSubtitle: () => '当前消息类型:${Pref.msgUnReadTypeV2.map((item) => item.title).join('、')}', ), + NormalModel( + onTap: _showBarHideTypeDialog, + title: '顶/底栏收起类型', + leading: const Icon(MdiIcons.arrowExpandVertical), + getSubtitle: () => '当前:${Pref.barHideType.label}', + ), SwitchModel( title: '首页顶栏收起', subtitle: '首页列表滑动时,收起顶栏', @@ -884,3 +891,22 @@ Future _showDefHomeDialog( setState(); } } + +Future _showBarHideTypeDialog( + BuildContext context, + VoidCallback setState, +) async { + final res = await showDialog( + context: context, + builder: (context) => SelectDialog( + title: '顶/底栏收起类型', + value: Pref.barHideType, + values: BarHideType.values.map((e) => (e, e.label)).toList(), + ), + ); + if (res != null) { + await GStorage.setting.put(SettingBoxKey.barHideType, res.index); + SmartDialog.showToast('重启生效'); + setState(); + } +} diff --git a/lib/utils/storage_key.dart b/lib/utils/storage_key.dart index 2118b9344..915cc6713 100644 --- a/lib/utils/storage_key.dart +++ b/lib/utils/storage_key.dart @@ -220,6 +220,7 @@ abstract final class SettingBoxKey { enableMYBar = 'enableMYBar', hideTopBar = 'hideSearchBar', hideBottomBar = 'hideTabBar', + barHideType = 'barHideType', tabBarSort = 'tabBarSort', dynamicBadgeMode = 'dynamicBadgeMode', msgBadgeMode = 'msgBadgeMode', diff --git a/lib/utils/storage_pref.dart b/lib/utils/storage_pref.dart index 08b25b9cc..b881dc547 100644 --- a/lib/utils/storage_pref.dart +++ b/lib/utils/storage_pref.dart @@ -3,6 +3,7 @@ import 'dart:math' show pow, sqrt; import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/constants.dart'; +import 'package:PiliPlus/models/common/bar_hide_type.dart'; import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart'; import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart'; import 'package:PiliPlus/models/common/dynamic/up_panel_position.dart'; @@ -672,6 +673,12 @@ abstract final class Pref { defaultValue: PlatformUtils.isMobile, ); + static BarHideType get barHideType => + BarHideType.values[_setting.get( + SettingBoxKey.barHideType, + defaultValue: BarHideType.sync.index, + )]; + static bool get enableSearchWord => _setting.get(SettingBoxKey.enableSearchWord, defaultValue: false);