/// 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(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(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> _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 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> for proper loading/error handling. @riverpod Future> 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> 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) : []; // 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 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> (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> blogCategories(Ref ref) async { final repository = await ref.watch(newsRepositoryProvider.future); return repository.getBlogCategories(); }