Files
nj/app/lib/features/home/bloc/home_bloc.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

192 lines
5.0 KiB
Dart
Raw 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.
// 首页 BLoC — 加载最近日记和心情概览
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:nuanji_app/data/models/journal_entry.dart';
import 'package:nuanji_app/data/repositories/journal_repository.dart';
// ===== Events =====
sealed class HomeEvent {
const HomeEvent();
}
/// 加载首页数据(最近日记 + 心情概览)
final class HomeLoadData extends HomeEvent {
const HomeLoadData();
}
/// 刷新首页
final class HomeRefresh extends HomeEvent {
const HomeRefresh();
}
// ===== State =====
/// 首页状态
sealed class HomeState {
const HomeState();
}
final class HomeInitial extends HomeState {
const HomeInitial();
}
final class HomeLoading extends HomeState {
const HomeLoading();
}
final class HomeLoaded extends HomeState {
/// 最近日记(取最新 10 条)
final List<JournalEntry> recentJournals;
/// 今日是否已写日记
final bool hasTodayEntry;
/// 最近常用心情
final Mood? topMood;
/// 连续写日记天数(从日记列表推算)
final int streakDays;
/// 本月日记数spec §3.4 quick-stats
final int monthCount;
/// 总日记数spec §3.4 quick-stats
final int totalCount;
/// 今日天气从今日日记中提取null 则默认晴)
final Weather? todayWeather;
const HomeLoaded({
this.recentJournals = const [],
this.hasTodayEntry = false,
this.topMood,
this.streakDays = 0,
this.monthCount = 0,
this.totalCount = 0,
this.todayWeather,
});
}
final class HomeError extends HomeState {
final String message;
const HomeError(this.message);
}
// ===== BLoC =====
class HomeBloc extends Bloc<HomeEvent, HomeState> {
final JournalRepository _journalRepo;
StreamSubscription<void>? _changeSubscription;
HomeBloc({required JournalRepository journalRepository})
: _journalRepo = journalRepository,
super(const HomeInitial()) {
on<HomeLoadData>(_onLoadData);
on<HomeRefresh>(_onRefresh);
// 监听日记变更,自动刷新首页数据
_changeSubscription = _journalRepo.onJournalChanged.listen((_) {
add(const HomeRefresh());
});
}
@override
Future<void> close() {
_changeSubscription?.cancel();
return super.close();
}
Future<void> _onLoadData(
HomeLoadData event,
Emitter<HomeState> emit,
) async {
emit(const HomeLoading());
try {
final journals = await _journalRepo.getJournals(
page: 1,
pageSize: 10,
);
// 检查今日是否已写日记
final today = DateTime.now();
final hasTodayEntry = journals.any((j) =>
j.date.year == today.year &&
j.date.month == today.month &&
j.date.day == today.day);
// 推算最常用心情
final moodCounts = <Mood, int>{};
for (final j in journals) {
moodCounts[j.mood] = (moodCounts[j.mood] ?? 0) + 1;
}
final topMood = moodCounts.entries
.fold<MapEntry<Mood, int>?>(null, (a, b) => a == null || b.value > a.value ? b : a)
?.key;
// 推算连续天数
final streakDays = _calculateStreak(journals);
// 本月日记数 — 使用日期范围查询,不受分页限制(修复 8b-D03
final monthStart = DateTime(today.year, today.month, 1);
final monthEnd = DateTime(today.year, today.month + 1, 1);
final monthJournals = await _journalRepo.getJournals(
dateFrom: monthStart,
dateTo: monthEnd,
);
final monthCount = monthJournals.length;
// 总日记数 — 使用仓库计数方法(不受分页限制)
final totalCount = await _journalRepo.getJournalCount();
// 今日天气 — 从今日日记中提取
final todayJournal = journals.firstWhere(
(j) => j.date.year == today.year &&
j.date.month == today.month &&
j.date.day == today.day,
orElse: () => journals.first, // fallback for type safety
);
final todayWeather = hasTodayEntry ? todayJournal.weather : null;
emit(HomeLoaded(
recentJournals: journals,
hasTodayEntry: hasTodayEntry,
topMood: topMood,
streakDays: streakDays,
monthCount: monthCount,
totalCount: totalCount,
todayWeather: todayWeather,
));
} catch (e) {
debugPrint('HomeBloc._onLoadData 失败: $e');
emit(const HomeLoaded()); // 空状态而非错误,离线友好
}
}
Future<void> _onRefresh(
HomeRefresh event,
Emitter<HomeState> emit,
) async {
add(const HomeLoadData());
}
/// 从日记列表推算连续写日记天数
int _calculateStreak(List<JournalEntry> journals) {
if (journals.isEmpty) return 0;
final dates = journals.map((j) => j.date).toSet();
var streak = 0;
var checkDate = DateTime.now();
while (dates.contains(DateTime(checkDate.year, checkDate.month, checkDate.day))) {
streak++;
checkDate = checkDate.subtract(const Duration(days: 1));
}
return streak;
}
}