本次提交包含多项核心更新: 1. 全量替换项目内所有xianyan.app域名变更为s2ss.com,包含配置文件、路由、隐私政策等 2. 重构图表库从fl_chart迁移至syncfusion_flutter_charts,优化图表渲染效果 3. 新增宽屏分屏布局支持,包含右侧面板注册表与可拖拽分割线 4. 完善触觉反馈服务与认证感知Mixin,修复多处内存泄漏问题 5. 合并勋章墙与金币记录入口至成就中心,简化个人中心导航 6. 新增收藏与时间线数据合并导入功能 7. 修复多处UI样式问题,统一主题颜色使用规范 8. 新增日历同步与跨平台触觉反馈依赖库 9. 修复BotToast初始化流程,避免路由切换时的弹窗崩溃
102 lines
3.1 KiB
Dart
102 lines
3.1 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 可拖拽分割线
|
||
/// 创建时间: 2026-05-29
|
||
/// 更新时间: 2026-05-29
|
||
/// 作用: 宽屏分屏的分割线组件,支持拖拽调整比例、hover高亮、触觉反馈
|
||
/// 上次更新: 初始创建
|
||
/// ============================================================
|
||
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:flutter/services.dart';
|
||
|
||
import '../theme/app_theme.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);
|
||
}
|
||
}
|