diff --git a/lib/pages/home/main_content_page.dart b/lib/pages/home/main_content_page.dart index a77c1bf..79f4f33 100644 --- a/lib/pages/home/main_content_page.dart +++ b/lib/pages/home/main_content_page.dart @@ -86,27 +86,34 @@ class _MainContentPageState extends State { @override Widget build(BuildContext context) { - return Column( - children: [ - if (!Breakpoint.isDesktop(context)) ...[ - _buildAppBar(context), - _buildTabBar(context), - ], - Expanded(child: _buildTabContent()), - ], + return Consumer( + builder: (context, provider, child) { + final isDropdown = provider.homeModuleSwitchMode == 1; + return Column( + children: [ + if (!Breakpoint.isDesktop(context)) ...[ + _buildAppBar(context, isDropdown), + if (!isDropdown) _buildTabBar(context), + ], + Expanded(child: _buildTabContent()), + ], + ); + }, ); } // ─── AppBar ────────────────────────────────────────── - Widget _buildAppBar(BuildContext context) { + Widget _buildAppBar(BuildContext context, bool isDropdown) { return Consumer( builder: (context, provider, child) { final colors = Theme.of(context).colorScheme; return AppBar( titleSpacing: 8, leadingWidth: 44, - title: Text(_getAppBarTitle(provider)), + title: isDropdown + ? _buildDropdownTitle(context, provider, colors) + : Text(_getAppBarTitle(provider)), actionsPadding: const EdgeInsets.only(right: 4), actions: [ _buildCloudSyncButton(context), @@ -143,6 +150,65 @@ class _MainContentPageState extends State { ); } + Widget _buildDropdownTitle(BuildContext context, AppProvider provider, ColorScheme colors) { + final tabs = _enabledTabs; + final safeIndex = _mapToEnabledTabIndex(provider.mainTabIndex).clamp(0, tabs.length - 1); + final currentLabel = tabs.isEmpty ? '主页' : tabs[safeIndex].label; + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => _showModulePicker(context, tabs, safeIndex), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(_tabIcon(currentLabel), size: 20, color: colors.primary), + const SizedBox(width: 6), + Text(currentLabel, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), + const SizedBox(width: 2), + Icon(Icons.arrow_drop_down, size: 22, color: colors.onSurface.withValues(alpha: 0.5)), + ], + ), + ); + } + + void _showModulePicker(BuildContext context, List<_TabItem> tabs, int currentIndex) { + final colors = Theme.of(context).colorScheme; + final provider = context.read(); + showModalBottomSheet( + context: context, + backgroundColor: colors.surface, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), + builder: (ctx) => SafeArea( + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Container(width: 36, height: 4, margin: const EdgeInsets.only(top: 12, bottom: 16), + decoration: BoxDecoration(color: colors.onSurface.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(2))), + for (int i = 0; i < tabs.length; i++) ...[ + if (i > 0) Divider(height: 0.5, indent: 20, endIndent: 20, color: colors.outlineVariant), + _modulePickerItem(ctx, tabs[i], i, i == currentIndex, colors, provider), + ], + const SizedBox(height: 12), + ]), + ), + ); + } + + Widget _modulePickerItem(BuildContext ctx, _TabItem tab, int idx, bool selected, ColorScheme colors, AppProvider provider) { + return ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20), + leading: Container(width: 36, height: 36, + decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)), + child: Icon(_tabIcon(tab.label), size: 20, color: selected ? colors.primary : colors.onSurface.withValues(alpha: 0.6))), + title: Text(tab.label, style: TextStyle(fontSize: 14, fontWeight: selected ? FontWeight.w600 : FontWeight.w400, color: colors.onSurface)), + trailing: selected ? Icon(Icons.check, size: 20, color: colors.primary) : null, + onTap: () { + Navigator.pop(ctx); + _isTabTap = true; + _pageController.jumpToPage(idx); + provider.setMainTabIndex(tab.originalIndex); + Future.delayed(const Duration(milliseconds: 50), () => _isTabTap = false); + }, + ); + } + String _getAppBarTitle(AppProvider provider) { switch (provider.mainTabIndex) { case -1: return '主页'; diff --git a/lib/pages/profile/layout_settings_page.dart b/lib/pages/profile/layout_settings_page.dart index f0f04a7..1e42877 100644 --- a/lib/pages/profile/layout_settings_page.dart +++ b/lib/pages/profile/layout_settings_page.dart @@ -12,6 +12,7 @@ class LayoutSettingsPage extends StatefulWidget { class _LayoutSettingsPageState extends State { final UserPrefs _userPrefs = UserPrefs(); + int _homeModuleSwitchMode = 0; int _noteLayout = 0; int _movieLayout = 0; int _bookLayout = 0; @@ -28,6 +29,7 @@ class _LayoutSettingsPageState extends State { @override void initState() { super.initState(); + _homeModuleSwitchMode = _userPrefs.homeModuleSwitchMode; _noteLayout = _userPrefs.noteLayoutStyle; _movieLayout = _userPrefs.movieLayoutStyle; _bookLayout = _userPrefs.bookLayoutStyle; @@ -51,6 +53,13 @@ class _LayoutSettingsPageState extends State { body: ListView( padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), children: [ + _buildCategoryTile( + icon: Icons.dashboard_outlined, + title: '主页模块显示方式', + color: colors.primary, + subtitle: _homeModuleSubtitle, + onTap: _showHomeModuleSheet, + ), _buildCategoryTile( icon: Icons.movie_outlined, title: '影视', @@ -84,6 +93,10 @@ class _LayoutSettingsPageState extends State { ); } + String get _homeModuleSubtitle { + return _homeModuleSwitchMode == 1 ? '顶部下拉切换' : '模块标签切换'; + } + String get _movieSubtitle { final parts = []; if (_movieWallMode) { @@ -305,6 +318,49 @@ class _LayoutSettingsPageState extends State { // ─── 弹窗 ──────────────────────────────────────────────────────── + void _showHomeModuleSheet() { + final colors = Theme.of(context).colorScheme; + showModalBottomSheet( + context: context, + backgroundColor: colors.surface, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), + builder: (ctx) => StatefulBuilder( + builder: (ctx, setSheetState) => SafeArea( + child: SingleChildScrollView( + child: Column(mainAxisSize: MainAxisSize.min, children: [ + _sheetHandle(colors), + _sheetTitle('主页模块显示方式', colors), + _sheetOptionRow( + label: '切换方式', + selected: _homeModuleSwitchMode, + options: const [ + (0, Icons.tab_outlined, '模块标签'), + (1, Icons.arrow_drop_down_outlined, '顶部下拉'), + ], + onChanged: (v) { + _userPrefs.setHomeModuleSwitchMode(v); + setState(() => _homeModuleSwitchMode = v); + if (mounted) context.read().setHomeModuleSwitchMode(v); + setSheetState(() {}); + }, + colors: colors, + ), + Padding( + padding: const EdgeInsets.fromLTRB(20, 4, 20, 16), + child: Text( + _homeModuleSwitchMode == 1 + ? 'AppBar 标题显示当前模块,点击弹出选择菜单。' + : '底部显示模块标签栏,点击切换。', + style: TextStyle(fontSize: 12, color: colors.onSurface.withValues(alpha: 0.45)), + ), + ), + ]), + ), + ), + ), + ); + } + void _showMovieSheet() { final colors = Theme.of(context).colorScheme; showModalBottomSheet( diff --git a/lib/providers/app_provider.dart b/lib/providers/app_provider.dart index 5fbe44e..0f5f110 100644 --- a/lib/providers/app_provider.dart +++ b/lib/providers/app_provider.dart @@ -61,6 +61,9 @@ class AppProvider extends ChangeNotifier { // 当前主界面选中的标签 (0: 观影,1: 阅读,2: 笔记) int _mainTabIndex = 0; + // 主页模块显示方式 (0=底部tab切换, 1=顶部下拉切换) + int _homeModuleSwitchMode = 0; + // 当前底部导航选中的索引 (0: 主页,1: 新增,2: 我的) int _bottomNavIndex = 0; @@ -199,6 +202,7 @@ class AppProvider extends ChangeNotifier { _bookshelfMode = userPrefs.bookshelfMode; _gameLayoutStyle = userPrefs.gameLayoutStyle; _gameWallMode = userPrefs.gameWallMode; + _homeModuleSwitchMode = userPrefs.homeModuleSwitchMode; final defaultIndex = userPrefs.defaultMainTabIndex; // Windows 桌面端且主页开启时,强制默认为主页 if (Platform.isWindows && userPrefs.showDesktopHomeTab) { @@ -297,6 +301,7 @@ class AppProvider extends ChangeNotifier { // Getters int get mainTabIndex => _mainTabIndex; + int get homeModuleSwitchMode => _homeModuleSwitchMode; int get bottomNavIndex => _bottomNavIndex; Movie? get selectedMovie => _selectedMovie; Book? get selectedBook => _selectedBook; @@ -340,6 +345,12 @@ class AppProvider extends ChangeNotifier { notifyListeners(); } + void setHomeModuleSwitchMode(int mode) { + _homeModuleSwitchMode = mode; + UserPrefs().setHomeModuleSwitchMode(mode); + notifyListeners(); + } + void setBottomNavIndex(int index) { if (index == 0 && _bottomNavIndex == 0) { // 已在首页,再次点击 → 回到顶部 diff --git a/lib/utils/user_prefs.dart b/lib/utils/user_prefs.dart index f20a8da..4d02f1d 100644 --- a/lib/utils/user_prefs.dart +++ b/lib/utils/user_prefs.dart @@ -108,6 +108,10 @@ class UserPrefs { bool get showGameTab => prefs.getBool('showGameTab') ?? false; Future setShowGameTab(bool value) => prefs.setBool('showGameTab', value); + /// 主页模块显示方式 (0=底部tab切换, 1=顶部下拉切换) + int get homeModuleSwitchMode => prefs.getInt('homeModuleSwitchMode') ?? 0; + Future setHomeModuleSwitchMode(int value) => prefs.setInt('homeModuleSwitchMode', value); + /// 默认启动标签 (-1: 主页, 0: 影视, 1: 阅读, 2: 笔记, 3: 游戏) int get defaultMainTabIndex => prefs.getInt('defaultMainTabIndex') ?? -1; Future setDefaultMainTabIndex(int value) => prefs.setInt('defaultMainTabIndex', value);