refactor: 完成项目架构重构,统一模块导入路径

- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层
- 修复所有相对路径导入错误,统一调整为扁平化模块引用
- 更新多平台 pubspec 版本号与依赖库版本
- 补充后端功能问题管理后台与脚本工具
- 调整部分页面的快捷方式文案适配新功能
- 更新部分翻译覆盖率与API文档
This commit is contained in:
Developer
2026-06-12 08:48:33 +08:00
parent 33e347dea7
commit f91be94e9c
560 changed files with 28489 additions and 14780 deletions

View File

@@ -1,380 +0,0 @@
/// ============================================================
/// 闲言APP — 自适应导航栏
/// 创建时间: 2026-05-29
/// 更新时间: 2026-05-29
/// 作用: 宽屏时垂直导航栏窄屏时底部导航栏支持4种停靠位置
/// 上次更新: 替换bitsdojo_window为window_manager桌面端窗口控制
/// ============================================================
import 'dart:ui';
import 'package:badges/badges.dart' as badges;
import 'package:window_manager/window_manager.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/split_view_provider.dart';
import '../theme/app_theme.dart';
import '../theme/app_spacing.dart';
import '../theme/glass_tokens.dart';
import '../utils/platform/platform_utils.dart' as pu;
import '../../features/discover/providers/chat_provider.dart';
import '../../features/mine/settings/providers/theme_settings_provider.dart';
import '../../shared/widgets/animation/tab_icon_sprite.dart';
class AdaptiveNavBar extends ConsumerWidget {
const AdaptiveNavBar({
required this.currentIndex,
required this.onTabSelected,
super.key,
});
final int currentIndex;
final ValueChanged<int> onTabSelected;
@override
Widget build(BuildContext context, WidgetRef ref) {
final splitState = ref.watch(splitViewProvider);
final position = splitState.navBarPosition;
return switch (position) {
NavBarPosition.left ||
NavBarPosition.right => _buildVertical(context, ref),
NavBarPosition.top ||
NavBarPosition.bottom => _buildHorizontal(context, ref),
};
}
Widget _buildVertical(BuildContext context, WidgetRef ref) {
final ext = AppTheme.ext(context);
final settings = ref.watch(themeSettingsProvider);
final unreadCount = ref.watch(chatProvider).unreadCount;
final animIntensity = settings.animationIntensity.durationMultiplier;
final expressionStyle = settings.tabExpressionStyle;
final characterId = settings.tabCharacterStyleId;
Widget buildTab(TabSpriteType type, int index, String label) {
final isSelected = index == currentIndex;
final showBadge = index == 1 && unreadCount > 0;
return GestureDetector(
onTap: () => onTabSelected(index),
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedScale(
scale: isSelected ? 1.08 : 1.0,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
child: showBadge
? badges.Badge(
badgeContent: Text(
'',
style: TextStyle(
color: ext.textOnAccent,
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: TabIconSprite(
type: type,
label: '',
isSelected: isSelected,
adjacentDirection: 0,
animationIntensity: animIntensity,
characterId: characterId,
eyeScale: expressionStyle.eyeScale,
mouthCurve: expressionStyle.mouthCurve,
bounceMultiplier: expressionStyle.bounceMultiplier,
),
)
: TabIconSprite(
type: type,
label: '',
isSelected: isSelected,
adjacentDirection: 0,
animationIntensity: animIntensity,
characterId: characterId,
eyeScale: expressionStyle.eyeScale,
mouthCurve: expressionStyle.mouthCurve,
bounceMultiplier: expressionStyle.bounceMultiplier,
),
),
const SizedBox(height: 2),
Text(
label,
style: TextStyle(
fontSize: 10,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
color: isSelected
? (ext.isDark ? ext.textInverse : ext.accent)
: (ext.isDark
? ext.textInverse.withValues(alpha: 0.38)
: const Color(0xFFAEAEB2)),
),
),
],
),
),
);
}
final splitState = ref.watch(splitViewProvider);
final isRight = splitState.navBarPosition == NavBarPosition.right;
return Container(
width: 72,
decoration: BoxDecoration(
color: ext.glassColor.withValues(
alpha: ext.isDark
? GlassTokens.elevatedOpacityDark
: GlassTokens.elevatedOpacityLight,
),
),
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: GlassTokens.elevatedBlur * ext.glassBlurMultiplier,
sigmaY: GlassTokens.elevatedBlur * ext.glassBlurMultiplier,
),
child: SafeArea(
right: isRight,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildTab(TabSpriteType.home, 0, '闲言'),
const SizedBox(height: AppSpacing.lg),
buildTab(TabSpriteType.discover, 1, '发现'),
const SizedBox(height: AppSpacing.lg),
buildTab(TabSpriteType.profile, 2, '我的'),
],
),
),
),
),
);
}
Widget _buildHorizontal(BuildContext context, WidgetRef ref) {
final ext = AppTheme.ext(context);
final settings = ref.watch(themeSettingsProvider);
final unreadCount = ref.watch(chatProvider).unreadCount;
final animIntensity = settings.animationIntensity.durationMultiplier;
final expressionStyle = settings.tabExpressionStyle;
final characterId = settings.tabCharacterStyleId;
final splitState = ref.watch(splitViewProvider);
final isTop = splitState.navBarPosition == NavBarPosition.top;
Widget buildTab(TabSpriteType type, int index, String label) {
final isSelected = index == currentIndex;
final showBadge = index == 1 && unreadCount > 0;
return Expanded(
child: GestureDetector(
onTap: () => onTabSelected(index),
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
AnimatedScale(
scale: isSelected ? 1.08 : 1.0,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
child: showBadge
? badges.Badge(
badgeContent: Text(
'',
style: TextStyle(
color: ext.textOnAccent,
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: TabIconSprite(
type: type,
label: '',
isSelected: isSelected,
adjacentDirection: 0,
animationIntensity: animIntensity,
characterId: characterId,
eyeScale: expressionStyle.eyeScale,
mouthCurve: expressionStyle.mouthCurve,
bounceMultiplier: expressionStyle.bounceMultiplier,
),
)
: TabIconSprite(
type: type,
label: '',
isSelected: isSelected,
adjacentDirection: 0,
animationIntensity: animIntensity,
characterId: characterId,
eyeScale: expressionStyle.eyeScale,
mouthCurve: expressionStyle.mouthCurve,
bounceMultiplier: expressionStyle.bounceMultiplier,
),
),
const SizedBox(width: 4),
Text(
label,
style: TextStyle(
fontSize: 11,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
color: isSelected
? (ext.isDark ? ext.textInverse : ext.accent)
: (ext.isDark
? ext.textInverse.withValues(alpha: 0.38)
: const Color(0xFFAEAEB2)),
),
),
],
),
),
),
);
}
return Container(
height: 52,
decoration: BoxDecoration(
color: ext.glassColor.withValues(
alpha: ext.isDark
? GlassTokens.elevatedOpacityDark
: GlassTokens.elevatedOpacityLight,
),
),
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: GlassTokens.elevatedBlur * ext.glassBlurMultiplier,
sigmaY: GlassTokens.elevatedBlur * ext.glassBlurMultiplier,
),
child: SafeArea(
top: isTop,
bottom: !isTop,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buildTab(TabSpriteType.home, 0, '闲言'),
buildTab(TabSpriteType.discover, 1, '发现'),
buildTab(TabSpriteType.profile, 2, '我的'),
if (isTop && pu.isDesktop) _buildDesktopWindowControls(ext),
],
),
),
),
),
);
}
/// 桌面端窗口控制按钮(最小化/最大化/关闭)+ 可拖拽标题栏区域
Widget _buildDesktopWindowControls(AppThemeExtension ext) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onPanStart: (_) => windowManager.startDragging(),
child: const SizedBox(height: 52, width: 40),
),
_WindowControlBtn(
icon: CupertinoIcons.minus,
ext: ext,
onPressed: () => windowManager.minimize(),
),
_WindowControlBtn(
icon: CupertinoIcons.square,
ext: ext,
iconSize: 12,
onPressed: () async {
if (await windowManager.isMaximized()) {
await windowManager.unmaximize();
} else {
await windowManager.maximize();
}
},
),
_WindowControlBtn(
icon: CupertinoIcons.xmark,
ext: ext,
onPressed: () => windowManager.close(),
isClose: true,
),
],
);
}
}
/// 窗口控制按钮(最小化/最大化/关闭)
class _WindowControlBtn extends StatefulWidget {
const _WindowControlBtn({
required this.icon,
required this.ext,
required this.onPressed,
this.iconSize = 14,
this.isClose = false,
});
final IconData icon;
final AppThemeExtension ext;
final VoidCallback onPressed;
final double iconSize;
final bool isClose;
@override
State<_WindowControlBtn> createState() => _WindowControlBtnState();
}
class _WindowControlBtnState extends State<_WindowControlBtn> {
bool _isHovering = false;
@override
Widget build(BuildContext context) {
final ext = widget.ext;
final bgColor = _isHovering
? (widget.isClose
? CupertinoColors.systemRed.withValues(alpha: 0.9)
: ext.textHint.withValues(alpha: 0.15))
: const Color(0x00000000);
return MouseRegion(
onEnter: (_) => setState(() => _isHovering = true),
onExit: (_) => setState(() => _isHovering = false),
child: GestureDetector(
onTap: widget.onPressed,
child: Container(
width: 46,
height: 52,
color: bgColor,
child: Icon(
widget.icon,
size: widget.iconSize,
color: _isHovering && widget.isClose
? CupertinoColors.white
: ext.textSecondary,
),
),
),
);
}
}

