news page

This commit is contained in:
Phuoc Nguyen
2025-11-03 11:48:41 +07:00
parent 21c1c3372c
commit ea485d8c3a
14 changed files with 2017 additions and 13 deletions

View File

@@ -0,0 +1,159 @@
/// 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:worker/core/constants/ui_constants.dart';
import 'package:worker/core/theme/colors.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) {
return GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.only(bottom: AppSpacing.md),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(AppRadius.card),
border: Border.all(color: const Color(0xFFE2E8F0)),
),
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: AppColors.grey100,
child: const Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
),
errorWidget: (context, url, error) => Container(
width: 80,
height: 80,
color: AppColors.grey100,
child: const Icon(
Icons.image_outlined,
size: 24,
color: AppColors.grey500,
),
),
),
),
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: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF1E293B),
height: 1.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
// Excerpt (max 2 lines)
Text(
article.excerpt,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF64748B),
height: 1.4,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
// Metadata row (date and views)
Row(
children: [
// Date
Icon(
Icons.calendar_today,
size: 12,
color: const Color(0xFF64748B),
),
const SizedBox(width: 4),
Text(
article.formattedDate,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF64748B),
),
),
const SizedBox(width: 16),
// Views
Icon(
Icons.visibility,
size: 12,
color: const Color(0xFF64748B),
),
const SizedBox(width: 4),
Text(
'${article.formattedViewCount} lượt xem',
style: const TextStyle(
fontSize: 12,
color: Color(0xFF64748B),
),
),
],
),
],
),
),
],
),
),
);
}
}