didvan-app/lib/views/monthly/monthly_list_state.dart

309 lines
9.4 KiB
Dart

import 'dart:convert';
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/monthly/monthly_model.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';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class MonthlyListState extends CoreProvier {
List<MonthlyItem> monthlyItems = [];
List<MonthlyItem> _allMonthlyItems = [];
String searchQuery = '';
String? startDate;
String? endDate;
final List<CategoryData> selectedCats = [];
List<CategoryData> categories = [];
bool filtering = false;
void initCategories() {
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,
),
];
}
bool get searching => searchQuery.isNotEmpty;
/// شناسه‌ی دسته‌ی «ماهنامه» در content-service.
static const String _monthlyCategoryId =
'd5e8b2c4-7f31-4a92-bc6f-8e2a5d1c3b47';
Future<void> fetchMonthlyList() async {
appState = AppState.busy;
notifyListeners();
// مسیر جدید: content-service.
if (!AuthConfig.useLegacyApi) {
try {
final res = await ContentService.instance.getContents(
page: 1,
limit: 30,
filterStatus: '\$in:accepted,published',
categoryId: _monthlyCategoryId,
sortBy: const [MapEntry('createdAt', 'DESC')],
);
if (!res.isSuccess || res.data == null) {
appState = AppState.failed;
notifyListeners();
return;
}
// endpoint لیست body نمی‌فرستد، پس برای هر آیتم detail کامل را
// می‌گیریم تا fileUrl پر شود.
final fetches = res.data!.data.map((c) async {
final detail = await ContentService.instance.getContent(c.id);
if (detail.isSuccess && detail.data != null) {
return detail.data!;
}
return c;
}).toList();
final fullItems = await Future.wait(fetches);
_allMonthlyItems = fullItems.map((c) {
final fileUrl = c.videoUrl ?? c.audioUrl ?? '';
final dateIso = (c.publishedAt ?? c.createdAt)?.toIso8601String() ??
DateTime.now().toIso8601String();
return MonthlyItem(
id: c.id.hashCode,
title: c.title,
image: c.coverImage ?? '',
description: c.summary ?? '',
pageNumber: 0,
publishedAt: dateIso,
file: fileUrl,
link: null,
tags: const <MonthlyTag>[],
isLiked: c.isLiked,
isBookmarked: c.isBookmarked,
);
}).toList();
monthlyItems = _applyFilters(_allMonthlyItems);
appState = AppState.idle;
} catch (e) {
debugPrint('❌ monthly (content-service) failed: $e');
appState = AppState.failed;
}
notifyListeners();
return;
}
// مسیر legacy.
try {
const url = '${RequestHelper.baseUrl}/monthly';
debugPrint('Fetching monthly list from: $url');
final response = await http.get(
Uri.parse(url),
headers: {
'Authorization': 'Bearer ${RequestService.token}',
'accept': '*/*',
'Content-Type': 'application/json; charset=UTF-8',
},
).timeout(const Duration(seconds: 30));
debugPrint('Response status code: ${response.statusCode}');
if (response.statusCode == 200) {
final jsonData = json.decode(response.body);
debugPrint('Decoded JSON: $jsonData');
final List<dynamic> resultList = jsonData['result'] as List;
debugPrint('Found ${resultList.length} items');
if (resultList.isNotEmpty) {
debugPrint('First item data: ${resultList.first}');
}
_allMonthlyItems =
resultList.map((item) => MonthlyItem.fromJson(item)).toList();
if (_allMonthlyItems.isNotEmpty) {
debugPrint('First item link: ${_allMonthlyItems.first.link}');
debugPrint('First item file: ${_allMonthlyItems.first.file}');
}
monthlyItems = _applyFilters(_allMonthlyItems);
debugPrint(
'Successfully parsed ${monthlyItems.length} monthly items after filters');
appState = AppState.idle;
} else {
debugPrint('Request failed with status code: ${response.statusCode}');
appState = AppState.failed;
}
} catch (e, stackTrace) {
debugPrint('Error fetching monthly list: $e');
debugPrint('Stack trace: $stackTrace');
appState = AppState.failed;
}
notifyListeners();
}
void search(String query) {
searchQuery = query;
var filtered = _allMonthlyItems;
if (query.isNotEmpty) {
filtered = filtered
.where((item) =>
item.title.toLowerCase().contains(query.toLowerCase()) ||
item.description.toLowerCase().contains(query.toLowerCase()))
.toList();
}
monthlyItems = _applyFilters(filtered);
notifyListeners();
}
Future<void> toggleLike(int id) async {
try {
final url = '${RequestHelper.baseUrl}/monthly/$id/like';
debugPrint('Toggling like for monthly item: $url');
final index = monthlyItems.indexWhere((item) => item.id == id);
if (index != -1) {
monthlyItems[index].isLiked = !monthlyItems[index].isLiked;
notifyListeners();
}
final response = await http.post(
Uri.parse(url),
headers: {
'Authorization': 'Bearer ${RequestService.token}',
'accept': '*/*',
'Content-Type': 'application/json; charset=UTF-8',
},
).timeout(const Duration(seconds: 30));
if (response.statusCode == 200) {
debugPrint('Like toggled successfully');
} else {
debugPrint('Failed to toggle like: ${response.statusCode}');
if (index != -1) {
monthlyItems[index].isLiked = !monthlyItems[index].isLiked;
notifyListeners();
}
}
} catch (e) {
debugPrint('Error toggling like: $e');
final index = monthlyItems.indexWhere((item) => item.id == id);
if (index != -1) {
monthlyItems[index].isLiked = !monthlyItems[index].isLiked;
notifyListeners();
}
}
}
Future<void> toggleBookmark(int id) async {
try {
final url = '${RequestHelper.baseUrl}/monthly/$id/mark';
debugPrint('Toggling bookmark for monthly item: $url');
final index = monthlyItems.indexWhere((item) => item.id == id);
if (index != -1) {
monthlyItems[index].isBookmarked = !monthlyItems[index].isBookmarked;
notifyListeners();
}
final response = await http.post(
Uri.parse(url),
headers: {
'Authorization': 'Bearer ${RequestService.token}',
'accept': '*/*',
'Content-Type': 'application/json; charset=UTF-8',
},
).timeout(const Duration(seconds: 30));
if (response.statusCode == 200) {
debugPrint('Bookmark toggled successfully');
} else {
debugPrint('Failed to toggle bookmark: ${response.statusCode}');
if (index != -1) {
monthlyItems[index].isBookmarked = !monthlyItems[index].isBookmarked;
notifyListeners();
}
}
} catch (e) {
debugPrint('Error toggling bookmark: $e');
final index = monthlyItems.indexWhere((item) => item.id == id);
if (index != -1) {
monthlyItems[index].isBookmarked = !monthlyItems[index].isBookmarked;
notifyListeners();
}
}
}
void resetFilters([bool notify = true]) {
startDate = null;
endDate = null;
selectedCats.clear();
filtering = false;
if (notify) {
fetchMonthlyList();
}
}
List<MonthlyItem> _applyFilters(List<MonthlyItem> items) {
var filtered = items.where((item) => item.file.isNotEmpty).toList();
if (startDate != null || endDate != null) {
filtered = filtered.where((item) {
final itemDate = DateTime.parse(item.publishedAt);
if (startDate != null) {
final start = DateTime.parse(startDate!);
if (itemDate.isBefore(start)) return false;
}
if (endDate != null) {
final end = DateTime.parse(endDate!).add(const Duration(days: 1));
if (itemDate.isAfter(end)) return false;
}
return true;
}).toList();
}
if (selectedCats.isNotEmpty) {
filtered = filtered.where((item) {
return item.tags
.any((tag) => selectedCats.any((cat) => cat.id == tag.id));
}).toList();
}
return filtered;
}
}