View File

@@ -1,479 +0,0 @@
// ============================================================
// 闲言APP — 应用布局壳
// 创建时间: 2026-04-20
// 更新时间: 2026-06-05
// 作用: ShellRoute 布局壳,宽屏分屏 + 窄屏底部导航
// 上次更新: 移除build中的高频日志减少IDE卡顿
// ============================================================
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:xianyan/core/utils/platform/platform_utils.dart' as pu;
import 'package:go_router/go_router.dart';
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
import '../providers/split_view_provider.dart';
import '../theme/app_theme.dart';
import '../utils/ui/interaction_animations.dart';
import '../../features/discover/providers/chat_provider.dart';
import '../../features/mine/settings/providers/theme_settings_provider.dart';
import '../../l10n/translations.dart';
import '../../shared/widgets/animation/tab_icon_sprite.dart';
import '../../main.dart' show liquidGlassReady;
import '../../shared/widgets/containers/wallpaper_background.dart';
import '../../shared/widgets/containers/glass_bottom_nav_bar.dart';
import '../../shared/widgets/feedback/app_error_boundary.dart';
import 'adaptive_split_view.dart';
import 'adaptive_nav_bar.dart';
import 'overview_dashboard.dart';
import 'right_panel_registry.dart';
import 'triple_column_view.dart';
import '../../features/mine/profile/presentation/panels/profile_dashboard.dart';
class AppShell extends ConsumerStatefulWidget {
const AppShell({super.key, required this.child});
final StatefulNavigationShell child;
static bool get _isOhos => pu.isOhos;
@override
ConsumerState<AppShell> createState() => _AppShellState();
}
class _AppShellState extends ConsumerState<AppShell> {
@override
Widget build(BuildContext context) {
final ext = AppTheme.ext(context);
final int currentIndex = widget.child.currentIndex;
final screenWidth = MediaQuery.sizeOf(context).width;
final splitState = ref.watch(splitViewProvider);
final isWidescreen =
screenWidth >= kSplitViewBreakpoint && splitState.splitViewEnabled;
// 鸿蒙端不再每次build打印日志避免高频日志导致IDE卡顿
// 同步当前Tab索引到SplitViewProvider
if (splitState.currentTab != currentIndex) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
ref.read(splitViewProvider.notifier).setCurrentTab(currentIndex);
}
});
}
if (isWidescreen) {
return _buildWidescreenLayout(context, currentIndex);
}
return _buildNarrowLayout(context, ext, currentIndex);
}
// ============================================================
// 宽屏分屏布局
// ============================================================
Widget _buildWidescreenLayout(BuildContext context, int currentIndex) {
final splitState = ref.watch(splitViewProvider);
final navBarPosition = splitState.navBarPosition;
final isNavBarVertical =
navBarPosition == NavBarPosition.left ||
navBarPosition == NavBarPosition.right;
final screenWidth = MediaQuery.sizeOf(context).width;
final isTripleColumn =
screenWidth >= kTripleColumnBreakpoint &&
splitState.thirdPanelContent != null;
final Widget navBar = AdaptiveNavBar(
currentIndex: currentIndex,
onTabSelected: (index) => _onTabTap(context, index),
);
final Widget splitView = isTripleColumn
? TripleColumnView(
leftPanel: RepaintBoundary(
child: AppErrorBoundary(label: '主页面', child: widget.child),
),
centerPanel: _buildRightPanel(context),
rightPanel: _buildThirdPanel(context),
)
: AdaptiveSplitView(
leftPanel: RepaintBoundary(
child: AppErrorBoundary(label: '主页面', child: widget.child),
),
rightPanel: _buildRightPanel(context),
);
/// 根据导航栏位置构建布局骨架
Widget buildLayout() {
if (isNavBarVertical) {
final isLeft = navBarPosition == NavBarPosition.left;
return Scaffold(
body: Stack(
children: [
const Positioned.fill(child: WallpaperBackground()),
Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (isLeft) navBar,
Expanded(child: splitView),
if (!isLeft) navBar,
],
),
],
),
);
}
final isTop = navBarPosition == NavBarPosition.top;
return Scaffold(
body: Stack(
children: [
const Positioned.fill(child: WallpaperBackground()),
Column(
children: [
if (isTop) navBar,
Expanded(child: splitView),
if (!isTop) navBar,
],
),
],
),
);
}
final Widget body = CelebrationOverlay(
child: PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (didPop) return;
},
child: buildLayout(),
),
);
/// 桌面端添加键盘快捷键 (Ctrl+1/2/3 切换Tab, Ctrl+W 关闭面板)
if (pu.isDesktop) {
return Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.digit1):
const _SwitchTabIntent(0),
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.digit2):
const _SwitchTabIntent(1),
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.digit3):
const _SwitchTabIntent(2),
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyW):
const _ClosePanelIntent(),
},
child: Actions(
actions: <Type, Action<Intent>>{
_SwitchTabIntent: _SwitchTabAction(
onSwitch: (index) => _onTabTap(context, index),
),
_ClosePanelIntent: _ClosePanelAction(ref: ref),
},
child: Focus(child: body),
),
);
}
return body;
}
/// 构建右侧面板内容
Widget _buildRightPanel(BuildContext context) {
final splitState = ref.watch(splitViewProvider);
final activePanel = splitState.activeRightPanel;
if (activePanel == null) {
if (splitState.currentTab == 2) {
return const ProfileDashboard();
}
return const OverviewDashboard();
}
return RightPanelRegistry.build(
activePanel,
context,
args: splitState.rightPanelArgs,
);
}
/// 构建第三栏面板内容(超宽屏三栏布局)
Widget _buildThirdPanel(BuildContext context) {
final splitState = ref.watch(splitViewProvider);
final thirdPanel = splitState.thirdPanelContent;
if (thirdPanel == null) {
return const OverviewDashboard();
}
return RightPanelRegistry.build(
thirdPanel,
context,
args: splitState.thirdPanelArgs,
);
}
// ============================================================
// 窄屏布局(原有逻辑)
// ============================================================
Widget _buildNarrowLayout(
BuildContext context,
AppThemeExtension ext,
int currentIndex,
) {
final unreadCount = ref.watch(chatProvider).unreadCount;
final settings = ref.watch(themeSettingsProvider);
final expressionStyle = settings.tabExpressionStyle;
final characterId = settings.tabCharacterStyleId;
final animIntensity = settings.animationIntensity.durationMultiplier;
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,
);
}
final Widget bottomBar = (AppShell._isOhos && !liquidGlassReady)
? _buildFallbackNavBar(context, currentIndex, ext, settings)
: GlassBottomBar(
tabs: [
GlassBottomBarTab(
label: '',
icon: buildSpriteIcon(TabSpriteType.home, 0, '闲言'),
activeIcon: buildSpriteIcon(TabSpriteType.home, 0, '闲言'),
glowColor: const Color(0xFFE8E8ED),
),
GlassBottomBarTab(
label: '',
icon: badges.Badge(
showBadge: unreadCount > 0,
badgeContent: Text(
'',
style: TextStyle(
color: ext.textOnAccent,
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: buildSpriteIcon(TabSpriteType.discover, 1, '发现'),
),
activeIcon: badges.Badge(
showBadge: unreadCount > 0,
badgeContent: Text(
'',
style: TextStyle(
color: ext.textOnAccent,
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: 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) => _onTabTap(context, index),
quality: GlassQuality.premium,
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
? ext.textInverse.withValues(alpha: 0.08)
: ext.overlaySubtle,
indicatorSettings: LiquidGlassSettings(
thickness: 24,
blur: 12,
refractiveIndex: 1.45,
chromaticAberration: 0.3,
lightIntensity: 1.2,
ambientStrength: 0.6,
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(
blur: 1.0,
refractiveIndex: 1.35,
chromaticAberration: 0.2,
lightIntensity: 0.8,
saturation: 1.0,
ambientStrength: 0.4,
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.08,
innerBlur: 1.0,
glowOpacity: 0.3,
glowBlurRadius: 16,
glowSpreadRadius: 2,
);
return CelebrationOverlay(
child: PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (didPop) return;
},
child: Scaffold(
extendBody: true,
body: _buildNarrowBody(),
bottomNavigationBar: bottomBar,
),
),
);
}
void _onTabTap(BuildContext context, int index) {
widget.child.goBranch(
index,
initialLocation: index == widget.child.currentIndex,
);
}
/// 窄屏布局body横屏未达分屏断点时居中限宽竖屏保持原样
Widget _buildNarrowBody() {
final size = MediaQuery.sizeOf(context);
final isLandscapeNarrow =
size.width > 600 &&
size.width < kSplitViewBreakpoint &&
size.height < size.width;
final content = Stack(
children: [
const WallpaperBackground(),
RepaintBoundary(
child: AppErrorBoundary(label: '主页面', child: widget.child),
),
],
);
if (isLandscapeNarrow) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: content,
),
);
}
return content;
}
Widget _buildFallbackNavBar(
BuildContext context,
int currentIndex,
AppThemeExtension ext,
ThemeSettingsState settings,
) {
final expressionStyle = settings.tabExpressionStyle;
final characterId = settings.tabCharacterStyleId;
final animIntensity = settings.animationIntensity.durationMultiplier;
final unreadCount = ref.watch(chatProvider).unreadCount;
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) => _onTabTap(context, index),
ext: ext,
animationIntensity: animIntensity,
expressionStyle: expressionStyle,
characterId: characterId,
);
}
}
// ============================================================
// 桌面端键盘快捷键 — Intent & Action
// ============================================================
/// 切换Tab意图
class _SwitchTabIntent extends Intent {
const _SwitchTabIntent(this.index);
final int index;
}
/// 关闭面板意图
class _ClosePanelIntent extends Intent {
const _ClosePanelIntent();
}
/// 切换Tab动作
class _SwitchTabAction extends Action<_SwitchTabIntent> {
_SwitchTabAction({required this.onSwitch});
final void Function(int) onSwitch;
@override
Object? invoke(_SwitchTabIntent intent) {
onSwitch(intent.index);
return null;
}
}
/// 关闭面板动作
class _ClosePanelAction extends Action<_ClosePanelIntent> {
_ClosePanelAction({required this.ref});
final WidgetRef ref;
@override
Object? invoke(_ClosePanelIntent intent) {
ref.read(splitViewProvider.notifier).clearActivePanel();
return null;
}
}

