// 首页 BLoC — 加载最近日记和心情概览 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 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 { final JournalRepository _journalRepo; HomeBloc({required JournalRepository journalRepository}) : _journalRepo = journalRepository, super(const HomeInitial()) { on(_onLoadData); on(_onRefresh); } Future _onLoadData( HomeLoadData event, Emitter 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 = {}; for (final j in journals) { moodCounts[j.mood] = (moodCounts[j.mood] ?? 0) + 1; } final topMood = moodCounts.entries .fold?>(null, (a, b) => a == null || b.value > a.value ? b : a) ?.key; // 推算连续天数 final streakDays = _calculateStreak(journals); // 本月日记数(spec §3.4 quick-stats) final monthCount = journals.where((j) => j.date.year == today.year && j.date.month == today.month).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 _onRefresh( HomeRefresh event, Emitter emit, ) async { add(const HomeLoadData()); } /// 从日记列表推算连续写日记天数 int _calculateStreak(List 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; } }