Files
xianyan/lib/core/layout/ohos_app_shell.dart
Developer fed86c0375 迁移
2026-05-21 04:27:21 +08:00

257 lines
8.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// ============================================================
/// 闲言APP — 鸿蒙端专用布局壳
/// 创建时间: 2026-05-18
/// 更新时间: 2026-05-18
/// 作用: 鸿蒙端使用 Scaffold+GlassBottomBar 替代 GoRouter+StatefulShellRoute
/// 上次更新: 恢复液态玻璃效果+TabIconSprite精灵动画仅阉割路由
/// ============================================================
///
/// 根因: 鸿蒙端 Flutter 引擎中 MaterialApp.router + 额外包导入 = 白屏
/// 方案: 鸿蒙端仅阉割路由(GoRouter),保留液态玻璃效果
import 'package:badges/badges.dart' as badges;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
import '../../core/theme/app_theme.dart';
import '../../core/utils/interaction_animations.dart';
import '../../core/utils/logger.dart';
import '../../core/utils/platform_utils.dart' show OhosDeviceCapabilities;
import '../../features/tool_center/inspiration/providers/chat_provider.dart';
import '../../features/tool_center/inspiration/presentation/pages/home/inspiration_page.dart';
import '../../features/home/presentation/home_page.dart';
import '../../features/mine/profile/presentation/profile_page.dart';
import '../../features/mine/settings/providers/theme_settings_provider.dart';
import '../../l10n/translations.dart';
import '../../main.dart' show liquidGlassReady;
import '../../shared/widgets/glass_bottom_nav_bar.dart';
import '../../shared/widgets/tab_icon_sprite.dart';
class OhosAppShell extends ConsumerStatefulWidget {
const OhosAppShell({super.key});
@override
ConsumerState<OhosAppShell> createState() => _OhosAppShellState();
}
class _OhosAppShellState extends ConsumerState<OhosAppShell> {
int _currentIndex = 0;
static const List<Widget> _tabPages = [
HomePage(),
InspirationPage(),
ProfilePage(),
];
@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: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: ext.isDark
? Brightness.light
: Brightness.dark,
statusBarBrightness: ext.isDark ? Brightness.dark : Brightness.light,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness: ext.isDark
? Brightness.light
: Brightness.dark,
systemNavigationBarDividerColor: Colors.transparent,
),
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: GlassQuality.premium,
selectedIconColor: ext.isDark ? Colors.white : ext.accent,
unselectedIconColor: ext.isDark
? Colors.white38
: 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),
),
glassSettings: 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,
);
}
}