// 班级 BLoC — 管理班级状态、成员、日记墙、主题布置和评语 import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:nuanji_app/data/models/journal_entry.dart'; import 'package:nuanji_app/data/models/school_class.dart'; // ===== Events ===== sealed class ClassEvent { const ClassEvent(); } /// 加载我的班级列表 final class ClassLoadMyClasses extends ClassEvent { const ClassLoadMyClasses(); } /// 选择当前班级 final class ClassSelected extends ClassEvent { final String classId; const ClassSelected(this.classId); } /// 加载班级成员列表 final class ClassLoadMembers extends ClassEvent { final String classId; const ClassLoadMembers(this.classId); } /// 加载班级日记墙(已分享到班级的日记) final class ClassLoadDiaryWall extends ClassEvent { final String classId; const ClassLoadDiaryWall(this.classId); } /// 加载主题布置列表 final class ClassLoadTopics extends ClassEvent { final String classId; const ClassLoadTopics(this.classId); } /// 创建班级(老师) final class ClassCreate extends ClassEvent { final String name; final String? schoolName; const ClassCreate({required this.name, this.schoolName}); } /// 布置主题(老师) final class TopicAssign extends ClassEvent { final String classId; final String title; final String? description; final DateTime? dueDate; const TopicAssign({ required this.classId, required this.title, this.description, this.dueDate, }); } /// 加载日记评语 final class ClassLoadComments extends ClassEvent { final String journalId; const ClassLoadComments(this.journalId); } // ===== State ===== /// 班级成员模型 class ClassMember { final String userId; final String role; final String? nickname; final DateTime joinedAt; const ClassMember({ required this.userId, required this.role, this.nickname, required this.joinedAt, }); } /// 主题布置模型 class TopicAssignment { final String id; final String classId; final String teacherId; final String title; final String? description; final DateTime? dueDate; final bool isActive; const TopicAssignment({ required this.id, required this.classId, required this.teacherId, required this.title, this.description, this.dueDate, this.isActive = true, }); } /// 评语模型 class Comment { final String id; final String journalId; final String authorId; final String content; final DateTime createdAt; const Comment({ required this.id, required this.journalId, required this.authorId, required this.content, required this.createdAt, }); } /// 班级状态 sealed class ClassState { const ClassState(); } /// 初始状态 final class ClassInitial extends ClassState { const ClassInitial(); } /// 加载中 final class ClassLoading extends ClassState { const ClassLoading(); } /// 班级列表已加载 final class ClassListLoaded extends ClassState { final List classes; final bool isLoading; const ClassListLoaded({ this.classes = const [], this.isLoading = false, }); ClassListLoaded copyWith({ List? classes, bool? isLoading, }) => ClassListLoaded( classes: classes ?? this.classes, isLoading: isLoading ?? this.isLoading, ); } /// 班级详情已加载(日记墙 / 成员 / 主题 / 评语) final class ClassDetailLoaded extends ClassState { final SchoolClass classInfo; final List members; final List diaryWall; final List topics; final List comments; final bool isLoadingWall; final bool isLoadingMembers; final String? selectedJournalId; const ClassDetailLoaded({ required this.classInfo, this.members = const [], this.diaryWall = const [], this.topics = const [], this.comments = const [], this.isLoadingWall = false, this.isLoadingMembers = false, this.selectedJournalId, }); ClassDetailLoaded copyWith({ SchoolClass? classInfo, List? members, List? diaryWall, List? topics, List? comments, bool? isLoadingWall, bool? isLoadingMembers, String? selectedJournalId, bool clearSelectedJournal = false, }) => ClassDetailLoaded( classInfo: classInfo ?? this.classInfo, members: members ?? this.members, diaryWall: diaryWall ?? this.diaryWall, topics: topics ?? this.topics, comments: comments ?? this.comments, isLoadingWall: isLoadingWall ?? this.isLoadingWall, isLoadingMembers: isLoadingMembers ?? this.isLoadingMembers, selectedJournalId: clearSelectedJournal ? null : (selectedJournalId ?? this.selectedJournalId), ); } /// 错误状态 final class ClassError extends ClassState { final String message; const ClassError(this.message); } // ===== BLoC ===== class ClassBloc extends Bloc { ClassBloc() : super(const ClassInitial()) { on(_onLoadMyClasses); on(_onClassSelected); on(_onLoadMembers); on(_onLoadDiaryWall); on(_onLoadTopics); on(_onCreateClass); on(_onTopicAssign); on(_onLoadComments); } Future _onLoadMyClasses( ClassLoadMyClasses event, Emitter emit, ) async { emit(const ClassListLoaded(isLoading: true)); // Phase 1: 占位数据,待 API 集成 await Future.delayed(const Duration(milliseconds: 300)); final now = DateTime.now(); emit(ClassListLoaded(classes: [ SchoolClass( id: 'class-1', name: '三年二班', schoolName: '阳光小学', teacherId: 'teacher-1', classCode: 'a1b2c3', memberCount: 28, createdAt: now, updatedAt: now, ), ])); } Future _onClassSelected( ClassSelected event, Emitter emit, ) async { final now = DateTime.now(); final classInfo = SchoolClass( id: event.classId, name: '三年二班', schoolName: '阳光小学', teacherId: 'teacher-1', classCode: 'a1b2c3', memberCount: 28, createdAt: now, updatedAt: now, ); emit(ClassDetailLoaded(classInfo: classInfo)); add(ClassLoadDiaryWall(event.classId)); add(ClassLoadMembers(event.classId)); add(ClassLoadTopics(event.classId)); } Future _onLoadMembers( ClassLoadMembers event, Emitter emit, ) async { if (state is! ClassDetailLoaded) return; final current = state as ClassDetailLoaded; emit(current.copyWith(isLoadingMembers: true)); await Future.delayed(const Duration(milliseconds: 200)); final now = DateTime.now(); final members = List.generate( 28, (i) => ClassMember( userId: 'user-$i', role: i == 0 ? 'teacher' : 'student', nickname: i == 0 ? '王老师' : '同学$i', joinedAt: now, ), ); emit(current.copyWith(members: members, isLoadingMembers: false)); } Future _onLoadDiaryWall( ClassLoadDiaryWall event, Emitter emit, ) async { if (state is! ClassDetailLoaded) return; final current = state as ClassDetailLoaded; emit(current.copyWith(isLoadingWall: true)); await Future.delayed(const Duration(milliseconds: 200)); final now = DateTime.now(); final titles = ['快乐的周末', '春天来了', '我的小猫', '数学课', '好朋友', '下雨天']; final diaries = List.generate( 6, (i) => JournalEntry( id: 'diary-$i', authorId: 'user-${i + 1}', classId: event.classId, title: titles[i], date: now.subtract(Duration(days: i)), mood: Mood.values[i % Mood.values.length], tags: const ['日常', '心情'], createdAt: now, updatedAt: now, ), ); emit(current.copyWith(diaryWall: diaries, isLoadingWall: false)); } Future _onLoadTopics( ClassLoadTopics event, Emitter emit, ) async { if (state is! ClassDetailLoaded) return; final current = state as ClassDetailLoaded; final topics = [ TopicAssignment( id: 'topic-1', classId: event.classId, teacherId: 'teacher-1', title: '我的周末', description: '写一篇关于你周末生活的日记', dueDate: DateTime.now().add(const Duration(days: 3)), ), TopicAssignment( id: 'topic-2', classId: event.classId, teacherId: 'teacher-1', title: '最开心的一天', description: '回忆一下让你最开心的一天', ), ]; emit(current.copyWith(topics: topics)); } Future _onCreateClass( ClassCreate event, Emitter emit, ) async { final newClass = SchoolClass.create( name: event.name, schoolName: event.schoolName ?? '', teacherId: 'current-user', classCode: 'x1y2z3', ); if (state is ClassListLoaded) { final current = state as ClassListLoaded; emit(current.copyWith(classes: [...current.classes, newClass])); } } Future _onTopicAssign( TopicAssign event, Emitter emit, ) async { if (state is! ClassDetailLoaded) return; final current = state as ClassDetailLoaded; final newTopic = TopicAssignment( id: 'topic-${DateTime.now().millisecondsSinceEpoch}', classId: event.classId, teacherId: 'current-user', title: event.title, description: event.description, dueDate: event.dueDate, ); emit(current.copyWith(topics: [newTopic, ...current.topics])); } Future _onLoadComments( ClassLoadComments event, Emitter emit, ) async { if (state is! ClassDetailLoaded) return; final current = state as ClassDetailLoaded; final comments = [ Comment( id: 'comment-1', journalId: event.journalId, authorId: 'teacher-1', content: '写得很好,继续保持!', createdAt: DateTime.now(), ), ]; emit(current.copyWith( comments: comments, selectedJournalId: event.journalId, )); } }