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 手势,支持双指缩放和旋转
155 lines
4.9 KiB
Dart
155 lines
4.9 KiB
Dart
// 远程日记仓库 — 通过 API 客户端连接后端
|
|
|
|
import '../models/journal_element.dart';
|
|
import '../models/journal_entry.dart';
|
|
import '../remote/api_client.dart';
|
|
import 'journal_repository.dart';
|
|
|
|
/// 远程日记仓库 — 通过 HTTP API 操作后端数据
|
|
///
|
|
/// 所有操作需要网络连接。离线场景由 SyncEngine 协调 Isar 本地仓库处理。
|
|
class RemoteJournalRepository implements JournalRepository {
|
|
final ApiClient _api;
|
|
|
|
RemoteJournalRepository({required ApiClient api}) : _api = api;
|
|
|
|
@override
|
|
Future<List<JournalEntry>> getJournals({
|
|
DateTime? dateFrom,
|
|
DateTime? dateTo,
|
|
int? page,
|
|
int? pageSize,
|
|
String? mood,
|
|
String? tag,
|
|
String? classId,
|
|
}) async {
|
|
final queryParams = <String, dynamic>{};
|
|
// 后端 NaiveDateTime 格式: "2026-06-01T00:00:00"(不带毫秒)
|
|
if (dateFrom != null) {
|
|
queryParams['date_from'] = dateFrom.toIso8601String().replaceFirst(RegExp(r'\.\d+'), '');
|
|
}
|
|
if (dateTo != null) {
|
|
queryParams['date_to'] = dateTo.toIso8601String().replaceFirst(RegExp(r'\.\d+'), '');
|
|
}
|
|
if (page != null) queryParams['page'] = page;
|
|
if (pageSize != null) queryParams['page_size'] = pageSize;
|
|
if (mood != null) queryParams['mood'] = mood;
|
|
if (tag != null) queryParams['tag'] = tag;
|
|
if (classId != null) queryParams['class_id'] = classId;
|
|
|
|
final response = await _api.get('/diary/journals', queryParams: queryParams);
|
|
final body = response.data as Map<String, dynamic>;
|
|
final items = body['data'] as List? ?? [];
|
|
return items
|
|
.map((json) => JournalEntry.fromJson(json as Map<String, dynamic>))
|
|
.toList();
|
|
}
|
|
|
|
@override
|
|
Future<int> getJournalCount() async {
|
|
final response = await _api.get('/diary/journals', queryParams: {
|
|
'page': 1,
|
|
'page_size': 1,
|
|
});
|
|
final body = response.data as Map<String, dynamic>;
|
|
return (body['total'] as int?) ?? 0;
|
|
}
|
|
|
|
@override
|
|
Future<JournalEntry?> getJournal(String id) async {
|
|
try {
|
|
final response = await _api.get('/diary/journals/$id');
|
|
final body = response.data as Map<String, dynamic>;
|
|
return JournalEntry.fromJson(body['data'] as Map<String, dynamic>);
|
|
} on ApiException catch (e) {
|
|
if (e.statusCode == 404) return null;
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<JournalEntry> createJournal(JournalEntry entry) async {
|
|
final response = await _api.post('/diary/journals', data: entry.toJson());
|
|
final body = response.data as Map<String, dynamic>;
|
|
return JournalEntry.fromJson(body['data'] as Map<String, dynamic>);
|
|
}
|
|
|
|
@override
|
|
Future<JournalEntry> updateJournal(JournalEntry entry) async {
|
|
final response = await _api.put(
|
|
'/diary/journals/${entry.id}',
|
|
data: {
|
|
'title': entry.title,
|
|
'mood': entry.mood.value,
|
|
'weather': entry.weather.value,
|
|
'tags': entry.tags,
|
|
'is_private': entry.isPrivate,
|
|
'shared_to_class': entry.sharedToClass,
|
|
'version': entry.version,
|
|
},
|
|
);
|
|
final body = response.data as Map<String, dynamic>;
|
|
return JournalEntry.fromJson(body['data'] as Map<String, dynamic>);
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteJournal(String id) async {
|
|
await _api.delete('/diary/journals/$id');
|
|
}
|
|
|
|
@override
|
|
Future<List<JournalElement>> getElements(String journalId) async {
|
|
final response = await _api.get('/diary/journals/$journalId/elements');
|
|
final body = response.data as Map<String, dynamic>;
|
|
final items = body['data'] as List? ?? [];
|
|
return items
|
|
.map((json) => JournalElement.fromJson(json as Map<String, dynamic>))
|
|
.toList();
|
|
}
|
|
|
|
@override
|
|
Future<JournalElement> addElement(JournalElement element) async {
|
|
final response = await _api.post(
|
|
'/diary/journals/${element.journalId}/elements',
|
|
data: element.toJson(),
|
|
);
|
|
final body = response.data as Map<String, dynamic>;
|
|
return JournalElement.fromJson(body['data'] as Map<String, dynamic>);
|
|
}
|
|
|
|
@override
|
|
Future<JournalElement> updateElement(JournalElement element) async {
|
|
final response = await _api.put(
|
|
'/diary/journals/${element.journalId}/elements/${element.id}',
|
|
data: element.toJson(),
|
|
);
|
|
final body = response.data as Map<String, dynamic>;
|
|
return JournalElement.fromJson(body['data'] as Map<String, dynamic>);
|
|
}
|
|
|
|
@override
|
|
Future<void> removeElement(String elementId) async {
|
|
await _api.delete('/diary/elements/$elementId');
|
|
}
|
|
|
|
/// 远程仓库不提供本地变更通知,返回空流
|
|
@override
|
|
Stream<void> get onJournalChanged => const Stream<void>.empty();
|
|
}
|
|
|
|
/// API 异常封装 — 后端返回非 2xx 状态码时抛出
|
|
class ApiException implements Exception {
|
|
final String message;
|
|
final int statusCode;
|
|
final dynamic responseBody;
|
|
|
|
const ApiException({
|
|
required this.message,
|
|
required this.statusCode,
|
|
this.responseBody,
|
|
});
|
|
|
|
@override
|
|
String toString() => 'ApiException($statusCode): $message';
|
|
}
|