Flutter 手写引擎 (Phase F3): - stroke_model.dart: 笔画数据模型 (StrokePoint/Stroke/BrushType) - stroke_renderer.dart: perfect_freehand 渲染管线 + 四画笔参数 - handwriting_canvas.dart: Listener 输入 + 掌心抑制 + 去抖过滤 - editor_bloc.dart: BLoC 状态管理 + 撤销/重做 (50步) Rust 日记 CRUD + 同步 (Phase B2): - journal_service.rs: CRUD + 软删除 + 分页列表 + 事件发布 - sync_service.rs: 版本号同步 + 冲突检测 - journal_handler.rs: 5个API端点 + utoipa注解 + 权限守卫 - sync_handler.rs: 同步API端点 - error.rs: From<DiaryError> for AppError + 8个单元测试 - 路由注册: /diary/journals + /diary/sync 验证: - cargo check: 0 error - cargo test: 433 测试全通过 - flutter analyze: 1 warning (unused private param)
112 lines
2.6 KiB
Dart
112 lines
2.6 KiB
Dart
// 笔画数据模型 — 手写不可变类(避免 build_runner 依赖)
|
|
|
|
import 'dart:collection';
|
|
|
|
/// 画笔类型
|
|
enum BrushType {
|
|
pen('pen'),
|
|
pencil('pencil'),
|
|
marker('marker'),
|
|
eraser('eraser');
|
|
|
|
const BrushType(this.value);
|
|
final String value;
|
|
}
|
|
|
|
/// 笔画点
|
|
class StrokePoint {
|
|
const StrokePoint({
|
|
required this.x,
|
|
required this.y,
|
|
this.pressure = 0.5,
|
|
this.timestamp = 0,
|
|
});
|
|
|
|
final double x;
|
|
final double y;
|
|
final double pressure;
|
|
final int timestamp;
|
|
|
|
StrokePoint copyWith({
|
|
double? x,
|
|
double? y,
|
|
double? pressure,
|
|
int? timestamp,
|
|
}) =>
|
|
StrokePoint(
|
|
x: x ?? this.x,
|
|
y: y ?? this.y,
|
|
pressure: pressure ?? this.pressure,
|
|
timestamp: timestamp ?? this.timestamp,
|
|
);
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'x': x,
|
|
'y': y,
|
|
'pressure': pressure,
|
|
'timestamp': timestamp,
|
|
};
|
|
|
|
factory StrokePoint.fromJson(Map<String, dynamic> json) => StrokePoint(
|
|
x: (json['x'] as num).toDouble(),
|
|
y: (json['y'] as num).toDouble(),
|
|
pressure: (json['pressure'] as num?)?.toDouble() ?? 0.5,
|
|
timestamp: (json['timestamp'] as int?) ?? 0,
|
|
);
|
|
}
|
|
|
|
/// 笔画
|
|
class Stroke {
|
|
const Stroke({
|
|
required this.id,
|
|
required this.points,
|
|
this.brushType = BrushType.pen,
|
|
this.color = '#2D2420',
|
|
this.width = 3.0,
|
|
});
|
|
|
|
final String id;
|
|
final List<StrokePoint> points;
|
|
final BrushType brushType;
|
|
final String color;
|
|
final double width;
|
|
|
|
Stroke copyWith({
|
|
String? id,
|
|
List<StrokePoint>? points,
|
|
BrushType? brushType,
|
|
String? color,
|
|
double? width,
|
|
}) =>
|
|
Stroke(
|
|
id: id ?? this.id,
|
|
points: points ?? this.points,
|
|
brushType: brushType ?? this.brushType,
|
|
color: color ?? this.color,
|
|
width: width ?? this.width,
|
|
);
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'points': points.map((p) => p.toJson()).toList(),
|
|
'brushType': brushType.value,
|
|
'color': color,
|
|
'width': width,
|
|
};
|
|
|
|
factory Stroke.fromJson(Map<String, dynamic> json) => Stroke(
|
|
id: json['id'] as String,
|
|
points: UnmodifiableListView(
|
|
(json['points'] as List)
|
|
.map((p) => StrokePoint.fromJson(p as Map<String, dynamic>))
|
|
.toList(),
|
|
),
|
|
brushType: BrushType.values.firstWhere(
|
|
(b) => b.value == json['brushType'],
|
|
orElse: () => BrushType.pen,
|
|
),
|
|
color: (json['color'] as String?) ?? '#2D2420',
|
|
width: (json['width'] as num?)?.toDouble() ?? 3.0,
|
|
);
|
|
}
|