update news

This commit is contained in:
Phuoc Nguyen
2025-11-10 15:37:55 +07:00
parent 36bdf6613b
commit 67fd5ed142
17 changed files with 1016 additions and 211 deletions

View File

@@ -47,27 +47,52 @@ Future<NewsRepository> newsRepository(Ref ref) async {
);
}
/// News Articles Provider
/// All News Articles Provider (Internal)
///
/// Fetches all news articles sorted by published date.
/// Returns AsyncValue<List<NewsArticle>> for proper loading/error handling.
/// Fetches ALL blog posts from Frappe API sorted by published date (latest first).
/// This is the complete list used by both featured and latest articles providers.
/// Do not use this provider directly in UI - use featuredArticle or newsArticles instead.
@riverpod
Future<List<NewsArticle>> newsArticles(Ref ref) async {
final repository = await ref.watch(newsRepositoryProvider.future);
return repository.getAllArticles();
Future<List<NewsArticle>> _allNewsArticles(Ref ref) async {
final remoteDataSource = await ref.watch(newsRemoteDataSourceProvider.future);
// Fetch blog posts from Frappe API
final blogPosts = await remoteDataSource.getBlogPosts();
// Convert to NewsArticle entities
final articles = blogPosts.map((post) => post.toEntity()).toList();
// Already sorted by published_on desc from API
return articles;
}
/// Featured Article Provider
///
/// Fetches the featured article for the top section.
/// Returns AsyncValue<NewsArticle?> (null if no featured article).
/// Returns the first article from the complete list.
/// This is the latest published article that will be displayed prominently at the top.
@riverpod
Future<NewsArticle?> featuredArticle(Ref ref) async {
final repository = await ref.watch(newsRepositoryProvider.future);
return repository.getFeaturedArticle();
final allArticles = await ref.watch(_allNewsArticlesProvider.future);
// Return first article if available (latest post)
return allArticles.isNotEmpty ? allArticles.first : null;
}
/// Selected News Category Provider
/// News Articles Provider
///
/// Returns latest news articles EXCLUDING the first item (which is shown as featured).
/// This ensures each article only appears once on the page.
/// Returns AsyncValue<List<NewsArticle>> for proper loading/error handling.
@riverpod
Future<List<NewsArticle>> newsArticles(Ref ref) async {
final allArticles = await ref.watch(_allNewsArticlesProvider.future);
// Return all articles except first (which is featured)
// If only 0-1 articles, return empty list
return allArticles.length > 1 ? allArticles.sublist(1) : [];
}
/// Selected News Category Provider (Legacy - using enum)
///
/// Manages the currently selected category filter.
/// null means "All" is selected (show all categories).
@@ -90,32 +115,67 @@ class SelectedNewsCategory extends _$SelectedNewsCategory {
}
}
/// Filtered News Articles Provider
/// Selected Category Name Provider
///
/// Returns news articles filtered by selected category.
/// If no category is selected, returns all articles.
/// Manages the currently selected blog category name (from Frappe API).
/// null means "All" is selected (show all categories).
///
/// Examples: "tin-tức", "dự-án", "chuyên-môn", "khuyến-mãi"
@riverpod
Future<List<NewsArticle>> filteredNewsArticles(Ref ref) async {
final selectedCategory = ref.watch(selectedNewsCategoryProvider);
final repository = await ref.watch(newsRepositoryProvider.future);
// If no category selected, return all articles
if (selectedCategory == null) {
return repository.getAllArticles();
class SelectedCategoryName extends _$SelectedCategoryName {
@override
String? build() {
// Default: show all categories
return null;
}
// Filter by selected category
return repository.getArticlesByCategory(selectedCategory);
/// Set selected category by name
void setCategoryName(String? categoryName) {
state = categoryName;
}
/// Clear selection (show all)
void clearSelection() {
state = null;
}
}
/// Filtered News Articles Provider
///
/// Returns news articles filtered by selected blog category name.
/// Excludes the first article (which is shown as featured).
/// If no category is selected, returns all articles except first.
///
/// The blog_category name from API is stored in article.tags[0] for filtering.
@riverpod
Future<List<NewsArticle>> filteredNewsArticles(Ref ref) async {
final selectedCategoryName = ref.watch(selectedCategoryNameProvider);
final allArticles = await ref.watch(_allNewsArticlesProvider.future);
// Get articles excluding first (which is featured)
final articlesWithoutFeatured = allArticles.length > 1 ? allArticles.sublist(1) : <NewsArticle>[];
// If no category selected, return all articles except first
if (selectedCategoryName == null) {
return articlesWithoutFeatured;
}
// Filter articles by blog_category name (stored in tags[0])
return articlesWithoutFeatured.where((article) {
// Check if article has tags and first tag matches selected category
return article.tags.isNotEmpty && article.tags[0] == selectedCategoryName;
}).toList();
}
/// News Article by ID Provider
///
/// Fetches a specific article by ID.
/// Fetches a specific article by ID from the Frappe API.
/// Uses frappe.client.get endpoint to fetch the full blog post detail.
/// Used for article detail page.
@riverpod
Future<NewsArticle?> newsArticleById(Ref ref, String articleId) async {
final repository = await ref.watch(newsRepositoryProvider.future);
return repository.getArticleById(articleId);
return repository.getArticleByIdFromApi(articleId);
}
/// Blog Categories Provider