| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import '../../core/theme/app_colors.dart';
- import 'package:go_router/go_router.dart';
- import 'package:tdesign_flutter/tdesign_flutter.dart';
- import '../../core/i18n/app_localizations.dart';
- import '../../core/theme/app_colors_extension.dart';
- import '../../features/shell/nav_bar_config.dart';
- /// 应用级 Scaffold:NavBar + body + 可选 BottomTabBar
- ///
- /// 替代原来的 AppShell。每个页面自行包裹,无需 ShellRoute。
- class AppScaffold extends ConsumerWidget {
- final Widget body;
- final bool showTabBar;
- const AppScaffold({super.key, required this.body, this.showTabBar = false});
- bool _isRootTab(String location) {
- return location == '/' || location == '/messages' || location == '/profile';
- }
- NavBarConfig _rootConfig(String location, AppLocalizations l10n) {
- if (location.startsWith('/messages')) {
- return NavBarConfig(title: l10n.get('tabMessages'), showBack: true, leadingIcon: Icons.close);
- }
- if (location == '/') {
- return NavBarConfig(title: l10n.get('appName'), showBack: true, leadingIcon: Icons.close);
- }
- if (location.startsWith('/profile')) {
- return NavBarConfig(title: l10n.get('tabProfile'), showBack: true, leadingIcon: Icons.close);
- }
- return NavBarConfig.home;
- }
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- final l10n = AppLocalizations.of(context);
- final location = GoRouterState.of(context).uri.toString();
- final config = _isRootTab(location)
- ? _rootConfig(location, l10n)
- : ref.watch(navBarConfigProvider);
- SystemChrome.setSystemUIOverlayStyle(
- SystemUiOverlayStyle(
- statusBarColor: colors.bgCard,
- statusBarIconBrightness: Brightness.dark,
- statusBarBrightness: Brightness.light,
- ),
- );
- return Scaffold(
- backgroundColor: colors.bgPage,
- body: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- _NavBarView(config: config, location: location, onBack: () {
- if (_isRootTab(location)) {
- SystemNavigator.pop();
- } else {
- GoRouter.of(context).pop();
- }
- }),
- Expanded(child: body),
- if (showTabBar)
- Container(
- color: colors.bgCard,
- child: _AppTabBar(location: location),
- ),
- ],
- ),
- );
- }
- }
- class _NavBarView extends StatelessWidget {
- final NavBarConfig config;
- final String location;
- final VoidCallback onBack;
- const _NavBarView({required this.config, required this.location, required this.onBack});
- @override
- Widget build(BuildContext context) {
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- List<TDNavBarItem>? leftItems;
- if (config.showBack) {
- final icon = config.leadingIcon ?? TDIcons.chevron_left;
- leftItems = [
- TDNavBarItem(
- icon: icon,
- iconSize: 22,
- iconColor: colors.textPrimary,
- action: config.onBack ?? onBack,
- ),
- ];
- }
- List<TDNavBarItem>? rightItems;
- if (config.showRight && config.rightWidget != null) {
- rightItems = [TDNavBarItem(iconWidget: config.rightWidget, iconSize: 22)];
- }
- return TDNavBar(
- title: config.title,
- titleColor: colors.textPrimary,
- titleFontWeight: FontWeight.w600,
- titleFont: Font(size: AppFontSizes.title.toInt(), lineHeight: 26),
- backgroundColor: colors.bgCard,
- height: 56,
- centerTitle: true,
- useDefaultBack: false,
- screenAdaptation: true,
- leftBarItems: leftItems,
- rightBarItems: rightItems,
- );
- }
- }
- class _AppTabBar extends StatelessWidget {
- final String location;
- const _AppTabBar({required this.location});
- static int tabIndex(String location) {
- if (location.startsWith('/messages')) return 0;
- if (location == '/' || (!location.startsWith('/messages') && !location.startsWith('/profile'))) return 1;
- return 2;
- }
- @override
- Widget build(BuildContext context) {
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- final l10n = AppLocalizations.of(context);
- return LayoutBuilder(
- builder: (ctx, constraints) {
- if (constraints.maxWidth <= 0) return const SizedBox.shrink();
- final bottomPadding = MediaQuery.of(ctx).padding.bottom;
- return Padding(
- padding: EdgeInsets.only(top: 8, bottom: 8 + bottomPadding),
- child: TDBottomTabBar(
- TDBottomTabBarBasicType.iconText,
- useSafeArea: false,
- componentType: TDBottomTabBarComponentType.label,
- outlineType: TDBottomTabBarOutlineType.capsule,
- currentIndex: tabIndex(location),
- navigationTabs: [
- TDBottomTabBarTabConfig(
- tabText: l10n.get('tabMessages'),
- selectedIcon: Icon(Icons.notifications, size: 22, color: colors.primary),
- unselectedIcon: Icon(Icons.notifications_outlined, size: 22, color: colors.textSecondary),
- selectTabTextStyle: TextStyle(fontSize: 10, fontWeight: FontWeight.w600, color: colors.primary),
- unselectTabTextStyle: TextStyle(fontSize: 10, color: colors.textSecondary),
- onTap: () => context.go('/messages'),
- ),
- TDBottomTabBarTabConfig(
- tabText: l10n.get('tabWorkbench'),
- selectedIcon: Icon(Icons.dashboard, size: 22, color: colors.primary),
- unselectedIcon: Icon(Icons.dashboard_outlined, size: 22, color: colors.textSecondary),
- selectTabTextStyle: TextStyle(fontSize: 10, fontWeight: FontWeight.w600, color: colors.primary),
- unselectTabTextStyle: TextStyle(fontSize: 10, color: colors.textSecondary),
- onTap: () => context.go('/'),
- ),
- TDBottomTabBarTabConfig(
- tabText: l10n.get('tabProfile'),
- selectedIcon: Icon(Icons.person, size: 22, color: colors.primary),
- unselectedIcon: Icon(Icons.person_outline, size: 22, color: colors.textSecondary),
- selectTabTextStyle: TextStyle(fontSize: 10, fontWeight: FontWeight.w600, color: colors.primary),
- unselectTabTextStyle: TextStyle(fontSize: 10, color: colors.textSecondary),
- onTap: () => context.go('/profile'),
- ),
- ],
- ),
- );
- },
- );
- }
- }
|