Files
nj/app/lib/features/editor/bloc/editor_bloc.dart
iven d0653614e0 feat(diary): 手写引擎 + 日记 CRUD + 同步 API (Phase F3 + B2)
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)
2026-06-01 00:36:05 +08:00

137 lines
3.5 KiB
Dart

// 编辑器 BLoC — 手写状态管理 + 撤销/重做
import 'package:flutter_bloc/flutter_bloc.dart';
import '../widgets/stroke_model.dart';
// ===== Events =====
abstract class EditorEvent {}
class BrushChanged extends EditorEvent {
final BrushType type;
final String color;
final double width;
BrushChanged({required this.type, required this.color, required this.width});
}
class StrokeCompleted extends EditorEvent {
final Stroke stroke;
StrokeCompleted(this.stroke);
}
class Undo extends EditorEvent {}
class Redo extends EditorEvent {}
class ClearCanvas extends EditorEvent {}
class StrokesLoaded extends EditorEvent {
final List<Stroke> strokes;
StrokesLoaded(this.strokes);
}
// ===== State =====
class EditorState {
final List<Stroke> strokes;
final List<Stroke> redoStack;
final BrushType brushType;
final String brushColor;
final double brushWidth;
final int maxUndoSteps;
const EditorState({
this.strokes = const [],
this.redoStack = const [],
this.brushType = BrushType.pen,
this.brushColor = '#2D2420',
this.brushWidth = 3.0,
this.maxUndoSteps = 50,
});
EditorState copyWith({
List<Stroke>? strokes,
List<Stroke>? redoStack,
BrushType? brushType,
String? brushColor,
double? brushWidth,
}) =>
EditorState(
strokes: strokes ?? this.strokes,
redoStack: redoStack ?? this.redoStack,
brushType: brushType ?? this.brushType,
brushColor: brushColor ?? this.brushColor,
brushWidth: brushWidth ?? this.brushWidth,
maxUndoSteps: maxUndoSteps,
);
}
// ===== BLoC =====
class EditorBloc extends Bloc<EditorEvent, EditorState> {
EditorBloc() : super(const EditorState()) {
on<BrushChanged>(_onBrushChanged);
on<StrokeCompleted>(_onStrokeCompleted);
on<Undo>(_onUndo);
on<Redo>(_onRedo);
on<ClearCanvas>(_onClearCanvas);
on<StrokesLoaded>(_onStrokesLoaded);
}
void _onBrushChanged(BrushChanged event, Emitter<EditorState> emit) {
emit(state.copyWith(
brushType: event.type,
brushColor: event.color,
brushWidth: event.width,
));
}
void _onStrokeCompleted(StrokeCompleted event, Emitter<EditorState> emit) {
final updatedStrokes = List<Stroke>.from(state.strokes)..add(event.stroke);
// 超过最大撤销步数时移除最旧的
if (updatedStrokes.length > state.maxUndoSteps) {
updatedStrokes.removeAt(0);
}
emit(state.copyWith(
strokes: updatedStrokes,
redoStack: [], // 新笔画清空重做栈
));
}
void _onUndo(Undo event, Emitter<EditorState> emit) {
if (state.strokes.isEmpty) return;
final updatedStrokes = List<Stroke>.from(state.strokes);
final lastStroke = updatedStrokes.removeLast();
final updatedRedoStack = List<Stroke>.from(state.redoStack)..add(lastStroke);
emit(state.copyWith(
strokes: updatedStrokes,
redoStack: updatedRedoStack,
));
}
void _onRedo(Redo event, Emitter<EditorState> emit) {
if (state.redoStack.isEmpty) return;
final updatedRedoStack = List<Stroke>.from(state.redoStack);
final stroke = updatedRedoStack.removeLast();
final updatedStrokes = List<Stroke>.from(state.strokes)..add(stroke);
emit(state.copyWith(
strokes: updatedStrokes,
redoStack: updatedRedoStack,
));
}
void _onClearCanvas(ClearCanvas event, Emitter<EditorState> emit) {
emit(state.copyWith(strokes: [], redoStack: []));
}
void _onStrokesLoaded(StrokesLoaded event, Emitter<EditorState> emit) {
emit(state.copyWith(strokes: event.strokes, redoStack: []));
}
}