Files
nj/app/lib/features/calendar/bloc/calendar_bloc.dart
iven 3c3d70c751
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
fix(app): 日历页切换月份后保留选中日期的日记列表
2026-06-07 10:44:55 +08:00

179 lines
5.1 KiB
Dart

// 日历 BLoC — 管理日历视图状态,通过 JournalRepository 加载数据
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 CalendarEvent {
const CalendarEvent();
}
/// 切换到指定月份
final class CalendarMonthChanged extends CalendarEvent {
final DateTime month;
const CalendarMonthChanged(this.month);
}
/// 选择某一天
final class CalendarDaySelected extends CalendarEvent {
final DateTime day;
const CalendarDaySelected(this.day);
}
/// 切换视图模式
final class CalendarViewModeChanged extends CalendarEvent {
final CalendarViewMode mode;
const CalendarViewModeChanged(this.mode);
}
// ===== State =====
/// 日历视图模式
enum CalendarViewMode { month, week, timeline }
/// 日历状态
sealed class CalendarState {
const CalendarState();
}
final class CalendarInitial extends CalendarState {
const CalendarInitial();
}
/// 日历已加载
final class CalendarLoaded extends CalendarState {
final DateTime focusedMonth;
final DateTime selectedDay;
final Map<DateTime, List<JournalEntry>> journalsByDate;
final List<JournalEntry> selectedDayJournals;
final CalendarViewMode viewMode;
final bool isLoading;
const CalendarLoaded({
required this.focusedMonth,
required this.selectedDay,
required this.journalsByDate,
required this.selectedDayJournals,
this.viewMode = CalendarViewMode.month,
this.isLoading = false,
});
CalendarLoaded copyWith({
DateTime? focusedMonth,
DateTime? selectedDay,
Map<DateTime, List<JournalEntry>>? journalsByDate,
List<JournalEntry>? selectedDayJournals,
CalendarViewMode? viewMode,
bool? isLoading,
}) =>
CalendarLoaded(
focusedMonth: focusedMonth ?? this.focusedMonth,
selectedDay: selectedDay ?? this.selectedDay,
journalsByDate: journalsByDate ?? this.journalsByDate,
selectedDayJournals: selectedDayJournals ?? this.selectedDayJournals,
viewMode: viewMode ?? this.viewMode,
isLoading: isLoading ?? this.isLoading,
);
}
final class CalendarError extends CalendarState {
final String message;
const CalendarError(this.message);
}
// ===== BLoC =====
class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
final JournalRepository _journalRepo;
CalendarBloc({required JournalRepository journalRepository})
: _journalRepo = journalRepository,
super(const CalendarInitial()) {
on<CalendarMonthChanged>(_onMonthChanged);
on<CalendarDaySelected>(_onDaySelected);
on<CalendarViewModeChanged>(_onViewModeChanged);
}
Future<void> _onMonthChanged(
CalendarMonthChanged event,
Emitter<CalendarState> emit,
) async {
final currentState = state is CalendarLoaded ? state as CalendarLoaded : null;
emit(CalendarLoaded(
focusedMonth: event.month,
selectedDay: event.month,
journalsByDate: currentState?.journalsByDate ?? {},
selectedDayJournals: [],
viewMode: currentState?.viewMode ?? CalendarViewMode.month,
isLoading: true,
));
try {
// 加载当月日记
final startOfMonth = DateTime(event.month.year, event.month.month, 1);
final endOfMonth = DateTime(event.month.year, event.month.month + 1, 0);
final journals = await _journalRepo.getJournals(
dateFrom: startOfMonth,
dateTo: endOfMonth,
);
// 按日期索引
final byDate = <DateTime, List<JournalEntry>>{};
for (final journal in journals) {
final key = DateTime(journal.date.year, journal.date.month, journal.date.day);
byDate.putIfAbsent(key, () => []).add(journal);
}
if (state is CalendarLoaded) {
final current = state as CalendarLoaded;
// 根据当前选中日期查找日记,避免进入页面时空白
final dayKey = DateTime(
current.selectedDay.year,
current.selectedDay.month,
current.selectedDay.day,
);
final selectedJournals = byDate[dayKey] ?? [];
emit(current.copyWith(
journalsByDate: byDate,
selectedDayJournals: selectedJournals,
isLoading: false,
));
}
} catch (e) {
debugPrint('CalendarBloc._onMonthChanged 失败: $e');
if (state is CalendarLoaded) {
emit((state as CalendarLoaded).copyWith(isLoading: false));
}
}
}
void _onDaySelected(
CalendarDaySelected event,
Emitter<CalendarState> emit,
) {
if (state is! CalendarLoaded) return;
final current = state as CalendarLoaded;
final dayKey = DateTime(event.day.year, event.day.month, event.day.day);
final dayJournals = current.journalsByDate[dayKey] ?? [];
emit(current.copyWith(
selectedDay: event.day,
selectedDayJournals: dayJournals,
));
}
void _onViewModeChanged(
CalendarViewModeChanged event,
Emitter<CalendarState> emit,
) {
if (state is! CalendarLoaded) return;
emit((state as CalendarLoaded).copyWith(viewMode: event.mode));
}
}