// 搜索页面 — 标签+心情筛选日记 // // 通过 SearchBloc 驱动搜索状态: // - 标签点击 → SearchByTag event // - 心情选择 → SearchByMood event // - 清除按钮 → SearchClear event // 搜索结果由 BlocBuilder 响应式渲染。 import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import '../../../core/theme/app_colors.dart'; import '../../../data/models/journal_entry.dart'; import '../bloc/search_bloc.dart'; /// 搜索页面 — 标签+心情筛选日记 class SearchPage extends StatefulWidget { const SearchPage({super.key}); @override State createState() => _SearchPageState(); } class _SearchPageState extends State { final _searchController = TextEditingController(); // Phase 1 占位标签数据 final _recentTags = ['日常', '学校', '旅行', '美食', '读书', '心情']; final _moodFilters = Mood.values; @override void dispose() { _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; return BlocBuilder( builder: (context, state) { final hasFilter = state is SearchLoaded && state.hasActiveFilter; return Scaffold( appBar: AppBar( title: TextField( controller: _searchController, decoration: InputDecoration( hintText: '搜索日记...', hintStyle: theme.textTheme.bodyLarge?.copyWith( color: colorScheme.onSurface.withValues(alpha: 0.4), ), border: InputBorder.none, prefixIcon: const Icon(Icons.search), suffixIcon: hasFilter ? IconButton( icon: const Icon(Icons.filter_alt_off), tooltip: '清除筛选', onPressed: _clearSearch, ) : (_searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); _clearSearch(); }, ) : null), ), textInputAction: TextInputAction.search, onSubmitted: (value) { if (value.trim().isNotEmpty) { context.read().add(SearchByTag(value.trim())); } }, ), ), body: _buildBody(context, theme, colorScheme, state), ); }, ); } /// 根据搜索状态构建 body Widget _buildBody( BuildContext context, ThemeData theme, ColorScheme colorScheme, SearchState state, ) { return switch (state) { SearchInitial() => _buildSuggestions(context, theme, colorScheme), SearchLoading() => const Center(child: CircularProgressIndicator()), SearchLoaded(:final results, :final activeMood, :final activeTag) => _hasActiveFilter(activeMood, activeTag) ? _buildResults(context, theme, colorScheme, results) : _buildSuggestions(context, theme, colorScheme), SearchError(:final message) => _buildError(colorScheme, message), }; } bool _hasActiveFilter(String? mood, String? tag) => mood != null || tag != null; /// 建议区域 — 标签云 + 心情选择 Widget _buildSuggestions( BuildContext context, ThemeData theme, ColorScheme colorScheme, ) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '常用标签', style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: _recentTags.map((tag) { return ActionChip( label: Text(tag), onPressed: () { _searchController.text = tag; context.read().add(SearchByTag(tag)); }, ); }).toList(), ), const SizedBox(height: 24), Text( '按心情筛选', style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), ), const SizedBox(height: 12), Wrap( spacing: 12, runSpacing: 12, children: _moodFilters.map((mood) { final color = AppColors.moodColors[mood.value] ?? colorScheme.primary; return GestureDetector( onTap: () { _searchController.text = _moodLabel(mood); context.read().add(SearchByMood(mood)); }, child: Column( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( shape: BoxShape.circle, color: color.withValues(alpha: 0.15), ), alignment: Alignment.center, child: Text(_moodEmoji(mood), style: const TextStyle(fontSize: 24)), ), const SizedBox(height: 4), Text(_moodLabel(mood), style: theme.textTheme.labelSmall), ], ), ); }).toList(), ), ], ), ); } /// 搜索结果列表 Widget _buildResults( BuildContext context, ThemeData theme, ColorScheme colorScheme, List results, ) { if (results.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.search_off_rounded, size: 48, color: colorScheme.onSurface.withValues(alpha: 0.2)), const SizedBox(height: 12), Text( '没有找到匹配的日记', style: theme.textTheme.bodyMedium?.copyWith( color: colorScheme.onSurface.withValues(alpha: 0.5), ), ), const SizedBox(height: 16), FilledButton.tonal( onPressed: _clearSearch, child: const Text('清除筛选'), ), ], ), ); } return ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), itemCount: results.length, separatorBuilder: (_, _) => const SizedBox(height: 8), itemBuilder: (context, index) { final entry = results[index]; return _JournalCard(entry: entry); }, ); } /// 错误提示 Widget _buildError(ColorScheme colorScheme, String message) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.error_outline_rounded, size: 48, color: colorScheme.error.withValues(alpha: 0.6)), const SizedBox(height: 12), Text( message, style: TextStyle( color: colorScheme.error, fontSize: 14, ), ), const SizedBox(height: 16), FilledButton.tonal( onPressed: _clearSearch, child: const Text('重试'), ), ], ), ); } void _clearSearch() { _searchController.clear(); context.read().add(const SearchClear()); } String _moodEmoji(Mood mood) => switch (mood) { Mood.happy => '😊', Mood.calm => '😌', Mood.sad => '😢', Mood.angry => '😠', Mood.thinking => '🤔', }; String _moodLabel(Mood mood) => switch (mood) { Mood.happy => '开心', Mood.calm => '平静', Mood.sad => '难过', Mood.angry => '生气', Mood.thinking => '思考', }; } /// 日记卡片 — 在搜索结果中展示单条日记摘要 class _JournalCard extends StatelessWidget { final JournalEntry entry; const _JournalCard({required this.entry}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; final moodColor = AppColors.moodColors[entry.mood.value] ?? colorScheme.primary; return Card( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide(color: colorScheme.outlineVariant), ), child: InkWell( borderRadius: BorderRadius.circular(16), onTap: () { // TODO: 导航到日记详情页 }, child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 第一行:心情 emoji + 标题 + 日期 Row( children: [ Text( _moodEmoji(entry.mood), style: const TextStyle(fontSize: 20), ), const SizedBox(width: 8), Expanded( child: Text( entry.title, style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), Text( DateFormat('MM/dd').format(entry.date), style: theme.textTheme.labelSmall?.copyWith( color: colorScheme.onSurfaceVariant, ), ), ], ), // 第二行:标签 if (entry.tags.isNotEmpty) ...[ const SizedBox(height: 8), Wrap( spacing: 6, runSpacing: 4, children: entry.tags.take(4).map((tag) { return Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: moodColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Text( tag, style: theme.textTheme.labelSmall?.copyWith( color: moodColor, fontSize: 11, ), ), ); }).toList(), ), ], ], ), ), ), ); } String _moodEmoji(Mood mood) => switch (mood) { Mood.happy => '😊', Mood.calm => '😌', Mood.sad => '😢', Mood.angry => '😠', Mood.thinking => '🤔', }; }