didvan-app/lib/models/overview_data.dart

303 lines
11 KiB
Dart

import 'package:didvan/models/category.dart';
import 'package:didvan/models/content/content_item.dart';
import 'package:didvan/utils/category_labels.dart';
// ignore: depend_on_referenced_packages
import 'package:html/parser.dart';
class OverviewData {
final int id;
/// شناسه‌ی Keycloak/UUID مربوط به content جدید. در داده‌های legacy null
/// است. وقتی این مقدار set باشد، عملیاتی مثل toggleBookmark/Like باید
/// از این string id استفاده کنند، نه از id عددی.
final String? keycloakId;
final String title;
final String image;
String description;
final String? category;
final int? timeToRead;
final int? duration;
final String? reference;
final String? link;
final String? iframe;
final bool forManagers;
final String createdAt;
final String type;
int typeInteger;
int comments;
bool marked;
bool liked;
int likes;
final List<CategoryData>? categories;
OverviewData({
required this.id,
required this.title,
required this.image,
required this.description,
required this.createdAt,
required this.type,
required this.marked,
required this.liked,
required this.likes,
required this.comments,
required this.forManagers,
this.keycloakId,
this.category,
this.link,
this.iframe,
this.duration,
this.timeToRead,
this.reference,
this.categories,
this.typeInteger = 0,
}) {
switch (type) {
case 'radar':
typeInteger = 1;
break;
case 'news':
typeInteger = 2;
break;
case 'video':
typeInteger = 3;
break;
case 'podcast':
typeInteger = 4;
break;
case 'saha':
typeInteger = 6;
break;
case 'infography':
typeInteger = 7;
break;
default:
typeInteger = 5;
}
}
factory OverviewData.fromJson(Map<String, dynamic> json) {
String? description;
if (json['description'] != null) {
final document = parse(json['description']);
description = parse(document.body!.text).documentElement!.text;
}
return OverviewData(
id: json['id'],
title: json['title'],
image: json['image'] ?? '',
description: description ?? '',
timeToRead: json['timeToRead'],
reference: json['reference'],
forManagers: json['forManagers'] ?? false,
comments: json['comments'] ?? 0,
category: json['category'] ?? '',
createdAt: json['createdAt'],
duration: json['duration'],
type: json['type'] ?? '',
marked: json['marked'] ?? true,
liked: json['liked'] ?? true,
likes: json['likes'] ?? 0,
link: json['link'],
iframe: json['iframe'],
categories: json['categories'] != null
? List<CategoryData>.from(
json['categories'].map(
(e) => CategoryData.fromJson(e),
),
)
: null,
);
}
/// ساختن OverviewData از یک ContentItem (داده‌ی content-service جدید).
/// چون id در ContentItem یک UUID رشته‌ای است، یک عدد عددی واحد برای
/// سازگاری با کدهای موجود (که از id int انتظار دارند) از hashCode
/// تولید می‌کنیم و keycloakId را به طور جداگانه نگه می‌داریم.
factory OverviewData.fromContentItem(ContentItem item) {
final fallbackImage =
item.coverImage ?? '';
String description = item.summary ?? '';
if (description.contains('<')) {
try {
final document = parse(description);
description = parse(document.body!.text).documentElement!.text;
} catch (_) {
// اگر parse نشد، summary را همان‌طور نگه می‌داریم.
}
}
return OverviewData(
id: item.id.hashCode,
keycloakId: item.id,
title: item.title,
image: fallbackImage,
description: description,
// برای جلوگیری از null! crash در ویجت‌های قدیمی، مقادیر default می‌دهیم.
reference: item.metaSource ?? item.author ?? '',
createdAt:
(item.publishedAt ?? item.createdAt)?.toIso8601String() ?? DateTime.now().toIso8601String(),
type: _typeForCategory(item.category?.id),
marked: item.isBookmarked,
liked: item.isLiked,
likes: item.likeCount,
comments: 0,
forManagers: false,
category: item.category?.name,
// Card badge label. The user wants the item's OWN subcategory, not the
// parent section name (e.g. show "اقتصادی" not "پویش افق"). Fallback
// chain: metadata.category (single) → metadata.categories[0] (multi) →
// first tag name → empty string. We deliberately do NOT fall back to
// the parent category name any more — an empty label makes the badge
// disappear on the widget side, which is better than every card in the
// section showing the same generic "پویش افق".
categories: () {
String label = '';
if (item.metaCategory != null && item.metaCategory!.isNotEmpty) {
label = CategoryLabels.label(item.metaCategory);
} else if (item.metaCategories.isNotEmpty) {
label = CategoryLabels.label(item.metaCategories.first);
} else if (item.tagNames.isNotEmpty) {
label = item.tagNames.first;
}
return [CategoryData(id: 0, label: label)];
}(),
link: webLinkForCategory(item.category?.id, item.id),
);
}
/// نگاشتِ categoryId محتوای جدید به type قدیمی، تا ویجت‌های موجود
/// (news/radar/podcast/video/infography) بتوانند بدون تغییر آن را رندر کنند.
static String _typeForCategory(String? categoryId) {
switch (categoryId) {
case '442c2b9a-2002-43a9-8567-79900ed37534':
return 'news';
case 'f97fbe3f-5712-4e27-81d4-b309cf58e91c':
return 'radar';
case '7cbe250a-d0c4-44c2-970b-c4abbdf0d6c2':
return 'podcast';
case '321a1cc9-b1e0-4787-a8db-6faa9a3477c4':
return 'video';
case 'b84d4921-2ae5-4201-874c-48f646f8f57b':
case '3e53627d-9a18-4bad-b4ae-1f0c55438147':
// هر دو id برای اینفوگرافی/بنر (legacy + content-service).
return 'infography';
default:
// برای دسته‌های جدید (startup/tech/trend/swot/...) از news (که UI
// ساده‌ای دارد) به‌عنوان fallback استفاده می‌کنیم.
return 'news';
}
}
/// آدرس وب‌ویوِ خواندنِ آیتم بر اساس دسته‌ی content-service.
/// مسیرِ canonical فرانت: `https://app.didvan.com/<slug>/<uuid>`.
/// برای دسته‌هایی که نباید وب‌ویو باز کنند (خبر، پویش) null برمی‌گردد.
/// برای فرصت‌وتهدید چون فرانت روت اختصاصی ندارد، به سرویس قدیمی
/// `opportunity-threat.didvan.com` می‌رود.
static String? webLinkForCategory(String? categoryId, String contentUuid) {
if (categoryId == null) return null;
switch (categoryId) {
case '442c2b9a-2002-43a9-8567-79900ed37534': // خبر (دنیای فولاد)
case 'f97fbe3f-5712-4e27-81d4-b309cf58e91c': // پویش افق
return null; // طبق درخواست، وب‌ویو ندارند.
case '29c27474-6bdb-4de0-8633-60cb8230faf4':
return 'https://app.didvan.com/startup/$contentUuid';
case 'a026cf50-aead-47b9-bec5-1544e8bf1e92':
return 'https://app.didvan.com/technology/$contentUuid';
case 'ce2cdb6e-0268-4986-81a8-8a6e7bc06fdf':
return 'https://app.didvan.com/trend/$contentUuid';
case 'f0092865-e004-4031-91c3-eca1fb0c6def':
// فرانتِ پنل روتِ عمومی برای فرصت‌وتهدید ندارد و سرویس قدیمی هم
// UUID جدید را نمی‌شناسد؛ تا اضافه‌شدنِ route، وب‌ویو ندارد.
return null;
default:
return null;
}
}
/// ساختن OverviewData برای یک «پادکست» از ContentItem (content-service).
/// type='podcast' تا کارت/پخش‌کننده‌ی موجود همان رفتار قبلی را داشته باشند.
/// link = آدرس مطلق فایل صوتی (از body استخراج شده).
factory OverviewData.fromPodcastContent(ContentItem item) {
final fallbackImage =
item.coverImage ?? '';
String description = item.summary ?? '';
if (description.contains('<')) {
try {
final document = parse(description);
description = parse(document.body!.text).documentElement!.text;
} catch (_) {}
}
return OverviewData(
id: item.id.hashCode,
keycloakId: item.id,
title: item.title,
image: fallbackImage,
description: description,
createdAt: (item.publishedAt ?? item.createdAt)?.toIso8601String() ?? DateTime.now().toIso8601String(),
type: 'podcast',
marked: item.isBookmarked,
liked: item.isLiked,
likes: item.likeCount,
comments: 0,
forManagers: false,
category: item.category?.name,
link: item.audioUrl,
duration: item.mediaDuration,
);
}
/// ساختن OverviewData برای یک «ویدیوکست» از ContentItem (content-service).
/// type='video' تا کارت‌های ویدیوکست همان رفتار قبلی را داشته باشند.
/// link = آدرس مطلق فایل ویدیویی (از body استخراج شده).
factory OverviewData.fromVideocastContent(ContentItem item) {
final fallbackImage =
item.coverImage ?? '';
String description = item.summary ?? '';
if (description.contains('<')) {
try {
final document = parse(description);
description = parse(document.body!.text).documentElement!.text;
} catch (_) {}
}
return OverviewData(
id: item.id.hashCode,
keycloakId: item.id,
title: item.title,
image: fallbackImage,
description: description,
createdAt: (item.publishedAt ?? item.createdAt)?.toIso8601String() ?? DateTime.now().toIso8601String(),
type: 'video',
marked: item.isBookmarked,
liked: item.isLiked,
likes: item.likeCount,
comments: 0,
forManagers: false,
category: item.category?.name,
link: item.videoUrl,
duration: item.mediaDuration,
);
}
Map<String, dynamic> toJson() => {
'id': id,
if (keycloakId != null) 'keycloakId': keycloakId,
'title': title,
'image': image,
'description': description,
'timeToRead': timeToRead,
'reference': reference,
'forManagers': forManagers,
'createdAt': createdAt,
'category': category,
'type': type,
'categories': categories?.map((e) => e.toJson()).toList(),
};
}