didvan-app/lib/views/home/home_state.dart

396 lines
11 KiB
Dart

// ignore_for_file: avoid_print
import 'dart:async';
import 'package:didvan/config/auth_config.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/category.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/providers/core.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/content/content_service.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:flutter/material.dart';
class MenuItemType {
final String label;
final String asset;
final String link;
MenuItemType({
required this.label,
required this.asset,
required this.link,
});
}
class HomeState extends CoreProvier {
int _currentPageIndex = 0;
String search = '';
String lastSearch = '';
Timer? _debounce;
String? startDate;
String? endDate;
int page = 1;
int lastPage = 1;
final List<CategoryData> selectedCats = [];
final List<OverviewData> results = [];
late TabController tabController;
int unreadCount = 0;
final FocusNode searchFieldFocusNode = FocusNode();
void onSearchChanged(String query) {
if (_debounce?.isActive ?? false) _debounce?.cancel();
_debounce = Timer(const Duration(milliseconds: 1500), () {
if (lastSearch != query && query.length >= 2) {
search = query;
searchAll(page: 1);
}
});
}
void clearSearch() {
if (_debounce?.isActive ?? false) _debounce?.cancel();
search = '';
lastSearch = '';
results.clear();
notifyListeners();
}
void resetFilters(bool isInit) {
startDate = null;
endDate = null;
selectedCats.clear();
search = '';
lastSearch = '';
if (!isInit) {
searchAll(page: 1);
}
}
set currentPageIndex(int value) {
_currentPageIndex = value;
notifyListeners();
}
int get currentPageIndex => _currentPageIndex;
List<MenuItemType> menuItems = [];
List<CategoryData> categories = [];
bool get filtering =>
selectedCats.isNotEmpty ||
startDate != null ||
endDate != null ||
search.isNotEmpty;
/// TODO(backend-migration): endpoint قدیمی `api.didvan.app/home/mark/v2`
/// در دسترس نیست. پس از افزوده شدن endpoint مشابه در api2.didvan.com
/// برای bookmark filtering، این متد باید به ContentService منتقل شود
/// (مثلا یک پارامتر `bookmarkedBy=<userId>` در `GET /contents`).
Future<void> searchMarks({required int page}) async {
this.page = page;
if (page == 1) {
results.clear();
appState = AppState.busy;
}
lastSearch = search;
final service = RequestService(
RequestHelper.searchMarks(
page: page,
search: search,
types: selectedCats.map((e) => e.id).toList(),
startDate: startDate,
endDate: endDate,
),
);
await service.httpGet();
if (service.isSuccess) {
lastPage = service.result['lastPage'];
results.addAll(
List<OverviewData>.from(
service.result['contents'].map(
(e) => OverviewData.fromJson(e),
),
),
);
lastPage = service.result['lastPage'];
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
Future<void> searchAll({required int page}) async {
this.page = page;
if (page == 1) {
results.clear();
appState = AppState.busy;
}
lastSearch = search;
final isSwotSelected = selectedCats.any((element) => element.id == 8);
final hasLegacyCategory =
selectedCats.any((element) => const [1, 2, 3, 5, 7].contains(element.id));
if (isSwotSelected) {
await _searchSwot(page);
return;
}
if (hasLegacyCategory) {
// TODO(backend-migration): این دسته‌بندی‌ها هنوز به api.didvan.app
// متصلند که در دسترس نیست. پس از افزوده شدن endpoint مشابه در
// api2.didvan.com، این شاخه را به ContentService منتقل کنید (مثلا
// فیلتر بر اساس category یا tag).
if (AuthConfig.useLegacyApi) {
await _searchLegacy(page);
} else {
await _searchNewContents(page);
}
return;
}
await _searchNewContents(page);
}
Future<void> _searchNewContents(int page) async {
final result = await ContentService.instance.getContents(
page: page,
limit: 15,
search: search.isEmpty ? null : search,
filterStatus: '\$in:accepted,published',
// publishedAt;
sortBy: const [MapEntry('createdAt', 'DESC')],
);
if (!result.isSuccess || result.data == null) {
print('searchNewContents failed: ${result.errorMessage}');
appState = AppState.failed;
return;
}
final pageData = result.data!;
results.addAll(pageData.data.map(OverviewData.fromContentItem));
lastPage = pageData.totalPages;
appState = AppState.idle;
}
static const String _swotCategoryId =
'f0092865-e004-4031-91c3-eca1fb0c6def';
static const String _swotVisibleStatus = '\$in:accepted,published';
Future<void> _searchSwot(int page) async {
try {
final res = await ContentService.instance.getContents(
page: page,
limit: 15,
search: search.isEmpty ? null : search,
filterStatus: _swotVisibleStatus,
categoryId: _swotCategoryId,
sortBy: const [MapEntry('createdAt', 'DESC')],
);
if (!res.isSuccess || res.data == null) {
appState = AppState.failed;
return;
}
final data = res.data!;
results.addAll(data.data.map((c) => OverviewData(
id: c.id.hashCode,
keycloakId: c.id,
title: c.title,
image: c.coverImage ?? '',
description: c.summary ?? '',
createdAt: c.publishedAt?.toIso8601String() ?? '',
type: 'swot',
marked: c.isBookmarked,
liked: c.isLiked,
likes: c.likeCount,
comments: 0,
forManagers: false,
link: OverviewData.webLinkForCategory(_swotCategoryId, c.id),
)));
lastPage = data.totalPages;
appState = AppState.idle;
} catch (e) {
print('Error fetching SWOT items: $e');
appState = AppState.failed;
}
}
Future<void> _searchLegacy(int page) async {
final service = RequestService(
RequestHelper.searchAll(
page: page,
search: search,
endDate: endDate,
types: selectedCats.map((e) => e.id).toList(),
startDate: startDate,
),
);
await service.httpGet();
if (service.isSuccess) {
lastPage = service.result['lastPage'];
unreadCount = service.result['unread'] ?? unreadCount;
results.addAll(
List<OverviewData>.from(
service.result['contents'].map(
(e) => OverviewData.fromJson(e),
),
),
);
lastPage = service.result['lastPage'];
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
final categoryFilters = [
CategoryData(id: 1, label: 'پویش افق'),
CategoryData(id: 2, label: 'دنیای فولاد'),
CategoryData(id: 3, label: 'رسانه'),
CategoryData(id: 5, label: 'رادارهای استراتژیک'),
// CategoryData(id: 6, label: 'سها'), // حذف شده
CategoryData(id: 8, label: 'بایدها و نبایدها'), // اضافه شده
CategoryData(id: 7, label: 'ماهنامه تحلیلی'),
];
void refresh() async {
menuItems = [
MenuItemType(
label: 'آمار و داده',
asset: Assets.stats,
link: 'tab-3',
),
MenuItemType(
label: 'دنیای فولاد',
asset: Assets.fooladWorld,
link: Routes.news,
),
MenuItemType(
label: 'پویش افق',
asset: Assets.ofogh,
link: Routes.radars,
),
MenuItemType(
label: 'ویدیوکست',
asset: Assets.videocast,
link: Routes.videocasts,
),
MenuItemType(
label: 'پادکست',
asset: Assets.podcast,
link: Routes.podcasts,
),
MenuItemType(
label: 'اینفوگرافی',
asset: Assets.infography,
link: Routes.infography,
),
MenuItemType(
label: 'رادار روند',
asset: Assets.trend,
link: 'https://trend.didvan.app/',
),
MenuItemType(
label: 'رادار تکنولوژی',
asset: Assets.tech,
link: 'https://tech.didvan.app/',
),
MenuItemType(
label: 'رادار ریسک',
asset: Assets.risk,
link: 'https://risk.didvan.app/',
),
MenuItemType(
label: 'رادار استارت‌آپ',
asset: Assets.startup,
link: 'https://startup.didvan.app/',
),
MenuItemType(
label: 'سها',
asset: Assets.saha,
link: 'https://saha.didvan.app/app',
),
MenuItemType(
label: 'هوشان',
asset: Assets.ai,
link: Routes.aiSection,
),
MenuItemType(
label: 'فرصت و تهدید',
asset: Assets.hugeideas,
link:
'http://opportunity-threat.didvan.com/?accessToken=${RequestService.token}',
),
];
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,
),
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,
),
];
Future.delayed(Duration.zero, notifyListeners);
}
@override
void dispose() {
_debounce?.cancel();
searchFieldFocusNode.dispose();
super.dispose();
}
}