Files
ai_english/client/lib/features/reading/screens/reading_search_screen.dart
2025-11-17 14:09:17 +08:00

684 lines
20 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/reading_article.dart';
import '../providers/reading_provider.dart';
import '../widgets/reading_article_card.dart';
import '../widgets/reading_search_bar.dart';
import 'reading_article_screen.dart';
/// 阅读搜索页面
class ReadingSearchScreen extends StatefulWidget {
final String? initialQuery;
const ReadingSearchScreen({
super.key,
this.initialQuery,
});
@override
State<ReadingSearchScreen> createState() => _ReadingSearchScreenState();
}
class _ReadingSearchScreenState extends State<ReadingSearchScreen> {
late TextEditingController _searchController;
String _currentQuery = '';
bool _isSearching = false;
List<ReadingArticle> _searchResults = [];
List<String> _searchHistory = [];
String _selectedDifficulty = 'all';
String _selectedCategory = 'all';
String _sortBy = 'relevance';
@override
void initState() {
super.initState();
_searchController = TextEditingController(text: widget.initialQuery);
_currentQuery = widget.initialQuery ?? '';
if (_currentQuery.isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_performSearch(_currentQuery);
});
}
_loadSearchHistory();
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
/// 加载搜索历史
void _loadSearchHistory() {
// TODO: 从本地存储加载搜索历史
_searchHistory = [
'四级阅读',
'商务英语',
'科技文章',
'新闻报道',
];
}
/// 保存搜索历史
void _saveSearchHistory(String query) {
if (query.trim().isEmpty) return;
setState(() {
_searchHistory.remove(query);
_searchHistory.insert(0, query);
if (_searchHistory.length > 10) {
_searchHistory = _searchHistory.take(10).toList();
}
});
// TODO: 保存到本地存储
}
/// 执行搜索
Future<void> _performSearch(String query) async {
if (query.trim().isEmpty) return;
setState(() {
_isSearching = true;
_currentQuery = query;
});
_saveSearchHistory(query);
try {
final provider = Provider.of<ReadingProvider>(context, listen: false);
await provider.searchArticles(query);
setState(() {
_searchResults = provider.articles;
});
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('搜索失败: $e'),
backgroundColor: Colors.red,
),
);
}
} finally {
if (mounted) {
setState(() {
_isSearching = false;
});
}
}
}
/// 清除搜索历史
void _clearSearchHistory() {
setState(() {
_searchHistory.clear();
});
// TODO: 清除本地存储
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
title: const Text('搜索文章'),
backgroundColor: Colors.white,
foregroundColor: Colors.black87,
elevation: 0,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(60),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: '搜索文章标题、内容或标签...',
hintStyle: TextStyle(color: Colors.grey[500]),
prefixIcon: const Icon(
Icons.search,
color: Color(0xFF2196F3),
),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_searchController.clear();
setState(() {
_currentQuery = '';
_searchResults.clear();
});
},
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12,
),
),
onChanged: (value) {
setState(() {});
},
onSubmitted: _performSearch,
textInputAction: TextInputAction.search,
),
),
),
),
body: Column(
children: [
// 筛选条件
_buildFilterSection(),
// 搜索结果或历史
Expanded(
child: _currentQuery.isEmpty
? _buildSearchHistory()
: _buildSearchResults(),
),
],
),
);
}
/// 构建筛选条件
Widget _buildFilterSection() {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
child: Column(
children: [
// 难度筛选
Row(
children: [
const Text(
'难度:',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
const SizedBox(width: 8),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildFilterChip('全部', 'all', _selectedDifficulty, (value) {
setState(() {
_selectedDifficulty = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
_buildFilterChip('初级', 'beginner', _selectedDifficulty, (value) {
setState(() {
_selectedDifficulty = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
_buildFilterChip('中级', 'intermediate', _selectedDifficulty, (value) {
setState(() {
_selectedDifficulty = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
_buildFilterChip('高级', 'advanced', _selectedDifficulty, (value) {
setState(() {
_selectedDifficulty = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
],
),
),
),
],
),
const SizedBox(height: 12),
// 分类筛选
Row(
children: [
const Text(
'分类:',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
const SizedBox(width: 8),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildFilterChip('全部', 'all', _selectedCategory, (value) {
setState(() {
_selectedCategory = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
_buildFilterChip('新闻', 'news', _selectedCategory, (value) {
setState(() {
_selectedCategory = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
_buildFilterChip('科技', 'technology', _selectedCategory, (value) {
setState(() {
_selectedCategory = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
_buildFilterChip('商务', 'business', _selectedCategory, (value) {
setState(() {
_selectedCategory = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
_buildFilterChip('文化', 'culture', _selectedCategory, (value) {
setState(() {
_selectedCategory = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
],
),
),
),
],
),
const SizedBox(height: 12),
// 排序方式
Row(
children: [
const Text(
'排序:',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
const SizedBox(width: 8),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildFilterChip('相关度', 'relevance', _sortBy, (value) {
setState(() {
_sortBy = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
_buildFilterChip('最新', 'newest', _sortBy, (value) {
setState(() {
_sortBy = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
_buildFilterChip('热门', 'popular', _sortBy, (value) {
setState(() {
_sortBy = value;
});
if (_currentQuery.isNotEmpty) {
_performSearch(_currentQuery);
}
}),
],
),
),
),
],
),
],
),
);
}
/// 构建筛选标签
Widget _buildFilterChip(
String label,
String value,
String selectedValue,
Function(String) onSelected,
) {
final isSelected = selectedValue == value;
return Padding(
padding: const EdgeInsets.only(right: 8),
child: GestureDetector(
onTap: () => onSelected(value),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF2196F3) : Colors.grey[100],
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isSelected ? const Color(0xFF2196F3) : Colors.grey[300]!,
),
),
child: Text(
label,
style: TextStyle(
fontSize: 12,
color: isSelected ? Colors.white : Colors.grey[700],
fontWeight: isSelected ? FontWeight.w500 : FontWeight.normal,
),
),
),
),
);
}
/// 构建搜索历史
Widget _buildSearchHistory() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 热门搜索
_buildHotSearches(),
const SizedBox(height: 24),
// 搜索历史
if (_searchHistory.isNotEmpty) _buildHistorySection(),
],
),
);
}
/// 构建热门搜索
Widget _buildHotSearches() {
final hotSearches = [
'四级阅读',
'六级阅读',
'托福阅读',
'雅思阅读',
'商务英语',
'日常对话',
'科技文章',
'新闻报道',
'文化差异',
'环境保护',
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'热门搜索',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: hotSearches.map((search) {
return GestureDetector(
onTap: () {
_searchController.text = search;
_performSearch(search);
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey[300]!),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.trending_up,
size: 16,
color: Colors.grey[600],
),
const SizedBox(width: 4),
Text(
search,
style: TextStyle(
fontSize: 14,
color: Colors.grey[700],
),
),
],
),
),
);
}).toList(),
),
],
);
}
/// 构建历史搜索
Widget _buildHistorySection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text(
'搜索历史',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
const Spacer(),
TextButton(
onPressed: _clearSearchHistory,
child: Text(
'清空',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
),
],
),
const SizedBox(height: 8),
..._searchHistory.map((history) {
return ListTile(
leading: Icon(
Icons.history,
color: Colors.grey[500],
size: 20,
),
title: Text(
history,
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
),
),
trailing: IconButton(
icon: Icon(
Icons.close,
color: Colors.grey[500],
size: 18,
),
onPressed: () {
setState(() {
_searchHistory.remove(history);
});
},
),
onTap: () {
_searchController.text = history;
_performSearch(history);
},
);
}).toList(),
],
);
}
/// 构建搜索结果
Widget _buildSearchResults() {
if (_isSearching) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF2196F3)),
),
SizedBox(height: 16),
Text(
'搜索中...',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
);
}
if (_searchResults.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.search_off,
size: 64,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
'没有找到相关文章',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
const SizedBox(height: 8),
Text(
'试试其他关键词或调整筛选条件',
style: TextStyle(
fontSize: 14,
color: Colors.grey[500],
),
),
],
),
);
}
return Column(
children: [
// 结果统计
Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
child: Row(
children: [
Text(
'找到 ${_searchResults.length} 篇相关文章',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
const Spacer(),
Text(
'搜索"$_currentQuery"',
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
],
),
),
// 文章列表
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _searchResults.length,
itemBuilder: (context, index) {
final article = _searchResults[index];
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: ReadingArticleCard(
article: article,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ReadingArticleScreen(
articleId: article.id,
),
),
);
},
),
);
},
),
),
],
);
}
}