/// ============================================================ /// 闲言APP — 鸿蒙端专用布局壳 /// 创建时间: 2026-05-18 /// 更新时间: 2026-06-12 /// 作用: 鸿蒙端使用 Scaffold+GlassBottomBar 替代 GoRouter+StatefulShellRoute /// 上次更新: 从 core/layout/ 迁移至 app/layout/,修复架构层级违规 /// ============================================================ /// /// 根因: 鸿蒙端 Flutter 引擎中 MaterialApp.router + 额外包导入 = 白屏 /// 方案: 鸿蒙端仅阉割路由(GoRouter),保留液态玻璃效果 import 'package:badges/badges.dart' as badges; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:liquid_glass_widgets/liquid_glass_widgets.dart'; import '../../core/storage/kv_storage.dart'; import '../../core/theme/app_theme.dart'; import '../../core/utils/platform/glass_quality_resolver.dart'; import '../../core/utils/ui/interaction_animations.dart'; import '../../core/utils/logger.dart'; import '../../core/utils/platform/platform_utils.dart' show OhosDeviceCapabilities; import '../../features/onboarding/presentation/onboarding_page.dart'; import '../../features/discover/providers/chat_provider.dart'; import '../../features/discover/presentation/pages/home/discover_page.dart'; import '../../features/home/presentation/home_page.dart'; import '../../features/profile/presentation/profile_page.dart'; import '../../features/settings/providers/theme_settings_provider.dart'; import '../../l10n/translations.dart'; import '../../main.dart' show liquidGlassReady; import '../../shared/widgets/containers/glass_bottom_nav_bar.dart'; import '../../shared/widgets/animation/tab_icon_sprite.dart'; class OhosAppShell extends ConsumerStatefulWidget { const OhosAppShell({super.key}); static _OhosAppShellState? _instance; /// 切换到指定 Tab 索引(供 OhosNavBridge.go() 调用) static void switchTab(int index) { _instance?.switchTab(index); } @override ConsumerState createState() => _OhosAppShellState(); } class _OhosAppShellState extends ConsumerState { int _currentIndex = 0; static const List _tabPages = [ HomePage(), DiscoverPage(), ProfilePage(), ]; @override void initState() { super.initState(); OhosAppShell._instance = this; WidgetsBinding.instance.addPostFrameCallback((_) { _checkOnboarding(); }); } void _checkOnboarding() { if (!mounted) return; // 等待KvStorage初始化完成再检查引导页状态 // 避免Hive未初始化时默认值导致引导页重复弹出 if (!KvStorage.isReady) { Log.i('🟢 [OHOS] 引导页检查: KvStorage未就绪,延迟检查'); Future.delayed(const Duration(milliseconds: 300), () { if (mounted) _checkOnboarding(); }); return; } final shouldShow = KvStorage.isFirstLaunch || KvStorage.shouldShowOnboarding; if (shouldShow) { Log.i('🟢 [OHOS] 引导页检查: 首次启动=$isFirstLaunch, 应显示引导=$shouldShow → 推送引导页'); Navigator.of(context).push( CupertinoPageRoute( fullscreenDialog: true, builder: (_) => const OnboardingPage(), ), ); } else { Log.i('🟢 [OHOS] 引导页检查: 已完成引导,直接进入主页'); } } bool get isFirstLaunch => KvStorage.isFirstLaunch; @override void dispose() { OhosAppShell._instance = null; super.dispose(); } /// 切换到指定 Tab 索引 void switchTab(int index) { if (index >= 0 && index < _tabPages.length) { setState(() => _currentIndex = index); } } @override Widget build(BuildContext context) { OhosDeviceCapabilities.init(context); final ext = AppTheme.ext(context); final unreadCount = ref.watch(chatProvider).unreadCount; final settings = ref.watch(themeSettingsProvider); final expressionStyle = settings.tabExpressionStyle; final characterId = settings.tabCharacterStyleId; final animIntensity = settings.animationIntensity.durationMultiplier; Log.i( '🟢 [OHOS] OhosAppShell.build() — currentIndex=$_currentIndex liquidGlass=$liquidGlassReady', ); final Widget bottomBar = liquidGlassReady ? _buildGlassBottomBar( ext: ext, unreadCount: unreadCount, expressionStyle: expressionStyle, characterId: characterId, animIntensity: animIntensity, ) : _buildFallbackNavBar( ext: ext, unreadCount: unreadCount, expressionStyle: expressionStyle, characterId: characterId, animIntensity: animIntensity, ); return CelebrationOverlay( child: PopScope( canPop: false, onPopInvokedWithResult: (didPop, _) { if (didPop) return; }, child: Scaffold( extendBody: true, body: IndexedStack(index: _currentIndex, children: _tabPages), bottomNavigationBar: bottomBar, ), ), ); } /// GPU Shader 液态玻璃底部导航栏 (LiquidGlassWidgets 可用时) Widget _buildGlassBottomBar({ required AppThemeExtension ext, required int unreadCount, required TabExpressionStyleOption expressionStyle, required String characterId, required double animIntensity, }) { int adjacentFor(int index) { if (index == _currentIndex) return 0; if (index < _currentIndex) return -1; return 1; } Widget buildSpriteIcon(TabSpriteType type, int index, String label) { return TabIconSprite( type: type, label: label, isSelected: index == _currentIndex, adjacentDirection: adjacentFor(index), animationIntensity: animIntensity, characterId: characterId, eyeScale: expressionStyle.eyeScale, mouthCurve: expressionStyle.mouthCurve, bounceMultiplier: expressionStyle.bounceMultiplier, ); } return GlassBottomBar( tabs: [ GlassBottomBarTab( label: '', icon: buildSpriteIcon(TabSpriteType.home, 0, '闲言'), activeIcon: buildSpriteIcon(TabSpriteType.home, 0, '闲言'), glowColor: const Color(0xFFE8E8ED), ), GlassBottomBarTab( label: '', icon: _buildBadgeIcon( unreadCount: unreadCount, child: buildSpriteIcon(TabSpriteType.discover, 1, '发现'), ), activeIcon: _buildBadgeIcon( unreadCount: unreadCount, child: buildSpriteIcon(TabSpriteType.discover, 1, '发现'), ), glowColor: const Color(0xFFE8E8ED), ), GlassBottomBarTab( label: '', icon: buildSpriteIcon(TabSpriteType.profile, 2, '我的'), activeIcon: buildSpriteIcon(TabSpriteType.profile, 2, '我的'), glowColor: const Color(0xFFE8E8ED), ), ], selectedIndex: _currentIndex, onTabSelected: (index) => setState(() => _currentIndex = index), quality: GlassQualityResolver.quality, selectedIconColor: ext.accent, unselectedIconColor: ext.isDark ? ext.accent.withValues(alpha: 0.38) : const Color(0xFFAEAEB2), barHeight: 68, barBorderRadius: 34, horizontalPadding: 16, verticalPadding: 16, indicatorColor: ext.isDark ? Colors.white.withValues(alpha: 0.08) : Colors.black.withValues(alpha: 0.04), indicatorSettings: LiquidGlassSettings( thickness: 40, blur: 25, refractiveIndex: 1.8, chromaticAberration: 1.2, lightIntensity: 3.5, ambientStrength: 1.2, glassColor: ext.isDark ? const Color.from(alpha: 0.18, red: 1, green: 1, blue: 1) : const Color.from(alpha: 0.12, red: 1, green: 1, blue: 1), ), settings: LiquidGlassSettings( thickness: 30, blur: 1.5, refractiveIndex: 1.5, chromaticAberration: 0.8, lightIntensity: 1.2, saturation: 1.0, ambientStrength: 0.6, glassColor: ext.isDark ? const Color.from(alpha: 0.08, red: 1, green: 1, blue: 1) : const Color.from(alpha: 0.05, red: 1, green: 1, blue: 1), ), magnification: 1.12, innerBlur: 1.5, glowOpacity: 0.4, glowBlurRadius: 24, glowSpreadRadius: 4, ); } /// 纯Dart BackdropFilter 液态玻璃底部导航栏 (降级方案) Widget _buildFallbackNavBar({ required AppThemeExtension ext, required int unreadCount, required TabExpressionStyleOption expressionStyle, required String characterId, required double animIntensity, }) { final t = ref.watch(translationsProvider); return GlassBottomNavBar( items: [ GlassBottomNavBarItem(spriteType: TabSpriteType.home, label: t.navHome), GlassBottomNavBarItem( spriteType: TabSpriteType.discover, label: t.navDiscover, badgeCount: unreadCount, ), GlassBottomNavBarItem( spriteType: TabSpriteType.profile, label: t.navProfile, ), ], selectedIndex: _currentIndex, onTabSelected: (index) => setState(() => _currentIndex = index), ext: ext, animationIntensity: animIntensity, expressionStyle: expressionStyle, characterId: characterId, ); } Widget _buildBadgeIcon({required int unreadCount, required Widget child}) { if (unreadCount <= 0) return child; return badges.Badge( badgeContent: Text( '$unreadCount', style: const TextStyle( color: Colors.white, fontSize: 9, fontWeight: FontWeight.bold, ), ), badgeStyle: const badges.BadgeStyle( badgeColor: CupertinoColors.systemRed, padding: EdgeInsets.all(3), ), position: badges.BadgePosition.topEnd(top: -4, end: -6), child: child, ); } }