Files
worker/lib/features/news/presentation/widgets/news_card.dart
Phuoc Nguyen 49a41d24eb update theme
2025-12-02 15:20:54 +07:00

162 lines
5.2 KiB
Dart

/// News Card Widget
///
/// Compact news article card for list display.
/// Horizontal layout with thumbnail and content.
library;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/features/news/domain/entities/news_article.dart';
/// News Card
///
/// Compact card with horizontal layout:
/// - 80x80 thumbnail (left)
/// - Title (max 2 lines, 0.875rem, bold)
/// - Excerpt (max 2 lines, 0.75rem, grey)
/// - Metadata: date and views
/// - Hover/tap effect (border color change)
class NewsCard extends StatelessWidget {
/// News article to display
final NewsArticle article;
/// Callback when card is tapped
final VoidCallback? onTap;
/// Constructor
const NewsCard({super.key, required this.article, this.onTap});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.only(bottom: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.card),
border: Border.all(color: colorScheme.outlineVariant),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Thumbnail (80x80)
ClipRRect(
borderRadius: BorderRadius.circular(AppRadius.md),
child: CachedNetworkImage(
imageUrl: article.imageUrl,
width: 80,
height: 80,
fit: BoxFit.cover,
placeholder: (context, url) => Container(
width: 80,
height: 80,
color: colorScheme.surfaceContainerHighest,
child: const Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
),
errorWidget: (context, url, error) => Container(
width: 80,
height: 80,
color: colorScheme.surfaceContainerHighest,
child: FaIcon(
FontAwesomeIcons.image,
size: 24,
color: colorScheme.onSurfaceVariant,
),
),
),
),
const SizedBox(width: AppSpacing.md),
// Content (flexible to fill remaining space)
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title (max 2 lines)
Text(
article.title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
height: 1.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
// Excerpt (max 2 lines)
Text(
article.excerpt,
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
height: 1.4,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
// Metadata row (date and views)
Row(
children: [
// Date
FaIcon(
FontAwesomeIcons.calendar,
size: 12,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: 4),
Text(
article.formattedDate,
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(width: 16),
// Views
// Icon(
// Icons.visibility,
// size: 12,
// color: colorScheme.onSurfaceVariant,
// ),
// const SizedBox(width: 4),
// Text(
// '${article.formattedViewCount} lượt xem',
// style: TextStyle(
// fontSize: 12,
// color: colorScheme.onSurfaceVariant,
// ),
// ),
],
),
],
),
),
],
),
),
);
}
}