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

274 lines
8.9 KiB
Dart

import 'package:flutter/material.dart';
import '../models/reading_article.dart';
/// 阅读文章卡片组件
class ReadingArticleCard extends StatelessWidget {
final ReadingArticle article;
final VoidCallback? onTap;
final VoidCallback? onFavorite;
final bool showProgress;
const ReadingArticleCard({
super.key,
required this.article,
this.onTap,
this.onFavorite,
this.showProgress = false,
});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 16),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题和收藏按钮
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
article.title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
if (onFavorite != null)
IconButton(
icon: const Icon(
Icons.favorite_border,
color: Colors.grey,
size: 20,
),
onPressed: onFavorite,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(
minWidth: 24,
minHeight: 24,
),
),
],
),
const SizedBox(height: 8),
// 文章摘要
if (article.content.isNotEmpty)
Text(
_getExcerpt(article.content),
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
height: 1.4,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 12),
// 标签行
Row(
children: [
// 分类标签
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: const Color(0xFF2196F3).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
article.categoryLabel,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF2196F3),
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(width: 8),
// 难度标签
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: _getDifficultyColor(article.difficulty).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
article.difficultyLabel,
style: TextStyle(
fontSize: 12,
color: _getDifficultyColor(article.difficulty),
fontWeight: FontWeight.w500,
),
),
),
const Spacer(),
// 字数和阅读时间
Text(
'${article.wordCount}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[500],
),
),
const SizedBox(width: 8),
Icon(
Icons.schedule,
size: 12,
color: Colors.grey[500],
),
const SizedBox(width: 2),
Text(
'${article.readingTime}分钟',
style: TextStyle(
fontSize: 12,
color: Colors.grey[500],
),
),
],
),
// 进度条(如果需要显示)
if (showProgress && article.isCompleted)
Padding(
padding: const EdgeInsets.only(top: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'已完成',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
if (article.comprehensionScore != null)
Text(
'得分: ${article.comprehensionScore}',
style: TextStyle(
fontSize: 12,
color: _getScoreColor(article.comprehensionScore!.toInt()),
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 4),
LinearProgressIndicator(
value: 1.0,
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation<Color>(
_getScoreColor((article.comprehensionScore ?? 0).toInt()),
),
),
],
),
),
// 标签(如果有)
if (article.tags.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Wrap(
spacing: 6,
runSpacing: 4,
children: article.tags.take(3).map((tag) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Text(
'#$tag',
style: TextStyle(
fontSize: 10,
color: Colors.grey[600],
),
),
);
}).toList(),
),
),
],
),
),
),
);
}
/// 获取文章摘要
String _getExcerpt(String content) {
// 移除HTML标签和多余空白
String cleanContent = content
.replaceAll(RegExp(r'<[^>]*>'), '')
.replaceAll(RegExp(r'\s+'), ' ')
.trim();
// 截取前100个字符作为摘要
if (cleanContent.length <= 100) {
return cleanContent;
}
return '${cleanContent.substring(0, 100)}...';
}
/// 获取难度颜色
Color _getDifficultyColor(String difficulty) {
switch (difficulty.toLowerCase()) {
case 'a1':
case 'a2':
return Colors.green;
case 'b1':
case 'b2':
return Colors.orange;
case 'c1':
case 'c2':
return Colors.red;
default:
return Colors.grey;
}
}
/// 获取分数颜色
Color _getScoreColor(int score) {
if (score >= 90) {
return Colors.green;
} else if (score >= 70) {
return Colors.orange;
} else {
return Colors.red;
}
}
}