Files
nj/app/lib/data/repositories/journal_repository.dart
iven 9fce34f4ef
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
fix(app): 修复 4 个 Flutter 交互问题
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 手势,支持双指缩放和旋转
2026-06-04 00:05:22 +08:00

224 lines
6.3 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.
// 日记仓库 — 抽象接口 + 内存实现(开发测试用)
//
// 设计思路:
// - 抽象接口 [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;
}