Files
xianyan/lib/core/layout/adaptive_split_view.dart
Developer 83720002e6 feat: 新增工作台模式、系统托盘,修复多平台兼容性问题
1. 新增工作台三栏布局模式,适配宽屏设备
2. 添加跨平台系统托盘支持,新增托盘图标资源
3. 修复工作台模式下导航返回异常问题
4. 统一JSON类型安全解析,替换硬类型转换
5. 增加macOS深度链接支持,统一渠道分发信息
6. 优化部分页面生命周期和状态加载逻辑
7. 移除废弃的nearby_connections依赖
2026-06-19 06:43:55 +08:00

127 lines
4.4 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-29
/// 更新时间: 2026-06-18
/// 作用: 宽屏时左右分屏布局,支持可拖拽分割线、手势隔离、动画过渡
/// 上次更新: 断点常量对齐设计规则 768/1024/1280支持工作台三栏模式
/// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/split_view_provider.dart';
import 'panel_cache.dart';
import 'split_divider.dart';
// ============================================================
// 响应式断点常量 — 对齐 .trae/rules/design-rules.md
// ============================================================
/// 紧凑模式断点:宽度 >= 768px 从单栏切换到双栏
const double kCompactBreakpoint = 768.0;
/// 中等模式断点:宽度 >= 1024px 从双栏切换到三栏
const double kMediumBreakpoint = 1024.0;
/// 展开模式断点:宽度 >= 1280px 三栏完整显示(中栏更宽)
const double kExpandedBreakpoint = 1280.0;
/// @deprecated 旧断点,保留向后兼容,实际指向 kCompactBreakpoint
const double kSplitViewBreakpoint = kCompactBreakpoint;
/// 判断当前宽度是否处于工作台模式(双栏或三栏)
bool isWorkbenchWidth(double width) => width >= kCompactBreakpoint;
/// 判断当前宽度是否处于三栏模式
bool isTripleColumnWidth(double width) => width >= kMediumBreakpoint;
/// 判断当前宽度是否处于三栏完整模式(中栏更宽)
bool isExpandedTripleColumnWidth(double width) => width >= kExpandedBreakpoint;
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> {
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.sizeOf(context).width;
final splitState = ref.watch(splitViewProvider);
final isSplitView =
screenWidth >= kSplitViewBreakpoint && splitState.splitViewEnabled;
final splitRatio = splitState.splitRatio;
final hasRightContent = splitState.activeRightPanel != null;
if (!isSplitView) {
return widget.leftPanel;
}
final leftWidth = screenWidth * splitRatio;
const double dividerWidth = 17.0;
final double clampedLeftWidth = leftWidth.clamp(
widget.minLeftWidth,
screenWidth - widget.minRightWidth - dividerWidth,
);
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
width: clampedLeftWidth,
child: KeepAlivePanelWrapper(
child: NotificationListener<ScrollNotification>(
onNotification: (_) => true,
child: widget.leftPanel,
),
),
),
SplitDivider(
currentPosition: splitRatio,
onPositionChanged: (newRatio) {
ref.read(splitViewProvider.notifier).setSplitRatio(newRatio);
},
minPosition: widget.minLeftWidth / screenWidth,
maxPosition:
(screenWidth - widget.minRightWidth - dividerWidth) / screenWidth,
),
SizedBox(
width: (screenWidth - clampedLeftWidth - dividerWidth).clamp(
widget.minRightWidth,
screenWidth - widget.minLeftWidth - dividerWidth,
),
child: KeepAlivePanelWrapper(
child:
NotificationListener<ScrollNotification>(
onNotification: (_) => true,
child: widget.rightPanel,
)
.animate(target: hasRightContent ? 1.0 : 0.0)
.slideX(
begin: 1.0,
end: 0.0,
duration: 350.ms,
curve: Curves.easeInOutCubic,
)
.fadeIn(duration: 200.ms),
),
),
],
);
}
}