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 手势,支持双指缩放和旋转
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
// - JournalEntry ↔ JournalEntryCollection(通过 toCollection/fromCollection)
|
// - JournalEntry ↔ JournalEntryCollection(通过 toCollection/fromCollection)
|
||||||
// - JournalElement ↔ JournalElementCollection(通过 toCollection/fromCollection)
|
// - JournalElement ↔ JournalElementCollection(通过 toCollection/fromCollection)
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
@@ -22,6 +23,11 @@ import 'journal_repository.dart';
|
|||||||
class IsarJournalRepository implements JournalRepository {
|
class IsarJournalRepository implements JournalRepository {
|
||||||
Isar get _isar => IsarDatabase.instance;
|
Isar get _isar => IsarDatabase.instance;
|
||||||
|
|
||||||
|
final StreamController<void> _changeController = StreamController<void>.broadcast();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<void> get onJournalChanged => _changeController.stream;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 日记 CRUD
|
// 日记 CRUD
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -107,6 +113,7 @@ class IsarJournalRepository implements JournalRepository {
|
|||||||
await _isar.writeTxn(() async {
|
await _isar.writeTxn(() async {
|
||||||
await _isar.journalEntryCollections.put(col);
|
await _isar.journalEntryCollections.put(col);
|
||||||
});
|
});
|
||||||
|
_changeController.add(null);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +150,7 @@ class IsarJournalRepository implements JournalRepository {
|
|||||||
await _isar.journalEntryCollections.put(col);
|
await _isar.journalEntryCollections.put(col);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_changeController.add(null);
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +184,7 @@ class IsarJournalRepository implements JournalRepository {
|
|||||||
await _isar.journalElementCollections.put(el);
|
await _isar.journalElementCollections.put(el);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
_changeController.add(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import '../models/journal_entry.dart';
|
|||||||
import '../models/journal_element.dart';
|
import '../models/journal_element.dart';
|
||||||
import 'journal_repository.dart';
|
import 'journal_repository.dart';
|
||||||
|
|
||||||
|
/// 空的变更通知流 — Web 平台 stub
|
||||||
|
const _emptyStream = Stream<void>.empty();
|
||||||
|
|
||||||
/// Isar 本地日记仓库 — Web 空实现(抛出 UnsupportedError)
|
/// Isar 本地日记仓库 — Web 空实现(抛出 UnsupportedError)
|
||||||
class IsarJournalRepository implements JournalRepository {
|
class IsarJournalRepository implements JournalRepository {
|
||||||
@override
|
@override
|
||||||
@@ -56,4 +59,7 @@ class IsarJournalRepository implements JournalRepository {
|
|||||||
@override
|
@override
|
||||||
Future<void> removeElement(String elementId) =>
|
Future<void> removeElement(String elementId) =>
|
||||||
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
|
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<void> get onJournalChanged => _emptyStream;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
// - SyncEngine 负责协调本地和远程仓库之间的数据同步
|
// - SyncEngine 负责协调本地和远程仓库之间的数据同步
|
||||||
// - 内存实现 [InMemoryJournalRepository] 用于开发阶段快速迭代
|
// - 内存实现 [InMemoryJournalRepository] 用于开发阶段快速迭代
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import '../models/journal_entry.dart';
|
import '../models/journal_entry.dart';
|
||||||
import '../models/journal_element.dart';
|
import '../models/journal_element.dart';
|
||||||
|
|
||||||
@@ -52,6 +54,9 @@ abstract class JournalRepository {
|
|||||||
|
|
||||||
/// 从日记中移除元素
|
/// 从日记中移除元素
|
||||||
Future<void> removeElement(String elementId);
|
Future<void> removeElement(String elementId);
|
||||||
|
|
||||||
|
/// 日记变更通知流 — create/update/delete 时发出信号
|
||||||
|
Stream<void> get onJournalChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 内存实现 — 用于开发阶段快速迭代和单元测试
|
/// 内存实现 — 用于开发阶段快速迭代和单元测试
|
||||||
@@ -61,6 +66,10 @@ abstract class JournalRepository {
|
|||||||
class InMemoryJournalRepository implements JournalRepository {
|
class InMemoryJournalRepository implements JournalRepository {
|
||||||
final Map<String, JournalEntry> _journals = {};
|
final Map<String, JournalEntry> _journals = {};
|
||||||
final Map<String, JournalElement> _elements = {};
|
final Map<String, JournalElement> _elements = {};
|
||||||
|
final StreamController<void> _changeController = StreamController<void>.broadcast();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<void> get onJournalChanged => _changeController.stream;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<JournalEntry>> getJournals({
|
Future<List<JournalEntry>> getJournals({
|
||||||
@@ -122,6 +131,7 @@ class InMemoryJournalRepository implements JournalRepository {
|
|||||||
@override
|
@override
|
||||||
Future<JournalEntry> createJournal(JournalEntry entry) async {
|
Future<JournalEntry> createJournal(JournalEntry entry) async {
|
||||||
_journals[entry.id] = entry;
|
_journals[entry.id] = entry;
|
||||||
|
_changeController.add(null);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +155,7 @@ class InMemoryJournalRepository implements JournalRepository {
|
|||||||
updatedAt: DateTime.now(),
|
updatedAt: DateTime.now(),
|
||||||
);
|
);
|
||||||
_journals[entry.id] = updated;
|
_journals[entry.id] = updated;
|
||||||
|
_changeController.add(null);
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +165,7 @@ class InMemoryJournalRepository implements JournalRepository {
|
|||||||
_journals.remove(id);
|
_journals.remove(id);
|
||||||
// 同时移除关联元素
|
// 同时移除关联元素
|
||||||
_elements.removeWhere((_, e) => e.journalId == id);
|
_elements.removeWhere((_, e) => e.journalId == id);
|
||||||
|
_changeController.add(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -131,6 +131,10 @@ class RemoteJournalRepository implements JournalRepository {
|
|||||||
Future<void> removeElement(String elementId) async {
|
Future<void> removeElement(String elementId) async {
|
||||||
await _api.delete('/diary/elements/$elementId');
|
await _api.delete('/diary/elements/$elementId');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 远程仓库不提供本地变更通知,返回空流
|
||||||
|
@override
|
||||||
|
Stream<void> get onJournalChanged => const Stream<void>.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// API 异常封装 — 后端返回非 2xx 状态码时抛出
|
/// API 异常封装 — 后端返回非 2xx 状态码时抛出
|
||||||
|
|||||||
@@ -107,6 +107,12 @@ class ToolChanged extends EditorEvent {
|
|||||||
ToolChanged(this.tool);
|
ToolChanged(this.tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 再次点击已激活的工具 — 重新弹出设置面板
|
||||||
|
class ToolReactivated extends EditorEvent {
|
||||||
|
final EditorTool tool;
|
||||||
|
ToolReactivated(this.tool);
|
||||||
|
}
|
||||||
|
|
||||||
/// 加载已有元素
|
/// 加载已有元素
|
||||||
class ElementsLoaded extends EditorEvent {
|
class ElementsLoaded extends EditorEvent {
|
||||||
final List<JournalElement> elements;
|
final List<JournalElement> elements;
|
||||||
@@ -227,6 +233,9 @@ class EditorState {
|
|||||||
final bool isDirty;
|
final bool isDirty;
|
||||||
final DateTime? lastSavedAt;
|
final DateTime? lastSavedAt;
|
||||||
|
|
||||||
|
// 工具重新激活时间戳(用于驱动面板重新弹出)
|
||||||
|
final int toolReactivatedAt;
|
||||||
|
|
||||||
const EditorState({
|
const EditorState({
|
||||||
this.strokes = const [],
|
this.strokes = const [],
|
||||||
this.redoStack = const [],
|
this.redoStack = const [],
|
||||||
@@ -243,6 +252,7 @@ class EditorState {
|
|||||||
this.title = '',
|
this.title = '',
|
||||||
this.isDirty = false,
|
this.isDirty = false,
|
||||||
this.lastSavedAt,
|
this.lastSavedAt,
|
||||||
|
this.toolReactivatedAt = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
EditorState copyWith({
|
EditorState copyWith({
|
||||||
@@ -261,6 +271,7 @@ class EditorState {
|
|||||||
String? title,
|
String? title,
|
||||||
bool? isDirty,
|
bool? isDirty,
|
||||||
DateTime? lastSavedAt,
|
DateTime? lastSavedAt,
|
||||||
|
int? toolReactivatedAt,
|
||||||
}) =>
|
}) =>
|
||||||
EditorState(
|
EditorState(
|
||||||
strokes: strokes ?? this.strokes,
|
strokes: strokes ?? this.strokes,
|
||||||
@@ -279,6 +290,7 @@ class EditorState {
|
|||||||
title: title ?? this.title,
|
title: title ?? this.title,
|
||||||
isDirty: isDirty ?? this.isDirty,
|
isDirty: isDirty ?? this.isDirty,
|
||||||
lastSavedAt: lastSavedAt ?? this.lastSavedAt,
|
lastSavedAt: lastSavedAt ?? this.lastSavedAt,
|
||||||
|
toolReactivatedAt: toolReactivatedAt ?? this.toolReactivatedAt,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// 是否处于手写模式
|
/// 是否处于手写模式
|
||||||
@@ -330,6 +342,7 @@ class EditorBloc extends Bloc<EditorEvent, EditorState> {
|
|||||||
|
|
||||||
// 工具栏事件
|
// 工具栏事件
|
||||||
on<ToolChanged>(_onToolChanged);
|
on<ToolChanged>(_onToolChanged);
|
||||||
|
on<ToolReactivated>(_onToolReactivated);
|
||||||
|
|
||||||
// 标签/心情/标题事件
|
// 标签/心情/标题事件
|
||||||
on<TagAdded>(_onTagAdded);
|
on<TagAdded>(_onTagAdded);
|
||||||
@@ -514,6 +527,13 @@ class EditorBloc extends Bloc<EditorEvent, EditorState> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onToolReactivated(ToolReactivated event, Emitter<EditorState> emit) {
|
||||||
|
// 不改变 activeTool,仅递增时间戳驱动 UI 层重新弹出面板
|
||||||
|
emit(state.copyWith(
|
||||||
|
toolReactivatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 标签/心情/标题事件处理
|
// 标签/心情/标题事件处理
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -625,6 +625,7 @@ class _EditorStack extends StatefulWidget {
|
|||||||
|
|
||||||
class _EditorStackState extends State<_EditorStack> {
|
class _EditorStackState extends State<_EditorStack> {
|
||||||
EditorTool? _lastTool;
|
EditorTool? _lastTool;
|
||||||
|
int _lastReactivatedAt = 0;
|
||||||
late final TextEditingController _titleController;
|
late final TextEditingController _titleController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -675,6 +676,26 @@ class _EditorStackState extends State<_EditorStack> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
_lastTool = currentTool;
|
_lastTool = currentTool;
|
||||||
|
|
||||||
|
// 工具重新激活(再次点击已选中的工具)→ 重新弹出面板
|
||||||
|
final reactivatedAt = widget.state.toolReactivatedAt;
|
||||||
|
if (reactivatedAt != _lastReactivatedAt) {
|
||||||
|
_lastReactivatedAt = reactivatedAt;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
switch (currentTool) {
|
||||||
|
case EditorTool.brush:
|
||||||
|
_showBrushPanel();
|
||||||
|
case EditorTool.sticker:
|
||||||
|
_showStickerPicker();
|
||||||
|
case EditorTool.more:
|
||||||
|
_showMoreSheet();
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 显示贴纸选择底部面板
|
/// 显示贴纸选择底部面板
|
||||||
@@ -962,6 +983,19 @@ class _EditorStackState extends State<_EditorStack> {
|
|||||||
positionY: y,
|
positionY: y,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
onResized: (id, w, h) {
|
||||||
|
context.read<EditorBloc>().add(ElementResized(
|
||||||
|
elementId: id,
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
onRotated: (id, r) {
|
||||||
|
context.read<EditorBloc>().add(ElementRotated(
|
||||||
|
elementId: id,
|
||||||
|
rotation: r,
|
||||||
|
));
|
||||||
|
},
|
||||||
onDeleted: (id) {
|
onDeleted: (id) {
|
||||||
context.read<EditorBloc>().add(ElementRemoved(id));
|
context.read<EditorBloc>().add(ElementRemoved(id));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -53,6 +53,16 @@ class ActiveStrokePainter extends CustomPainter {
|
|||||||
|
|
||||||
final path = buildStrokePath(outlinePoints);
|
final path = buildStrokePath(outlinePoints);
|
||||||
|
|
||||||
|
// 橡皮擦实时反馈:绘制半透明灰色,让用户看到擦除范围
|
||||||
|
// 实际擦除在笔画完成后的合成图中通过 BlendMode.dstOut 执行
|
||||||
|
if (brushType == BrushType.eraser) {
|
||||||
|
canvas.drawPath(path, Paint()
|
||||||
|
..color = const Color(0x40808080) // 25% 灰色
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..isAntiAlias = true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 构造临时 Stroke 用于获取 Paint
|
// 构造临时 Stroke 用于获取 Paint
|
||||||
final stroke = Stroke(
|
final stroke = Stroke(
|
||||||
id: '__active__',
|
id: '__active__',
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ class DraggableElement extends StatefulWidget {
|
|||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final ValueChanged<String> onTap;
|
final ValueChanged<String> onTap;
|
||||||
final void Function(String id, double x, double y) onMoved;
|
final void Function(String id, double x, double y) onMoved;
|
||||||
|
final void Function(String id, double w, double h)? onResized;
|
||||||
|
final void Function(String id, double rotation)? onRotated;
|
||||||
final ValueChanged<String> onDeleted;
|
final ValueChanged<String> onDeleted;
|
||||||
|
|
||||||
const DraggableElement({
|
const DraggableElement({
|
||||||
@@ -27,6 +29,8 @@ class DraggableElement extends StatefulWidget {
|
|||||||
this.isSelected = false,
|
this.isSelected = false,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
required this.onMoved,
|
required this.onMoved,
|
||||||
|
this.onResized,
|
||||||
|
this.onRotated,
|
||||||
required this.onDeleted,
|
required this.onDeleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -41,6 +45,11 @@ class _DraggableElementState extends State<DraggableElement> {
|
|||||||
late double _height;
|
late double _height;
|
||||||
late double _rotation;
|
late double _rotation;
|
||||||
|
|
||||||
|
// Scale 手势状态
|
||||||
|
double _baseWidth = 0;
|
||||||
|
double _baseHeight = 0;
|
||||||
|
double _baseRotation = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -76,15 +85,35 @@ class _DraggableElementState extends State<DraggableElement> {
|
|||||||
child: Transform.rotate(
|
child: Transform.rotate(
|
||||||
angle: _rotation,
|
angle: _rotation,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
// 拖拽移动
|
// 缩放开始 — 记录基准值
|
||||||
onPanUpdate: (details) {
|
onScaleStart: (details) {
|
||||||
|
_baseWidth = _width;
|
||||||
|
_baseHeight = _height;
|
||||||
|
_baseRotation = _rotation;
|
||||||
|
},
|
||||||
|
// 缩放更新 — 支持单指拖拽 + 双指缩放/旋转
|
||||||
|
onScaleUpdate: (details) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_x += details.delta.dx;
|
// 拖拽(单指和双指都支持)
|
||||||
_y += details.delta.dy;
|
_x += details.focalPointDelta.dx;
|
||||||
|
_y += details.focalPointDelta.dy;
|
||||||
|
|
||||||
|
// 双指缩放 + 旋转
|
||||||
|
if (details.pointerCount >= 2) {
|
||||||
|
final newW = (_baseWidth * details.scale).clamp(40.0, 400.0);
|
||||||
|
final newH = (_baseHeight * details.scale).clamp(40.0, 400.0);
|
||||||
|
_width = newW;
|
||||||
|
_height = newH;
|
||||||
|
_rotation = _baseRotation + details.rotation;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
widget.onMoved(widget.element.id, _x, _y);
|
widget.onMoved(widget.element.id, _x, _y);
|
||||||
|
if (details.pointerCount >= 2) {
|
||||||
|
widget.onResized?.call(widget.element.id, _width, _height);
|
||||||
|
widget.onRotated?.call(widget.element.id, _rotation);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onPanEnd: (_) {
|
onScaleEnd: (_) {
|
||||||
// 确保最终位置已通知
|
// 确保最终位置已通知
|
||||||
widget.onMoved(widget.element.id, _x, _y);
|
widget.onMoved(widget.element.id, _x, _y);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class EditorToolbar extends StatelessWidget {
|
|||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => onEvent(ToolChanged(tool)),
|
onTap: () => onEvent(isActive ? ToolReactivated(tool) : ToolChanged(tool)),
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(minWidth: 36, minHeight: 36),
|
constraints: const BoxConstraints(minWidth: 36, minHeight: 36),
|
||||||
|
|||||||
@@ -235,7 +235,10 @@ class _HandwritingCanvasState extends State<HandwritingCanvas> {
|
|||||||
widget.onStrokeCompleted?.call(stroke);
|
widget.onStrokeCompleted?.call(stroke);
|
||||||
|
|
||||||
// 光栅化新笔画到缓存(异步,不阻塞 UI)
|
// 光栅化新笔画到缓存(异步,不阻塞 UI)
|
||||||
_cache.addStroke(stroke);
|
// 完成后 setState 确保 Layer 1 (CachedStrokesPainter) 用新合成图重绘
|
||||||
|
_cache.addStroke(stroke).then((_) {
|
||||||
|
if (mounted) setState(() {});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 指针取消(如来电打断):丢弃当前笔画。
|
/// 指针取消(如来电打断):丢弃当前笔画。
|
||||||
|
|||||||
@@ -39,19 +39,21 @@ class _BrushConfig {
|
|||||||
|
|
||||||
/// 各画笔的渲染参数。
|
/// 各画笔的渲染参数。
|
||||||
const Map<BrushType, _BrushConfig> _brushConfigs = {
|
const Map<BrushType, _BrushConfig> _brushConfigs = {
|
||||||
/// 钢笔:中等粗细,强压感变化,模拟毛笔效果
|
/// 钢笔:粗壮平滑,模拟签字笔效果
|
||||||
BrushType.pen: _BrushConfig(
|
BrushType.pen: _BrushConfig(
|
||||||
size: 8,
|
size: 10,
|
||||||
thinning: 0.7,
|
thinning: 0.65,
|
||||||
smoothing: 0.5,
|
smoothing: 0.65,
|
||||||
|
streamline: 0.6,
|
||||||
simulatePressure: true,
|
simulatePressure: true,
|
||||||
),
|
),
|
||||||
|
|
||||||
/// 铅笔:细线,轻微压感,高平滑度产生自然线条
|
/// 铅笔:纤细有质感,保留书写抖动
|
||||||
BrushType.pencil: _BrushConfig(
|
BrushType.pencil: _BrushConfig(
|
||||||
size: 4,
|
size: 3,
|
||||||
thinning: 0.3,
|
thinning: 0.4,
|
||||||
smoothing: 0.7,
|
smoothing: 0.35,
|
||||||
|
streamline: 0.3,
|
||||||
simulatePressure: true,
|
simulatePressure: true,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// 首页 BLoC — 加载最近日记和心情概览
|
// 首页 BLoC — 加载最近日记和心情概览
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:nuanji_app/data/models/journal_entry.dart';
|
import 'package:nuanji_app/data/models/journal_entry.dart';
|
||||||
@@ -78,12 +80,24 @@ final class HomeError extends HomeState {
|
|||||||
|
|
||||||
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||||
final JournalRepository _journalRepo;
|
final JournalRepository _journalRepo;
|
||||||
|
StreamSubscription<void>? _changeSubscription;
|
||||||
|
|
||||||
HomeBloc({required JournalRepository journalRepository})
|
HomeBloc({required JournalRepository journalRepository})
|
||||||
: _journalRepo = journalRepository,
|
: _journalRepo = journalRepository,
|
||||||
super(const HomeInitial()) {
|
super(const HomeInitial()) {
|
||||||
on<HomeLoadData>(_onLoadData);
|
on<HomeLoadData>(_onLoadData);
|
||||||
on<HomeRefresh>(_onRefresh);
|
on<HomeRefresh>(_onRefresh);
|
||||||
|
|
||||||
|
// 监听日记变更,自动刷新首页数据
|
||||||
|
_changeSubscription = _journalRepo.onJournalChanged.listen((_) {
|
||||||
|
add(const HomeRefresh());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_changeSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLoadData(
|
Future<void> _onLoadData(
|
||||||
|
|||||||
@@ -233,4 +233,7 @@ class _FailingJournalRepository implements JournalRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> removeElement(String elementId) async {}
|
Future<void> removeElement(String elementId) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<void> get onJournalChanged => const Stream<void>.empty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,6 +271,9 @@ class _FailingJournalRepository implements JournalRepository {
|
|||||||
Future<void> removeElement(String elementId) async {
|
Future<void> removeElement(String elementId) async {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<void> get onJournalChanged => const Stream<void>.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 在指定 bloc 上触发事件并等待处理完毕
|
/// 在指定 bloc 上触发事件并等待处理完毕
|
||||||
|
|||||||
Reference in New Issue
Block a user