196 lines
6.6 KiB
Dart
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();
|
|
}
|