Files
xianyan/lib/core/layout/ohos_app_shell.dart
Developer 63a0559721 refactor: 重构项目路由与模块结构,统一发现页命名与路径
1. 全局替换tool_center/inspiration为discover模块,统一路由路径
2. 调整AppRoutes路由常量,将discover作为主Tab页,inspiration作为子页面
3. 更新页面注册表与路由配置,修正跳转目标
4. 调整启动页可选配置项,修正路由ID对应关系
5. 新增翻译服务、内容发现、热搜相关工具类与数据模型
6. 修复缓存清理后未刷新统计的问题,调整x86_64架构注释
7. 更新AGENTS.md文档约束规则
8. 新增一批调试用截图资源文件
2026-05-28 06:42:20 +08:00

296 lines
9.5 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-22
/// 作用: 鸿蒙端使用 Scaffold+GlassBottomBar 替代 GoRouter+StatefulShellRoute
/// 上次更新: 修复引导页在鸿蒙端不显示的问题initState检查onboarding状态
/// ============================================================
///
/// 根因: 鸿蒙端 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/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/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/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;
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: 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,
);
}
}