Files
xianyan/lib/app/layout/ohos_app_shell.dart
22 7ea4a068a1 feat(macos+flutter): 新增 Impeller 渲染引擎开关与 Intel Mac 渲染兼容修复
本次提交包含以下核心变更:
1. 修复 RawKeyboard 断言错误,添加 HardwareKeyboard 事件处理器
2. 实现 Intel Mac 自动降级玻璃渲染质量,避免黑屏闪烁
3. 新增 macOS 端 Impeller 渲染引擎开关设置,支持动态切换
4. 修复 macOS 双标题栏问题,隐藏系统原生交通灯按钮
5. 更新多语言国际化支持,新增 Impeller 相关翻译
6. 优化 WebRTC 依赖下载,使用国内镜像避免超时
2026-06-25 08:44:00 +08:00

308 lines
9.9 KiB
Dart
Raw Permalink 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-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<OhosAppShell> createState() => _OhosAppShellState();
}
class _OhosAppShellState extends ConsumerState<OhosAppShell> {
int _currentIndex = 0;
static const List<Widget> _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<void>(
CupertinoPageRoute<void>(
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,
);
}
}