Files
nj/app/lib/features/search/bloc/search_bloc.dart
iven 49d4aa36a7
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
fix(app): Phase 1.1 紧急修复 — SyncEngine 接入 + authorId + catch 异常处理
- feat(sync): SyncEngine 接入 EditorPage, 保存时 enqueue + 网络恢复自动 trySync
- fix(editor): authorId 从 AuthBloc 获取, 替代硬编码 'local'
- fix(bloc): class_bloc/calendar/profile/parent catch(_).全部改为 debugPrint
- feat(editor): 编辑器工具栏拆分 (brush_panel/tag_panel/text_format_bar/dot_grid_painter)
- feat(editor): EditorBloc 扩展 + EditorPage 增强
- feat(search): SearchBloc 扩展搜索功能
- feat(home): HomeBloc/HomePage 增强
- feat(auth): LoginPage 增强
- feat(templates): TemplateGalleryPage 重构
- fix(web): 管理端班级/日记页面修复
- fix(server): comment_service + theme_handler 修复
- docs: 添加全链路审计报告和验证截图
2026-06-02 21:21:43 +08:00

148 lines
4.1 KiB
Dart

// 搜索 BLoC — 关键词+标签+心情筛选日记
//
// 状态机: SearchInitial → SearchLoading → SearchLoaded/SearchError
// 支持关键词搜索、标签筛选、心情筛选、结果分类 tab。
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../data/models/journal_entry.dart';
import '../../../data/repositories/journal_repository.dart';
part 'search_event.dart';
part 'search_state.dart';
/// 搜索 BLoC
class SearchBloc extends Bloc<SearchEvent, SearchState> {
final JournalRepository _journalRepo;
/// 内存搜索历史(最多 10 条)
final List<String> _searchHistory = [];
SearchBloc({required JournalRepository journalRepository})
: _journalRepo = journalRepository,
super(const SearchInitial()) {
on<SearchByMood>(_onSearchByMood);
on<SearchByTag>(_onSearchByTag);
on<SearchByKeyword>(_onSearchByKeyword);
on<SearchClear>(_onSearchClear);
on<SearchTabChanged>(_onSearchTabChanged);
}
/// 按心情筛选日记
Future<void> _onSearchByMood(
SearchByMood event,
Emitter<SearchState> emit,
) async {
emit(const SearchLoading());
try {
if (event.mood == null) {
emit(SearchLoaded(searchHistory: List.unmodifiable(_searchHistory)));
return;
}
final results = await _journalRepo.getJournals(
mood: event.mood!.value,
page: 1,
pageSize: 50,
);
emit(SearchLoaded(
results: results,
activeMood: event.mood!.value,
searchHistory: List.unmodifiable(_searchHistory),
));
} catch (e) {
emit(const SearchError('搜索失败,请重试'));
}
}
/// 按标签筛选日记
Future<void> _onSearchByTag(
SearchByTag event,
Emitter<SearchState> emit,
) async {
emit(const SearchLoading());
try {
_addToHistory(event.tag);
final results = await _journalRepo.getJournals(
tag: event.tag,
page: 1,
pageSize: 50,
);
emit(SearchLoaded(
results: results,
activeTag: event.tag,
searchHistory: List.unmodifiable(_searchHistory),
));
} catch (e) {
emit(const SearchError('搜索失败,请重试'));
}
}
/// 关键词搜索 — 在标题中匹配关键词
Future<void> _onSearchByKeyword(
SearchByKeyword event,
Emitter<SearchState> emit,
) async {
final keyword = event.keyword.trim();
if (keyword.isEmpty) {
emit(SearchLoaded(searchHistory: List.unmodifiable(_searchHistory)));
return;
}
emit(const SearchLoading());
try {
_addToHistory(keyword);
// 获取所有日记并在客户端按关键词过滤
final allJournals = await _journalRepo.getJournals(
page: 1,
pageSize: 200,
);
final lowerKeyword = keyword.toLowerCase();
final results = allJournals.where((j) {
final titleMatch = j.title.toLowerCase().contains(lowerKeyword);
final excerptMatch = (j.contentExcerpt ?? '')
.toLowerCase()
.contains(lowerKeyword);
final tagMatch =
j.tags.any((t) => t.toLowerCase().contains(lowerKeyword));
return titleMatch || excerptMatch || tagMatch;
}).toList();
emit(SearchLoaded(
results: results,
activeKeyword: keyword,
searchHistory: List.unmodifiable(_searchHistory),
));
} catch (e) {
emit(const SearchError('搜索失败,请重试'));
}
}
/// 清除搜索结果
void _onSearchClear(
SearchClear event,
Emitter<SearchState> emit,
) {
emit(SearchLoaded(searchHistory: List.unmodifiable(_searchHistory)));
}
/// 切换结果分类 tab
void _onSearchTabChanged(
SearchTabChanged event,
Emitter<SearchState> emit,
) {
final current = state;
if (current is SearchLoaded) {
emit(current.copyWith(activeTab: event.tab));
}
}
/// 添加到搜索历史(去重,最多 10 条)
void _addToHistory(String keyword) {
_searchHistory.remove(keyword);
_searchHistory.insert(0, keyword);
if (_searchHistory.length > 10) {
_searchHistory.removeLast();
}
}
}