Files
worker/lib/features/news/presentation/providers/news_provider.dart
Phuoc Nguyen 67fd5ed142 update news
2025-11-10 15:37:55 +07:00

196 lines
6.6 KiB
Dart

/// News Providers
///
/// State management for news articles using Riverpod.
/// Provides access to news data and filtering capabilities.
library;
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:worker/core/network/dio_client.dart';
import 'package:worker/core/services/frappe_auth_provider.dart';
import 'package:worker/features/news/data/datasources/news_local_datasource.dart';
import 'package:worker/features/news/data/datasources/news_remote_datasource.dart';
import 'package:worker/features/news/domain/entities/blog_category.dart';
import 'package:worker/features/news/domain/entities/news_article.dart';
import 'package:worker/features/news/domain/repositories/news_repository.dart';
import 'package:worker/features/news/data/repositories/news_repository_impl.dart';
part 'news_provider.g.dart';
/// News Local DataSource Provider
///
/// Provides instance of NewsLocalDataSource.
@riverpod
NewsLocalDataSource newsLocalDataSource(Ref ref) {
return NewsLocalDataSource();
}
/// News Remote DataSource Provider
///
/// Provides instance of NewsRemoteDataSource with Frappe auth service.
@riverpod
Future<NewsRemoteDataSource> newsRemoteDataSource(Ref ref) async {
final dioClient = await ref.watch(dioClientProvider.future);
final frappeAuthService = ref.watch(frappeAuthServiceProvider);
return NewsRemoteDataSource(dioClient, frappeAuthService);
}
/// News Repository Provider
///
/// Provides instance of NewsRepository implementation.
@riverpod
Future<NewsRepository> newsRepository(Ref ref) async {
final localDataSource = ref.watch(newsLocalDataSourceProvider);
final remoteDataSource = await ref.watch(newsRemoteDataSourceProvider.future);
return NewsRepositoryImpl(
localDataSource: localDataSource,
remoteDataSource: remoteDataSource,
);
}
/// All News Articles Provider (Internal)
///
/// 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>> _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
///
/// 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 allArticles = await ref.watch(_allNewsArticlesProvider.future);
// Return first article if available (latest post)
return allArticles.isNotEmpty ? allArticles.first : null;
}
/// 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).
@riverpod
class SelectedNewsCategory extends _$SelectedNewsCategory {
@override
NewsCategory? build() {
// Default: show all categories
return null;
}
/// Set selected category
void setCategory(NewsCategory? category) {
state = category;
}
/// Clear selection (show all)
void clearSelection() {
state = null;
}
}
/// Selected Category Name Provider
///
/// 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
class SelectedCategoryName extends _$SelectedCategoryName {
@override
String? build() {
// Default: show all categories
return null;
}
/// 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 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.getArticleByIdFromApi(articleId);
}
/// Blog Categories Provider
///
/// Fetches all published blog categories from Frappe API.
/// Returns AsyncValue<List<BlogCategory>> (domain entities) for proper loading/error handling.
///
/// Example categories:
/// - Tin tức (News)
/// - Chuyên môn (Professional)
/// - Dự án (Projects)
/// - Khuyến mãi (Promotions)
@riverpod
Future<List<BlogCategory>> blogCategories(Ref ref) async {
final repository = await ref.watch(newsRepositoryProvider.future);
return repository.getBlogCategories();
}