Files
xianyan/docs/superpowers/plans/2026-05-29-widescreen-adaptation.md
Developer 5a49d20c8a chore: 完成项目品牌域名批量替换与功能迭代
本次提交包含多项核心更新:
1. 全量替换项目内所有xianyan.app域名变更为s2ss.com,包含配置文件、路由、隐私政策等
2. 重构图表库从fl_chart迁移至syncfusion_flutter_charts,优化图表渲染效果
3. 新增宽屏分屏布局支持,包含右侧面板注册表与可拖拽分割线
4. 完善触觉反馈服务与认证感知Mixin,修复多处内存泄漏问题
5. 合并勋章墙与金币记录入口至成就中心,简化个人中心导航
6. 新增收藏与时间线数据合并导入功能
7. 修复多处UI样式问题,统一主题颜色使用规范
8. 新增日历同步与跨平台触觉反馈依赖库
9. 修复BotToast初始化流程,避免路由切换时的弹窗崩溃
2026-05-29 10:06:55 +08:00

1284 lines
38 KiB
Markdown
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.
# 宽屏适配 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 为闲言APP添加宽屏/横屏分屏适配三个主Tab页面支持左右双面板布局
**Architecture:** 混合方案 — AppShell提供AdaptiveSplitView框架各Tab通过RightPanelRegistry注册自己的详情面板。宽屏(≥900px)时底部导航栏变为垂直导航栏,内容区左右分屏。窄屏(<900px)保持现有行为。
**Tech Stack:** Flutter 3.x, Riverpod, GoRouter, liquid_glass_widgets, dual_screen(可选)
---
## File Structure
### 新增文件
| 文件 | 职责 |
|------|------|
| `lib/core/providers/split_view_provider.dart` | SplitView状态Provider分割比例/面板内容/导航栏位置/分屏开关) |
| `lib/core/layout/adaptive_split_view.dart` | AdaptiveSplitView核心分屏组件左右面板+分割线+手势隔离) |
| `lib/core/layout/split_divider.dart` | SplitDivider可拖拽分割线hover/拖拽/触觉反馈) |
| `lib/core/layout/adaptive_nav_bar.dart` | AdaptiveNavBar垂直/水平导航栏4种停靠位置 |
| `lib/core/layout/right_panel_registry.dart` | RightPanelRegistry右侧面板注册表 |
| `lib/core/layout/overview_dashboard.dart` | OverviewDashboard概览仪表盘空状态页面 |
| `lib/features/home/presentation/panels/sentence_detail_panel.dart` | 句子详情面板从Sheet提取内容复用 |
| `lib/features/home/presentation/panels/home_dashboard.dart` | 首页概览仪表盘 |
| `lib/features/discover/presentation/panels/chat_flow_panel.dart` | 会话流详情面板 |
| `lib/features/discover/presentation/panels/discover_dashboard.dart` | 发现页概览仪表盘 |
| `lib/features/mine/profile/presentation/panels/profile_dashboard.dart` | 我的概览仪表盘 |
| `lib/features/mine/settings/presentation/panels/settings_panels.dart` | 设置项面板集合 |
### 修改文件
| 文件 | 修改内容 |
|------|----------|
| `lib/core/layout/app_shell.dart` | 集成AdaptiveLayoutSwitcher宽屏用分屏布局 |
| `lib/features/home/presentation/home_page.dart` | 宽屏点击判断 |
| `lib/features/discover/presentation/pages/home/discover_page.dart` | 宽屏点击判断 |
| `lib/features/mine/profile/presentation/profile_page.dart` | 宽屏点击判断 |
| `lib/features/mine/settings/presentation/general/general_settings_sections.dart` | 新增导航栏位置/分屏设置项 |
| `pubspec.yaml` | 添加dual_screen依赖 |
---
## Phase 1: 核心框架SplitView + Provider + Divider
### Task 1: SplitView Provider
**Files:**
- Create: `lib/core/providers/split_view_provider.dart`
- [ ] **Step 1: 创建split_view_provider.dart**
```dart
/// ============================================================
/// 闲言APP — 宽屏分屏状态管理
/// 创建时间: 2026-05-29
/// 更新时间: 2026-05-29
/// 作用: 管理分屏比例、右侧面板内容、导航栏位置、分屏开关
/// 上次更新: 初始创建
/// ============================================================
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:xianyan/core/storage/app_kv_store.dart';
/// 导航栏停靠位置
enum NavBarPosition {
left,
right,
top,
bottom;
String get label => switch (this) {
left => '左侧',
right => '右侧',
top => '顶部',
bottom => '底部',
};
String get emoji => switch (this) {
left => '⬅️',
right => '➡️',
top => '⬆️',
bottom => '⬇️',
};
}
/// 分屏比例选项
class SplitRatioOption {
const SplitRatioOption(this.value, this.label);
final double value;
final String label;
static const List<SplitRatioOption> options = [
SplitRatioOption(0.30, '30:70'),
SplitRatioOption(0.35, '35:65'),
SplitRatioOption(0.40, '40:60'),
SplitRatioOption(0.45, '45:55'),
SplitRatioOption(0.50, '50:50'),
SplitRatioOption(0.55, '55:45'),
SplitRatioOption(0.60, '60:40'),
];
}
const String _kSplitRatio = 'split_view_ratio';
const String _kNavBarPosition = 'nav_bar_position';
const String _kSplitViewEnabled = 'split_view_enabled';
/// 分割比例
final splitRatioProvider = StateProvider<double>((ref) {
return KvStorage.getDouble(_kSplitRatio) ?? 0.4;
});
/// 右侧面板内容标识
final rightPanelContentProvider = StateProvider<String?>((ref) => null);
/// 右侧面板参数
final rightPanelArgsProvider = StateProvider<Map<String, dynamic>?>((ref) => null);
/// 导航栏位置
final navBarPositionProvider = StateProvider<NavBarPosition>((ref) {
final index = KvStorage.getInt(_kNavBarPosition) ?? 0;
return NavBarPosition.values[index];
});
/// 分屏功能开关
final splitViewEnabledProvider = StateProvider<bool>((ref) {
return KvStorage.getBool(_kSplitViewEnabled) ?? true;
});
/// 首页右侧面板
final homeRightPanelProvider = StateProvider<String?>((ref) => null);
/// 发现页右侧面板
final discoverRightPanelProvider = StateProvider<String?>((ref) => null);
/// 我的页面右侧面板
final profileRightPanelProvider = StateProvider<String?>((ref) => null);
/// 当前Tab索引
final currentTabProvider = StateProvider<int>((ref) => 0);
/// 当前活跃的右侧面板根据当前Tab自动选择
final activeRightPanelProvider = Provider<String?>((ref) {
final tab = ref.watch(currentTabProvider);
return switch (tab) {
0 => ref.watch(homeRightPanelProvider),
1 => ref.watch(discoverRightPanelProvider),
2 => ref.watch(profileRightPanelProvider),
_ => null,
};
});
/// SplitView设置变更Notifier
class SplitViewSettingsNotifier extends Notifier<void> {
@override
void build() {}
void setSplitRatio(double value) {
KvStorage.setDouble(_kSplitRatio, value);
ref.read(splitRatioProvider.notifier).state = value;
}
void setNavBarPosition(NavBarPosition position) {
KvStorage.setInt(_kNavBarPosition, position.index);
ref.read(navBarPositionProvider.notifier).state = position;
}
void setSplitViewEnabled(bool enabled) {
KvStorage.setBool(_kSplitViewEnabled, enabled);
ref.read(splitViewEnabledProvider.notifier).state = enabled;
}
}
final splitViewSettingsProvider =
NotifierProvider<SplitViewSettingsNotifier, void>(
SplitViewSettingsNotifier.new,
);
```
- [ ] **Step 2: 验证编译**
Run: `flutter analyze lib/core/providers/split_view_provider.dart`
Expected: 无错误
- [ ] **Step 3: Commit**
```bash
git add lib/core/providers/split_view_provider.dart
git commit -m "feat: add SplitView providers for widescreen adaptation"
```
---
### Task 2: SplitDivider 可拖拽分割线
**Files:**
- Create: `lib/core/layout/split_divider.dart`
- [ ] **Step 1: 创建split_divider.dart**
```dart
/// ============================================================
/// 闲言APP — 可拖拽分割线
/// 创建时间: 2026-05-29
/// 更新时间: 2026-05-29
/// 作用: 宽屏分屏的分割线组件支持拖拽调整比例、hover高亮、触觉反馈
/// 上次更新: 初始创建
/// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import '../theme/app_theme.dart';
import '../theme/app_spacing.dart';
class SplitDivider extends StatefulWidget {
const SplitDivider({
required this.onPositionChanged,
this.currentPosition = 0.4,
this.minPosition = 0.2,
this.maxPosition = 0.7,
this.isVertical = true,
super.key,
});
final ValueChanged<double> onPositionChanged;
final double currentPosition;
final double minPosition;
final double maxPosition;
final bool isVertical;
@override
State<SplitDivider> createState() => _SplitDividerState();
}
class _SplitDividerState extends State<SplitDivider> {
bool _isHovering = false;
bool _isDragging = false;
@override
Widget build(BuildContext context) {
final ext = AppTheme.ext(context);
final dividerColor = ext.textHint.withValues(alpha: 0.15);
final handleColor = _isDragging
? ext.accent.withValues(alpha: 0.8)
: _isHovering
? ext.accent.withValues(alpha: 0.5)
: ext.textHint.withValues(alpha: 0.3);
return MouseRegion(
onEnter: (_) => setState(() => _isHovering = true),
onExit: (_) => setState(() => _isHovering = false),
cursor: SystemMouseCursors.resizeColumn,
child: GestureDetector(
onHorizontalDragStart: _onDragStart,
onHorizontalDragUpdate: _onDragUpdate,
onHorizontalDragEnd: _onDragEnd,
behavior: HitTestBehavior.translucent,
child: Container(
width: 17,
alignment: Alignment.center,
child: Container(
width: 1,
color: dividerColor,
child: Center(
child: AnimatedContainer(
duration: const Duration(milliseconds: 150),
curve: Curves.easeOut,
width: _isDragging ? 6 : _isHovering ? 4 : 4,
height: 32,
decoration: BoxDecoration(
color: handleColor,
borderRadius: BorderRadius.circular(2),
),
),
),
),
),
),
);
}
void _onDragStart(DragStartDetails details) {
setState(() => _isDragging = true);
HapticFeedback.selectionClick();
}
void _onDragUpdate(DragUpdateDetails details) {
final box = context.findRenderObject() as RenderBox;
final parent = box.parent as RenderBox;
final totalWidth = parent.size.width;
if (totalWidth <= 0) return;
final localX = details.globalPosition.dx - parent.localToGlobal(Offset.zero).dx;
final newPosition = (localX / totalWidth).clamp(widget.minPosition, widget.maxPosition);
widget.onPositionChanged(newPosition);
}
void _onDragEnd(DragEndDetails details) {
setState(() => _isDragging = false);
}
}
```
- [ ] **Step 2: 验证编译**
Run: `flutter analyze lib/core/layout/split_divider.dart`
Expected: 无错误
- [ ] **Step 3: Commit**
```bash
git add lib/core/layout/split_divider.dart
git commit -m "feat: add SplitDivider with drag/hover/haptic support"
```
---
### Task 3: AdaptiveSplitView 核心分屏组件
**Files:**
- Create: `lib/core/layout/adaptive_split_view.dart`
- [ ] **Step 1: 创建adaptive_split_view.dart**
```dart
/// ============================================================
/// 闲言APP — 自适应分屏组件
/// 创建时间: 2026-05-29
/// 更新时间: 2026-05-29
/// 作用: 宽屏时左右分屏布局,支持可拖拽分割线、手势隔离、动画过渡
/// 上次更新: 初始创建
/// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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 'split_divider.dart';
/// 分屏断点:宽度 >= 900px 进入分屏模式
const double kSplitViewBreakpoint = 900.0;
class AdaptiveSplitView extends ConsumerStatefulWidget {
const AdaptiveSplitView({
required this.leftPanel,
required this.rightPanel,
this.minLeftWidth = 320,
this.minRightWidth = 400,
super.key,
});
final Widget leftPanel;
final Widget rightPanel;
final double minLeftWidth;
final double minRightWidth;
@override
ConsumerState<AdaptiveSplitView> createState() => _AdaptiveSplitViewState();
}
class _AdaptiveSplitViewState extends ConsumerState<AdaptiveSplitView>
with SingleTickerProviderStateMixin {
late AnimationController _panelAnimationController;
late Animation<Offset> _slideAnimation;
late Animation<double> _fadeAnimation;
bool _wasSplitView = false;
@override
void initState() {
super.initState();
_panelAnimationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 350),
);
_slideAnimation = Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _panelAnimationController,
curve: Curves.easeInOutCubic,
));
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _panelAnimationController,
curve: Curves.easeIn,
),
);
}
@override
void dispose() {
_panelAnimationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.sizeOf(context).width;
final isSplitView = screenWidth >= kSplitViewBreakpoint &&
ref.watch(splitViewEnabledProvider);
final splitRatio = ref.watch(splitRatioProvider);
final rightPanelContent = ref.watch(activeRightPanelProvider);
final hasRightContent = rightPanelContent != null;
// 动画控制:宽屏+有内容 → 滑入,否则 → 滑出
if (isSplitView && hasRightContent) {
_panelAnimationController.forward();
} else {
_panelAnimationController.reverse();
}
// 窄屏:直接返回左面板
if (!isSplitView) {
if (_wasSplitView) {
_wasSplitView = false;
}
return widget.leftPanel;
}
_wasSplitView = true;
final leftWidth = screenWidth * splitRatio;
final rightWidth = screenWidth * (1 - splitRatio);
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
width: leftWidth.clamp(widget.minLeftWidth, screenWidth - widget.minRightWidth - 17),
child: NotificationListener<ScrollNotification>(
onNotification: (_) => true,
child: widget.leftPanel,
),
),
SplitDivider(
currentPosition: splitRatio,
onPositionChanged: (newRatio) {
ref.read(splitViewSettingsProvider.notifier).setSplitRatio(newRatio);
},
minPosition: widget.minLeftWidth / screenWidth,
maxPosition: (screenWidth - widget.minRightWidth - 17) / screenWidth,
),
SizedBox(
width: (screenWidth - leftWidth - 17).clamp(widget.minRightWidth, screenWidth - widget.minLeftWidth - 17),
child: SlideTransition(
position: _slideAnimation,
child: FadeTransition(
opacity: _fadeAnimation,
child: NotificationListener<ScrollNotification>(
onNotification: (_) => true,
child: widget.rightPanel,
),
),
),
),
],
);
}
}
```
- [ ] **Step 2: 验证编译**
Run: `flutter analyze lib/core/layout/adaptive_split_view.dart`
Expected: 无错误
- [ ] **Step 3: Commit**
```bash
git add lib/core/layout/adaptive_split_view.dart
git commit -m "feat: add AdaptiveSplitView with animation and gesture isolation"
```
---
### Task 4: RightPanelRegistry 右侧面板注册表
**Files:**
- Create: `lib/core/layout/right_panel_registry.dart`
- [ ] **Step 1: 创建right_panel_registry.dart**
```dart
/// ============================================================
/// 闲言APP — 右侧面板注册表
/// 创建时间: 2026-05-29
/// 更新时间: 2026-05-29
/// 作用: 管理右侧面板的注册与构建各Tab页面通过注册表提供面板内容
/// 上次更新: 初始创建
/// ============================================================
import 'package:flutter/widgets.dart';
typedef RightPanelBuilder = Widget Function(
BuildContext context,
Map<String, dynamic>? args,
);
class RightPanelRegistry {
RightPanelRegistry._();
static final Map<String, RightPanelBuilder> _builders = {};
/// 注册面板构建器
static void register(String panelId, RightPanelBuilder builder) {
_builders[panelId] = builder;
}
/// 批量注册
static void registerAll(Map<String, RightPanelBuilder> entries) {
_builders.addAll(entries);
}
/// 构建面板Widget
static Widget build(String panelId, BuildContext context, {Map<String, dynamic>? args}) {
final builder = _builders[panelId];
if (builder == null) {
return _buildPlaceholder(context, panelId);
}
return builder(context, args);
}
/// 是否已注册
static bool hasPanel(String panelId) => _builders.containsKey(panelId);
/// 获取所有已注册的panelId
static List<String> get registeredIds => _builders.keys.toList();
/// 未注册面板的占位Widget
static Widget _buildPlaceholder(BuildContext context, String panelId) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('🚧', style: TextStyle(fontSize: 48)),
const SizedBox(height: 16),
Text(
'面板 "$panelId" 尚未注册',
style: const TextStyle(fontSize: 14),
),
],
),
);
}
}
```
- [ ] **Step 2: 验证编译**
Run: `flutter analyze lib/core/layout/right_panel_registry.dart`
Expected: 无错误
- [ ] **Step 3: Commit**
```bash
git add lib/core/layout/right_panel_registry.dart
git commit -m "feat: add RightPanelRegistry for panel management"
```
---
### Task 5: AdaptiveNavBar 垂直/水平导航栏
**Files:**
- Create: `lib/core/layout/adaptive_nav_bar.dart`
- [ ] **Step 1: 创建adaptive_nav_bar.dart**
组件需要:
- 垂直模式left/right72px宽3个Tab垂直排列TabIconSprite(28x28)+标签(10px)
- 水平模式top/bottom与现有GlassBottomBar样式一致
- 毛玻璃背景
- 选中态accent色+放大1.08x
- 未读红点支持
- 4种停靠位置
```dart
/// ============================================================
/// 闲言APP — 自适应导航栏
/// 创建时间: 2026-05-29
/// 更新时间: 2026-05-29
/// 作用: 宽屏时垂直导航栏窄屏时底部导航栏支持4种停靠位置
/// 上次更新: 初始创建
/// ============================================================
import 'dart:ui';
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 '../providers/split_view_provider.dart';
import '../theme/app_theme.dart';
import '../theme/app_spacing.dart';
import '../theme/glass_tokens.dart';
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 position = ref.watch(navBarPositionProvider);
return switch (position) {
NavBarPosition.left || NavBarPosition.right => _buildVertical(context, ref),
NavBarPosition.top => _buildHorizontal(context, ref),
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(
showBadge: true,
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)),
),
),
],
),
),
);
}
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: ref.watch(navBarPositionProvider) == NavBarPosition.right,
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) {
return const SizedBox.shrink();
}
}
```
- [ ] **Step 2: 验证编译**
Run: `flutter analyze lib/core/layout/adaptive_nav_bar.dart`
Expected: 无错误
- [ ] **Step 3: Commit**
```bash
git add lib/core/layout/adaptive_nav_bar.dart
git commit -m "feat: add AdaptiveNavBar with vertical/horizontal modes"
```
---
### Task 6: OverviewDashboard 概览仪表盘
**Files:**
- Create: `lib/core/layout/overview_dashboard.dart`
- [ ] **Step 1: 创建overview_dashboard.dart**
概览仪表盘包含5个区块问候区、今日推荐、快捷操作、最近浏览、数据统计。使用现有的设计令牌和GlassContainer。
```dart
/// ============================================================
/// 闲言APP — 概览仪表盘
/// 创建时间: 2026-05-29
/// 更新时间: 2026-05-29
/// 作用: 宽屏分屏右侧面板的空状态页面,显示概览信息
/// 上次更新: 初始创建
/// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../theme/app_theme.dart';
import '../theme/app_spacing.dart';
import '../theme/app_radius.dart';
import '../../shared/widgets/containers/glass_container.dart';
class OverviewDashboard extends ConsumerWidget {
const OverviewDashboard({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final ext = AppTheme.ext(context);
return SingleChildScrollView(
padding: const EdgeInsets.all(AppSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildGreeting(context),
const SizedBox(height: AppSpacing.lg),
_buildTodayRecommend(context),
const SizedBox(height: AppSpacing.lg),
_buildQuickActions(context),
const SizedBox(height: AppSpacing.lg),
_buildRecentHistory(context),
const SizedBox(height: AppSpacing.lg),
_buildStats(context),
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) {
final ext = AppTheme.ext(context);
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: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 3,
separatorBuilder: (_, __) => const SizedBox(width: AppSpacing.sm),
itemBuilder: (context, index) {
return GlassContainer(
depth: GlassDepth.base,
width: 180,
padding: const EdgeInsets.all(AppSpacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'📝 示例句子 ${index + 1}',
style: TextStyle(
fontSize: 13,
color: ext.textPrimary,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
Text(
'—— 作者',
style: TextStyle(
fontSize: 11,
color: ext.textHint,
),
),
],
),
);
},
),
),
],
);
}
Widget _buildQuickActions(BuildContext context) {
final ext = AppTheme.ext(context);
final actions = [
('🔍', '搜索'),
('', '收藏'),
('📖', '稍后读'),
('📝', '笔记'),
('📊', '统计'),
('⚙️', '设置'),
('🎨', '主题'),
('🔔', '提醒'),
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'🚀 快捷操作',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: ext.textPrimary,
),
),
const SizedBox(height: AppSpacing.sm),
Wrap(
spacing: AppSpacing.sm,
runSpacing: AppSpacing.sm,
children: actions.map((action) {
return GestureDetector(
onTap: () {},
child: GlassContainer(
depth: GlassDepth.base,
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(action.$1, style: const TextStyle(fontSize: 16)),
const SizedBox(width: AppSpacing.xs),
Text(
action.$2,
style: TextStyle(
fontSize: 13,
color: ext.textPrimary,
),
),
],
),
),
);
}).toList(),
),
],
);
}
Widget _buildRecentHistory(BuildContext context) {
final ext = AppTheme.ext(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'🕐 最近浏览',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: ext.textPrimary,
),
),
const SizedBox(height: AppSpacing.sm),
GlassContainer(
depth: GlassDepth.base,
padding: const EdgeInsets.all(AppSpacing.md),
child: Center(
child: Column(
children: [
Text('📭', style: const TextStyle(fontSize: 32)),
const SizedBox(height: AppSpacing.sm),
Text(
'暂无浏览记录',
style: TextStyle(fontSize: 13, color: ext.textHint),
),
],
),
),
),
],
);
}
Widget _buildStats(BuildContext context) {
final ext = AppTheme.ext(context);
final stats = [
('📖', '阅读', '0'),
('', '收藏', '0'),
('👍', '点赞', '0'),
('🔥', '连续', '0天'),
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'📊 数据统计',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: ext.textPrimary,
),
),
const SizedBox(height: AppSpacing.sm),
Row(
children: stats.map((stat) {
return Expanded(
child: GlassContainer(
depth: GlassDepth.base,
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(),
),
],
);
}
}
```
- [ ] **Step 2: 验证编译**
Run: `flutter analyze lib/core/layout/overview_dashboard.dart`
Expected: 无错误
- [ ] **Step 3: Commit**
```bash
git add lib/core/layout/overview_dashboard.dart
git commit -m "feat: add OverviewDashboard for split view empty state"
```
---
### Task 7: 修改 AppShell 集成分屏布局
**Files:**
- Modify: `lib/core/layout/app_shell.dart`
- [ ] **Step 1: 修改AppShell添加宽屏分屏逻辑**
在AppShell的build方法中检测屏幕宽度
- 宽屏(≥900px)使用Row布局 [AdaptiveNavBar + AdaptiveSplitView]
- 窄屏(<900px)保持现有Scaffold + GlassBottomBar
关键修改点:
1. 导入新组件
2. 在build方法开头检测屏幕宽度
3. 宽屏时构建Row布局NavBar + SplitView
4. 窄屏时保持现有逻辑
5. 更新currentTabProvider
- [ ] **Step 2: 验证编译**
Run: `flutter analyze lib/core/layout/app_shell.dart`
Expected: 无错误
- [ ] **Step 3: 运行应用验证窄屏行为不变**
Run: `flutter run`
Expected: 窄屏设备上底部导航栏和页面行为与之前完全一致
- [ ] **Step 4: Commit**
```bash
git add lib/core/layout/app_shell.dart
git commit -m "feat: integrate AdaptiveSplitView into AppShell for widescreen"
```
---
## Phase 2: 首页分屏适配
### Task 8: SentenceDetailPanel 句子详情面板
**Files:**
- Create: `lib/features/home/presentation/panels/sentence_detail_panel.dart`
- [ ] **Step 1: 从sentence_detail_sheet.dart提取内容为独立Widget**
将_SentenceDetailContent提取为公共Widget SentenceDetailContent
SentenceDetailPanel包裹SentenceDetailContent并添加面板标题栏。
SentenceDetailSheet也改为使用SentenceDetailContent。
- [ ] **Step 2: 注册面板**
在RightPanelRegistry中注册 `sentence_detail``home_dashboard`
- [ ] **Step 3: 修改HomePage宽屏点击判断**
点击句子卡片时:
- 宽屏:设置 homeRightPanelProvider 为 'sentence_detail',传递句子数据
- 窄屏保持底部Sheet弹出
- [ ] **Step 4: 验证**
Run: `flutter run`
Expected: 宽屏点击句子卡片→右侧显示详情面板窄屏→底部Sheet
- [ ] **Step 5: Commit**
```bash
git add lib/features/home/presentation/panels/ lib/features/home/presentation/home_page.dart lib/features/home/presentation/providers/sentence_detail_sheet.dart
git commit -m "feat: add SentenceDetailPanel for home split view"
```
---
## Phase 3: 发现页分屏适配
### Task 9: ChatFlowPanel 会话流详情面板
**Files:**
- Create: `lib/features/discover/presentation/panels/chat_flow_panel.dart`
- Create: `lib/features/discover/presentation/panels/discover_dashboard.dart`
- [ ] **Step 1: 创建ChatFlowPanel**
将ChatFlowPage的核心内容提取为可嵌入面板的Widget。
面板内包含:导航栏+搜索栏+分类栏+消息列表+输入栏。
需要处理键盘管理在面板内的适配。
- [ ] **Step 2: 修改DiscoverPage宽屏点击判断**
点击会话行时:
- 宽屏:设置 discoverRightPanelProvider 为 'chat_flow'
- 窄屏:保持全屏导航
- [ ] **Step 3: 验证**
- [ ] **Step 4: Commit**
```bash
git add lib/features/discover/presentation/panels/ lib/features/discover/presentation/pages/home/discover_page.dart
git commit -m "feat: add ChatFlowPanel for discover split view"
```
---
## Phase 4: 我的页面分屏适配
### Task 10: 设置项面板集合
**Files:**
- Create: `lib/features/mine/settings/presentation/panels/settings_panels.dart`
- Create: `lib/features/mine/profile/presentation/panels/profile_dashboard.dart`
- [ ] **Step 1: 创建各设置项面板**
为每个设置项创建面板Widget复用现有页面的内容区域。
面板包含:标题栏 + 设置内容。
- [ ] **Step 2: 修改ProfilePage宽屏点击判断**
- [ ] **Step 3: 验证**
- [ ] **Step 4: Commit**
```bash
git add lib/features/mine/ lib/features/mine/profile/presentation/panels/
git commit -m "feat: add settings panels for profile split view"
```
---
## Phase 5: 通用设置新增项
### Task 11: 导航栏位置与分屏设置
**Files:**
- Modify: `lib/features/mine/settings/presentation/general/general_settings_sections.dart`
- [ ] **Step 1: 在显示设置分组中新增3项**
1. 导航栏位置selectionleft/right/top/bottom
2. 分屏默认比例selection30%-60%
3. 分屏功能toggleon/off
- [ ] **Step 2: 验证**
- [ ] **Step 3: Commit**
```bash
git add lib/features/mine/settings/presentation/general/general_settings_sections.dart
git commit -m "feat: add nav bar position and split view settings"
```
---
## Phase 6: 动画优化与最终验证
### Task 12: 动画与过渡优化
- [ ] **Step 1: 优化右侧面板滑入/滑出动画**
确保AnimationController在宽屏↔窄屏切换时正确触发。
- [ ] **Step 2: 添加导航栏位置切换动画**
使用AnimatedSwitcher包裹NavBar。
- [ ] **Step 3: 添加概览仪表盘入场动画**
使用flutter_animate添加fadeIn + slideY + stagger。
- [ ] **Step 4: 全量测试**
验证所有断点、主题、手势隔离、面板切换。
- [ ] **Step 5: Commit**
```bash
git add -A
git commit -m "feat: polish animations and transitions for widescreen adaptation"
```
---
## Phase 7: CHANGELOG与清理
### Task 13: 更新CHANGELOG.md
- [ ] **Step 1: 添加版本记录**
在CHANGELOG.md中添加宽屏适配功能记录。
- [ ] **Step 2: 删除spec文档按用户要求**
删除 docs/specs/ 下的设计文档(开发完成后)。
- [ ] **Step 3: 最终Commit**
```bash
git add CHANGELOG.md
git commit -m "docs: update CHANGELOG for widescreen adaptation feature"
```