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? 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 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.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.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': return 'infography'; default: // برای دسته‌های جدید (startup/tech/trend/swot/...) از news (که UI // ساده‌ای دارد) به‌عنوان fallback استفاده می‌کنیم. return 'news'; } } /// آدرس وب‌ویوِ خواندنِ آیتم بر اساس دسته‌ی content-service. /// مسیرِ canonical فرانت: `https://app.didvan.com//`. /// برای دسته‌هایی که نباید وب‌ویو باز کنند (خبر، پویش) 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 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(), }; }