297 lines
10 KiB
Dart
297 lines
10 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,
|
|
// RadarOverview widget از `categories!.first.label` استفاده میکند.
|
|
// اگر زیر-دسته (metadata.category) موجود است، آن را به فارسی
|
|
// ترجمه میکنیم (مثلا "economical" → "اقتصادی"). در غیر این صورت،
|
|
// به دستهی parent برمیگردیم.
|
|
categories: [
|
|
CategoryData(
|
|
id: 0,
|
|
label: item.metaCategory != null && item.metaCategory!.isNotEmpty
|
|
? CategoryLabels.label(item.metaCategory)
|
|
: (item.category?.name ?? ''),
|
|
),
|
|
],
|
|
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(),
|
|
};
|
|
}
|