didvan-app/lib/views/news/news_state.dart

236 lines
7.1 KiB
Dart

import 'package:didvan/config/auth_config.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/category.dart';
import 'package:didvan/models/content/content_item.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/news.dart';
import 'package:didvan/providers/core.dart';
import 'package:didvan/services/content/content_service.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
/// Banner category id in content-service (همان دسته‌ی «بنر»).
const String _bannerCategoryId = '3e53627d-9a18-4bad-b4ae-1f0c55438147';
/// نگاشت label فارسی به کلیدِ canonical که در metadata.category بنر ذخیره می‌شود.
const Map<String, String> kNewsCategoryKeyByLabel = {
'اقتصادی': 'economic',
'سیاسی': 'political',
'فناوری': 'technology',
'کسب و کار': 'business',
'زیست‌محیطی': 'environmental',
'اجتماعی': 'social',
};
class NewsState extends CoreProvier {
String search = '';
String lastSearch = '';
String? startDate;
String? endDate;
int page = 1;
int lastPage = 0;
int visibleCount = 4;
final List<CategoryData> selectedCats = [];
List<CategoryData> categories = [];
final List<OverviewData> news = [];
/// بنرها به تفکیک کلیدِ دسته‌بندی (مثلا 'economic' → لیست بنرها).
/// از panel با metadata.$single:category:<key> ذخیره می‌شوند.
Map<String, List<ContentItem>> bannersByCategory = {};
/// آیا بارگذاری بنرها در حال انجام است.
bool isLoadingBanners = false;
void init() {
search = '';
lastSearch = '';
startDate = null;
endDate = null;
Future.delayed(Duration.zero, () {
getNews(page: 1);
loadBanners();
});
visibleCount = 4;
categories = [
CategoryData(
id: 1,
label: 'اقتصادی',
asset: Assets.economicCategoryIcon,
),
CategoryData(
id: 2,
label: 'سیاسی',
asset: Assets.politicalCategoryIcon,
),
CategoryData(
id: 3,
label: 'فناوری',
asset: Assets.techCategoryIcon,
),
CategoryData(
id: 4,
label: 'کسب و کار',
asset: Assets.businessCategoryIcon,
),
CategoryData(
id: 5,
label: 'زیست‌محیطی',
asset: Assets.enviromentalCategoryIcon,
),
CategoryData(
id: 6,
label: 'اجتماعی',
asset: Assets.socialCategoryIcon,
),
];
}
void resetFilters() {
startDate = null;
endDate = null;
visibleCount = 4;
getNews(page: 1);
}
void loadMore() {
visibleCount += 4;
if (visibleCount >= news.length && page < lastPage) {
getNews(page: page + 1);
}
notifyListeners();
}
/// شناسه‌ی دسته‌ی «دنیای فولاد» (خبر) در content-service جدید.
static const String _newsCategoryId =
'442c2b9a-2002-43a9-8567-79900ed37534';
static const String _visibleStatusFilter = '\$in:accepted,published';
Future<void> getNews({
required int page,
}) async {
this.page = page;
if (this.page == 1) {
news.clear();
}
if (search != '') {
lastSearch = search;
}
lastSearch = search;
if (page == 1) {
appState = AppState.busy;
}
// مسیر جدید: از content-service با دسته‌ی «دنیای فولاد».
if (!AuthConfig.useLegacyApi) {
final res = await ContentService.instance.getContents(
page: page,
limit: 15,
search: search.isEmpty ? null : search,
filterStatus: _visibleStatusFilter,
categoryId: _newsCategoryId,
sortBy: const [MapEntry('createdAt', 'DESC')],
);
if (res.isSuccess && res.data != null) {
lastPage = res.data!.totalPages;
news.addAll(res.data!.data.map(OverviewData.fromContentItem));
appState = AppState.idle;
} else {
appState = AppState.failed;
}
return;
}
final service = RequestService(
RequestHelper.newsOverviews(
args: NewsRequestArgs(
page: page,
startDate: startDate?.split(' ').first,
endDate: endDate?.split(' ').first,
search: search == '' ? null : search,
categories: selectedCats.map((e) => e.id).toList(),
),
),
);
await service.httpGet();
if (service.isSuccess) {
lastPage = service.result['lastPage'];
final newsList = service.result['news'];
for (var i = 0; i < newsList.length; i++) {
news.add(OverviewData.fromJson(newsList[i]));
}
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
Future<void> onMarkChanged(int id, bool value, bool shouldUpdate) async {
news.firstWhere((element) => element.id == id).marked = value;
if (shouldUpdate) {
notifyListeners();
}
}
Future<void> onLikedChanged(int id, bool value, bool shouldUpdate) async {
news.firstWhere((element) => element.id == id).liked = value;
if (shouldUpdate) {
notifyListeners();
}
}
bool get isFiltering =>
startDate != null || endDate != null || selectedCats.isNotEmpty;
/// بنرهای دسته‌ی فعلی را برمی‌گرداند.
/// اگر کاربر هیچ دسته‌ای انتخاب نکرده باشد، هیچ بنری نمایش داده نمی‌شود
/// (در غیر این صورت بنر فقط در دسته‌ای که در پنل تعیین شده دیده می‌شد).
List<ContentItem> get bannersForCurrentSelection {
if (selectedCats.isEmpty) {
return const [];
}
final keys = selectedCats
.map((c) => kNewsCategoryKeyByLabel[c.label])
.whereType<String>()
.toSet();
final result = <ContentItem>[];
for (final k in keys) {
result.addAll(bannersByCategory[k] ?? const []);
}
return result;
}
/// تمام بنرها را از content-service می‌خواند و بر اساس metadata.category
/// گروه‌بندی می‌کند. خروجی در `bannersByCategory` ذخیره می‌شود.
Future<void> loadBanners() async {
if (isLoadingBanners) return;
isLoadingBanners = true;
notifyListeners();
try {
final res = await ContentService.instance.getContents(
page: 1,
limit: 50,
filterStatus: '\$in:accepted,published',
categoryId: _bannerCategoryId,
sortBy: const [MapEntry('createdAt', 'DESC')],
);
if (res.isSuccess && res.data != null) {
final map = <String, List<ContentItem>>{};
for (final item in res.data!.data) {
final key = (item.metaCategory ?? '').trim();
if (key.isEmpty) continue;
map.putIfAbsent(key, () => []).add(item);
}
bannersByCategory = map;
}
} catch (_) {
// silent fail — بنرها optional هستن
} finally {
isLoadingBanners = false;
notifyListeners();
}
}
}