210 lines
5.0 KiB
Dart
210 lines
5.0 KiB
Dart
/// Domain Entity: News Article
|
|
///
|
|
/// Pure business entity representing a news article or blog post.
|
|
/// This entity is framework-independent and contains only business logic.
|
|
library;
|
|
|
|
/// News Article Entity
|
|
///
|
|
/// Represents a news article/blog post in the app.
|
|
/// Used for displaying news, tips, project showcases, and professional content.
|
|
class NewsArticle {
|
|
/// Unique article ID
|
|
final String id;
|
|
|
|
/// Article title
|
|
final String title;
|
|
|
|
/// Article excerpt/summary
|
|
final String excerpt;
|
|
|
|
/// Full article content (optional, may load separately)
|
|
final String? content;
|
|
|
|
/// Featured image URL
|
|
final String imageUrl;
|
|
|
|
/// Article category
|
|
final NewsCategory category;
|
|
|
|
/// Publication date
|
|
final DateTime publishedDate;
|
|
|
|
/// View count
|
|
final int viewCount;
|
|
|
|
/// Estimated reading time in minutes
|
|
final int readingTimeMinutes;
|
|
|
|
/// Whether this is a featured article
|
|
final bool isFeatured;
|
|
|
|
/// Author name (optional)
|
|
final String? authorName;
|
|
|
|
/// Author avatar URL (optional)
|
|
final String? authorAvatar;
|
|
|
|
/// Constructor
|
|
const NewsArticle({
|
|
required this.id,
|
|
required this.title,
|
|
required this.excerpt,
|
|
this.content,
|
|
required this.imageUrl,
|
|
required this.category,
|
|
required this.publishedDate,
|
|
required this.viewCount,
|
|
required this.readingTimeMinutes,
|
|
this.isFeatured = false,
|
|
this.authorName,
|
|
this.authorAvatar,
|
|
});
|
|
|
|
/// Get formatted publication date (dd/MM/yyyy)
|
|
String get formattedDate {
|
|
return '${publishedDate.day.toString().padLeft(2, '0')}/'
|
|
'${publishedDate.month.toString().padLeft(2, '0')}/'
|
|
'${publishedDate.year}';
|
|
}
|
|
|
|
/// Get formatted view count (e.g., "2.3K")
|
|
String get formattedViewCount {
|
|
if (viewCount >= 1000) {
|
|
return '${(viewCount / 1000).toStringAsFixed(1)}K';
|
|
}
|
|
return viewCount.toString();
|
|
}
|
|
|
|
/// Get reading time display text
|
|
String get readingTimeText => '$readingTimeMinutes phút đọc';
|
|
|
|
/// Copy with method for immutability
|
|
NewsArticle copyWith({
|
|
String? id,
|
|
String? title,
|
|
String? excerpt,
|
|
String? content,
|
|
String? imageUrl,
|
|
NewsCategory? category,
|
|
DateTime? publishedDate,
|
|
int? viewCount,
|
|
int? readingTimeMinutes,
|
|
bool? isFeatured,
|
|
String? authorName,
|
|
String? authorAvatar,
|
|
}) {
|
|
return NewsArticle(
|
|
id: id ?? this.id,
|
|
title: title ?? this.title,
|
|
excerpt: excerpt ?? this.excerpt,
|
|
content: content ?? this.content,
|
|
imageUrl: imageUrl ?? this.imageUrl,
|
|
category: category ?? this.category,
|
|
publishedDate: publishedDate ?? this.publishedDate,
|
|
viewCount: viewCount ?? this.viewCount,
|
|
readingTimeMinutes: readingTimeMinutes ?? this.readingTimeMinutes,
|
|
isFeatured: isFeatured ?? this.isFeatured,
|
|
authorName: authorName ?? this.authorName,
|
|
authorAvatar: authorAvatar ?? this.authorAvatar,
|
|
);
|
|
}
|
|
|
|
/// Equality operator
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) return true;
|
|
|
|
return other is NewsArticle &&
|
|
other.id == id &&
|
|
other.title == title &&
|
|
other.excerpt == excerpt &&
|
|
other.content == content &&
|
|
other.imageUrl == imageUrl &&
|
|
other.category == category &&
|
|
other.publishedDate == publishedDate &&
|
|
other.viewCount == viewCount &&
|
|
other.readingTimeMinutes == readingTimeMinutes &&
|
|
other.isFeatured == isFeatured &&
|
|
other.authorName == authorName &&
|
|
other.authorAvatar == authorAvatar;
|
|
}
|
|
|
|
/// Hash code
|
|
@override
|
|
int get hashCode {
|
|
return Object.hash(
|
|
id,
|
|
title,
|
|
excerpt,
|
|
content,
|
|
imageUrl,
|
|
category,
|
|
publishedDate,
|
|
viewCount,
|
|
readingTimeMinutes,
|
|
isFeatured,
|
|
authorName,
|
|
authorAvatar,
|
|
);
|
|
}
|
|
|
|
/// String representation
|
|
@override
|
|
String toString() {
|
|
return 'NewsArticle(id: $id, title: $title, category: $category, '
|
|
'publishedDate: $publishedDate, isFeatured: $isFeatured)';
|
|
}
|
|
}
|
|
|
|
/// News Category enum
|
|
enum NewsCategory {
|
|
/// General news
|
|
news,
|
|
|
|
/// Professional/technical content
|
|
professional,
|
|
|
|
/// Project showcases
|
|
projects,
|
|
|
|
/// Events
|
|
events,
|
|
|
|
/// Promotions
|
|
promotions,
|
|
}
|
|
|
|
/// Extension for News Category display
|
|
extension NewsCategoryX on NewsCategory {
|
|
String get displayName {
|
|
switch (this) {
|
|
case NewsCategory.news:
|
|
return 'Tin tức';
|
|
case NewsCategory.professional:
|
|
return 'Chuyên môn';
|
|
case NewsCategory.projects:
|
|
return 'Dự án';
|
|
case NewsCategory.events:
|
|
return 'Sự kiện';
|
|
case NewsCategory.promotions:
|
|
return 'Khuyến mãi';
|
|
}
|
|
}
|
|
|
|
String get filterName {
|
|
switch (this) {
|
|
case NewsCategory.news:
|
|
return 'news';
|
|
case NewsCategory.professional:
|
|
return 'professional';
|
|
case NewsCategory.projects:
|
|
return 'projects';
|
|
case NewsCategory.events:
|
|
return 'events';
|
|
case NewsCategory.promotions:
|
|
return 'promotions';
|
|
}
|
|
}
|
|
}
|