View File

@@ -1,306 +0,0 @@
/// ============================================================
/// 闲言APP — 鸿蒙端专用布局壳
/// 创建时间: 2026-05-18
/// 更新时间: 2026-06-06
/// 作用: 鸿蒙端使用 Scaffold+GlassBottomBar 替代 GoRouter+StatefulShellRoute
/// 上次更新: 修复鸿蒙端杀后台重启时引导页重复弹出的问题添加KvStorage.isReady检查
/// ============================================================
///
/// 根因: 鸿蒙端 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;
// 等待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: GlassQuality.premium,
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),
),
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,
);
}
}

View File

@@ -1,399 +0,0 @@
/// ============================================================
/// 闲言APP — 概览仪表盘
/// 创建时间: 2026-05-29
/// 更新时间: 2026-06-01
/// 作用: 宽屏分屏右侧面板的空状态页面,显示概览信息
/// 上次更新: 最近浏览接入RecentRouteService展示真实浏览记录
/// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../theme/app_theme.dart';
import '../theme/app_spacing.dart';
import '../../shared/widgets/containers/glass_container.dart';
import '../../features/home/providers/home_provider.dart';
import '../../features/home/providers/favorite_provider.dart';
import '../../features/home/providers/likes_provider.dart';
import '../../features/auth/providers/auth_provider.dart';
import '../../features/mine/signin/providers/signin_provider.dart';
import '../../features/home/providers/tool_center_recent_provider.dart';
import '../../features/home/presentation/home_tool_center.dart';
import '../router/app_routes.dart';
import '../router/app_nav_extension.dart';
import '../services/navigation/recent_route_service.dart';
class OverviewDashboard extends ConsumerWidget {
const OverviewDashboard({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return SingleChildScrollView(
padding: const EdgeInsets.all(AppSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildGreeting(context),
const SizedBox(height: AppSpacing.lg),
_buildTodayRecommend(context, ref),
const SizedBox(height: AppSpacing.lg),
_buildQuickActions(context),
const SizedBox(height: AppSpacing.lg),
_buildRecentHistory(context, ref),
const SizedBox(height: AppSpacing.lg),
_buildStats(context, ref),
const SizedBox(height: AppSpacing.xxl),
],
),
);
}
Widget _buildGreeting(BuildContext context) {
final ext = AppTheme.ext(context);
final hour = DateTime.now().hour;
final greeting = switch (hour) {
>= 6 && < 12 => '早上好 ☀️',
>= 12 && < 14 => '中午好 🌤️',
>= 14 && < 18 => '下午好 🌅',
>= 18 && < 22 => '晚上好 🌙',
_ => '夜深了 🌛',
};
return GlassContainer(
depth: GlassDepth.elevated,
padding: const EdgeInsets.all(AppSpacing.lg),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
greeting,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: ext.textPrimary,
),
),
const SizedBox(height: AppSpacing.xs),
Text(
'选择左侧内容查看详情',
style: TextStyle(fontSize: 14, color: ext.textSecondary),
),
],
),
),
],
),
);
}
Widget _buildTodayRecommend(BuildContext context, WidgetRef ref) {
final ext = AppTheme.ext(context);
final homeState = ref.watch(homeProvider);
final recommends = homeState.dailySentences;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'✨ 今日推荐',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: ext.textPrimary,
),
),
const SizedBox(height: AppSpacing.sm),
SizedBox(
height: 120,
child: recommends.isEmpty
? GlassContainer(
padding: const EdgeInsets.all(AppSpacing.md),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('📭', style: TextStyle(fontSize: 28)),
const SizedBox(height: AppSpacing.xs),
Text(
'暂无推荐内容',
style: TextStyle(fontSize: 13, color: ext.textHint),
),
],
),
),
)
: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: recommends.length,
separatorBuilder: (_, __) =>
const SizedBox(width: AppSpacing.sm),
itemBuilder: (context, index) {
final sentence = recommends[index];
return GlassContainer(
width: 180,
padding: const EdgeInsets.all(AppSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
sentence.text,
style: TextStyle(
fontSize: 13,
color: ext.textPrimary,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
Text(
sentence.author != null
? '—— ${sentence.author}'
: '—— 佚名',
style: TextStyle(fontSize: 11, color: ext.textHint),
),
],
),
);
},
),
),
],
);
}
Widget _buildQuickActions(BuildContext context) {
final ext = AppTheme.ext(context);
final actions = <({String emoji, String label, String route})>[
(emoji: '🔍', label: '搜索', route: AppRoutes.search),
(emoji: '', label: '收藏', route: AppRoutes.favorites),
(emoji: '📖', label: '稍后读', route: AppRoutes.readLater),
(emoji: '🕐', label: '历史', route: AppRoutes.history),
(emoji: '', label: '签到', route: AppRoutes.signin),
(emoji: '📊', label: '使用报告', route: AppRoutes.readingReport),
(emoji: '🌤️', label: '每日推荐', route: AppRoutes.dailyCard),
(emoji: '⚙️', label: '设置', route: AppRoutes.generalSettings),
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'🚀 快捷操作',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: ext.textPrimary,
),
),
const SizedBox(height: AppSpacing.sm),
AnimationLimiter(
child: Wrap(
spacing: AppSpacing.sm,
runSpacing: AppSpacing.sm,
children: actions.asMap().entries.map((entry) {
return AnimationConfiguration.staggeredList(
position: entry.key,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: GestureDetector(
onTap: () => context.appPush(entry.value.route),
child: GlassContainer(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(entry.value.emoji,
style: const TextStyle(fontSize: 16)),
const SizedBox(width: AppSpacing.xs),
Text(
entry.value.label,
style: TextStyle(
fontSize: 13, color: ext.textPrimary),
),
],
),
),
),
),
),
);
}).toList(),
),
),
],
);
}
Widget _buildRecentHistory(BuildContext context, WidgetRef ref) {
final ext = AppTheme.ext(context);
ref.watch(toolCenterRecentProvider);
final recentRoutes = RecentRouteService.getRecentRoutes();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'🕐 最近浏览',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: ext.textPrimary,
),
),
const SizedBox(height: AppSpacing.sm),
recentRoutes.isEmpty
? GlassContainer(
padding: const EdgeInsets.all(AppSpacing.md),
child: Center(
child: Column(
children: [
const Text('📭', style: TextStyle(fontSize: 32)),
const SizedBox(height: AppSpacing.sm),
Text(
'暂无浏览记录',
style: TextStyle(fontSize: 13, color: ext.textHint),
),
],
),
),
)
: AnimationLimiter(
child: Column(
children: recentRoutes.take(6).toList().asMap().entries.map((entry) {
final String route = entry.value;
final String iconText = ToolCenterIconMap.getIconText(route);
final String name = ToolCenterIconMap.getName(route);
return AnimationConfiguration.staggeredList(
position: entry.key,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 30.0,
child: FadeInAnimation(
child: Padding(
padding: const EdgeInsets.only(bottom: AppSpacing.xs),
child: GestureDetector(
onTap: () => context.appPush(route),
child: GlassContainer(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
child: Row(
children: [
Text(iconText, style: const TextStyle(fontSize: 18)),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Text(
name,
style: TextStyle(
fontSize: 14,
color: ext.textPrimary,
),
),
),
Icon(
CupertinoIcons.chevron_right,
size: 14,
color: ext.textHint,
),
],
),
),
),
),
),
),
);
}).toList(),
),
),
],
);
}
Widget _buildStats(BuildContext context, WidgetRef ref) {
final ext = AppTheme.ext(context);
final authState = ref.watch(authProvider);
final favoriteState = ref.watch(favoriteProvider);
final likesState = ref.watch(likesProvider);
final signinState = ref.watch(signinProvider);
final readCount = authState.user?.signinDays ?? 0;
final favCount = favoriteState.total;
final likeCount = likesState.total;
final streakDays = signinState.continuous;
final stats = [
('📖', '阅读', '$readCount'),
('', '收藏', '$favCount'),
('👍', '点赞', '$likeCount'),
('🔥', '连续', '$streakDays天'),
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'📊 数据统计',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: ext.textPrimary,
),
),
const SizedBox(height: AppSpacing.sm),
AnimationLimiter(
child: Row(
children: stats.asMap().entries.map((entry) {
final stat = entry.value;
return AnimationConfiguration.staggeredList(
position: entry.key,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: Expanded(
child: GlassContainer(
padding: const EdgeInsets.all(AppSpacing.sm),
margin: const EdgeInsets.symmetric(
horizontal: AppSpacing.xs / 2,
),
child: Column(
children: [
Text(stat.$1, style: const TextStyle(fontSize: 20)),
const SizedBox(height: 4),
Text(
stat.$3,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: ext.textPrimary,
),
),
Text(
stat.$2,
style: TextStyle(fontSize: 11, color: ext.textHint),
),
],
),
),
),
),
),
);
}).toList(),
),
),
],
);
}
}