1. 首页数据不刷新 — JournalRepository 添加 onJournalChanged Stream 变更通知,HomeBloc 订阅后自动刷新 2. 画笔再次点击不弹出面板 — 添加 ToolReactivated 事件, 工具栏检测已激活工具时发出重新激活信号 3. 钢笔铅笔效果一样 — 调整 perfect_freehand 参数 (pen: size 10/smooth 0.65, pencil: size 3/smooth 0.35) 4. 橡皮擦不生效 — ActiveStrokePainter 橡皮擦模式绘制 半透明灰色反馈,笔画完成后 setState 触发 Layer 1 重绘 5. 贴纸文字无法缩放 — DraggableElement 用 Scale 手势 替换 Pan 手势,支持双指缩放和旋转
224 lines
6.3 KiB
Dart
224 lines
6.3 KiB
Dart
// 日记仓库 — 抽象接口 + 内存实现(开发测试用)
|
||
//
|
||
// 设计思路:
|
||
// - 抽象接口 [JournalRepository] 定义数据操作契约
|
||
// - 后续实现 IsarJournalRepository(本地)和 RemoteJournalRepository(远程)
|
||
// - SyncEngine 负责协调本地和远程仓库之间的数据同步
|
||
// - 内存实现 [InMemoryJournalRepository] 用于开发阶段快速迭代
|
||
|
||
import 'dart:async';
|
||
|
||
import '../models/journal_entry.dart';
|
||
import '../models/journal_element.dart';
|
||
|
||
/// 日记仓库抽象接口 — 所有数据操作的统一契约
|
||
///
|
||
/// 查询参数说明:
|
||
/// - [dateFrom]/[dateTo]: 日期范围过滤(闭区间)
|
||
/// - [page]/[pageSize]: 分页参数,从 1 开始
|
||
abstract class JournalRepository {
|
||
/// 获取日记列表(支持日期范围、心情、标签、班级过滤和分页)
|
||
Future<List<JournalEntry>> getJournals({
|
||
DateTime? dateFrom,
|
||
DateTime? dateTo,
|
||
int? page,
|
||
int? pageSize,
|
||
String? mood,
|
||
String? tag,
|
||
String? classId,
|
||
});
|
||
|
||
/// 获取日记总数
|
||
Future<int> getJournalCount();
|
||
|
||
/// 获取单篇日记(返回 null 表示不存在)
|
||
Future<JournalEntry?> getJournal(String id);
|
||
|
||
/// 创建新日记
|
||
Future<JournalEntry> createJournal(JournalEntry entry);
|
||
|
||
/// 更新日记(使用 version 字段做乐观锁冲突检测)
|
||
Future<JournalEntry> updateJournal(JournalEntry entry);
|
||
|
||
/// 删除日记(软删除,设置 deleted_at)
|
||
Future<void> deleteJournal(String id);
|
||
|
||
/// 获取指定日记的所有元素
|
||
Future<List<JournalElement>> getElements(String journalId);
|
||
|
||
/// 添加元素到日记
|
||
Future<JournalElement> addElement(JournalElement element);
|
||
|
||
/// 更新元素
|
||
Future<JournalElement> updateElement(JournalElement element);
|
||
|
||
/// 从日记中移除元素
|
||
Future<void> removeElement(String elementId);
|
||
|
||
/// 日记变更通知流 — create/update/delete 时发出信号
|
||
Stream<void> get onJournalChanged;
|
||
}
|
||
|
||
/// 内存实现 — 用于开发阶段快速迭代和单元测试
|
||
///
|
||
/// 数据存储在内存中,应用重启后丢失。
|
||
/// 线程安全说明:Flutter 是单线程模型,无需加锁。
|
||
class InMemoryJournalRepository implements JournalRepository {
|
||
final Map<String, JournalEntry> _journals = {};
|
||
final Map<String, JournalElement> _elements = {};
|
||
final StreamController<void> _changeController = StreamController<void>.broadcast();
|
||
|
||
@override
|
||
Stream<void> get onJournalChanged => _changeController.stream;
|
||
|
||
@override
|
||
Future<List<JournalEntry>> getJournals({
|
||
DateTime? dateFrom,
|
||
DateTime? dateTo,
|
||
int? page,
|
||
int? pageSize,
|
||
String? mood,
|
||
String? tag,
|
||
String? classId,
|
||
}) async {
|
||
var results = _journals.values.toList();
|
||
|
||
// 日期范围过滤
|
||
if (dateFrom != null) {
|
||
results = results.where((j) => j.date.isAfter(dateFrom)).toList();
|
||
}
|
||
if (dateTo != null) {
|
||
results = results.where((j) => j.date.isBefore(dateTo)).toList();
|
||
}
|
||
|
||
// 心情过滤
|
||
if (mood != null) {
|
||
results = results.where((j) => j.mood.value == mood).toList();
|
||
}
|
||
|
||
// 标签过滤(日记 tags 列表包含指定标签)
|
||
if (tag != null) {
|
||
results = results.where((j) => j.tags.contains(tag)).toList();
|
||
}
|
||
|
||
// 班级过滤
|
||
if (classId != null) {
|
||
results = results.where((j) => j.classId == classId).toList();
|
||
}
|
||
|
||
// 按日期降序排列(最新在前)
|
||
results.sort((a, b) => b.date.compareTo(a.date));
|
||
|
||
// 分页
|
||
if (page != null && pageSize != null) {
|
||
final start = (page - 1) * pageSize;
|
||
if (start >= results.length) return [];
|
||
final end = (start + pageSize).clamp(0, results.length);
|
||
results = results.sublist(start, end);
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
@override
|
||
Future<int> getJournalCount() async => _journals.length;
|
||
|
||
@override
|
||
Future<JournalEntry?> getJournal(String id) async {
|
||
return _journals[id];
|
||
}
|
||
|
||
@override
|
||
Future<JournalEntry> createJournal(JournalEntry entry) async {
|
||
_journals[entry.id] = entry;
|
||
_changeController.add(null);
|
||
return entry;
|
||
}
|
||
|
||
@override
|
||
Future<JournalEntry> updateJournal(JournalEntry entry) async {
|
||
final existing = _journals[entry.id];
|
||
if (existing == null) {
|
||
throw StateError('日记不存在: ${entry.id}');
|
||
}
|
||
|
||
// 乐观锁冲突检测:版本号必须匹配
|
||
if (existing.version != entry.version) {
|
||
throw StateError(
|
||
'版本冲突: 本地版本 ${entry.version}, 服务端版本 ${existing.version}',
|
||
);
|
||
}
|
||
|
||
// 版本号 +1,更新时间戳
|
||
final updated = entry.copyWith(
|
||
version: entry.version + 1,
|
||
updatedAt: DateTime.now(),
|
||
);
|
||
_journals[entry.id] = updated;
|
||
_changeController.add(null);
|
||
return updated;
|
||
}
|
||
|
||
@override
|
||
Future<void> deleteJournal(String id) async {
|
||
// 内存实现:直接移除(软删除由 Isar 实现处理)
|
||
_journals.remove(id);
|
||
// 同时移除关联元素
|
||
_elements.removeWhere((_, e) => e.journalId == id);
|
||
_changeController.add(null);
|
||
}
|
||
|
||
@override
|
||
Future<List<JournalElement>> getElements(String journalId) async {
|
||
return _elements.values
|
||
.where((e) => e.journalId == journalId)
|
||
.toList()
|
||
..sort((a, b) => a.zIndex.compareTo(b.zIndex));
|
||
}
|
||
|
||
@override
|
||
Future<JournalElement> addElement(JournalElement element) async {
|
||
_elements[element.id] = element;
|
||
return element;
|
||
}
|
||
|
||
@override
|
||
Future<JournalElement> updateElement(JournalElement element) async {
|
||
final existing = _elements[element.id];
|
||
if (existing == null) {
|
||
throw StateError('元素不存在: ${element.id}');
|
||
}
|
||
|
||
// 乐观锁冲突检测
|
||
if (existing.version != element.version) {
|
||
throw StateError(
|
||
'版本冲突: 本地版本 ${element.version}, 服务端版本 ${existing.version}',
|
||
);
|
||
}
|
||
|
||
final updated = element.copyWith(
|
||
version: element.version + 1,
|
||
updatedAt: DateTime.now(),
|
||
);
|
||
_elements[element.id] = updated;
|
||
return updated;
|
||
}
|
||
|
||
@override
|
||
Future<void> removeElement(String elementId) async {
|
||
_elements.remove(elementId);
|
||
}
|
||
|
||
/// 清空所有数据(测试辅助方法)
|
||
void clearAll() {
|
||
_journals.clear();
|
||
_elements.clear();
|
||
}
|
||
|
||
/// 获取日记总数(测试辅助方法)
|
||
int get journalCount => _journals.length;
|
||
|
||
/// 获取元素总数(测试辅助方法)
|
||
int get elementCount => _elements.length;
|
||
}
|