didvan-app/lib/views/home/main/widgets/infography_item.dart

238 lines
8.8 KiB
Dart

// ignore_for_file: deprecated_member_use
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/models/infography/infography_content.dart' as inf;
import 'package:didvan/models/tag.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/category_labels.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
/// کارت اینفوگرافی — دقیقا کپی از PodcastListCard (تب پادکست در media page).
/// تفاوت‌ها:
/// * به‌جای آیکن clock + مدت‌زمان، آیکن puzzle + دسته‌ی موضوعی نمایش داده می‌شود.
/// * onTap به‌جای navigation به صفحه‌ی پادکست، تصویر را در InteractiveViewer باز می‌کند.
class InfographyItem extends StatelessWidget {
final String image;
final String title;
final String category;
/// کلید English از metadata.category (مثلا 'political'). اگر داده شود،
/// به‌جای `category` ـ والد، با ترجمه‌ی فارسی نمایش داده می‌شود.
final String? metaCategory;
final List<Tag> tag;
final String createdAt;
final int id;
final bool marked;
final bool isLastItem;
/// UUID محتوا در content-service. اگر null/خالی باشه به جای navigation به
/// صفحه‌ی detail، تصویر در InteractiveViewer باز می‌شه (سازگاری با آیتم‌های legacy).
final String? keycloakId;
/// لینک خارجی (اختیاری). به صفحه‌ی detail پاس داده می‌شه تا دکمه‌ی «مشاهده منبع» نشون داده شه.
final String? link;
// ignore: unused_field
final bool liked;
// ignore: unused_field
final int likes;
final void Function(int id, bool value, bool shouldUpdate) onMarkChanged;
// ignore: unused_field
final void Function(int id, bool value, bool shouldUpdate) onLikedChanged;
const InfographyItem({
super.key,
required this.onMarkChanged,
required this.image,
required this.category,
required this.title,
required this.tag,
required this.createdAt,
required this.id,
required this.marked,
required this.liked,
required this.likes,
required this.onLikedChanged,
this.metaCategory,
this.isLastItem = false,
this.keycloakId,
this.link,
});
void _openDetail(BuildContext context) {
Navigator.of(context).pushNamed(
Routes.infographyDetails,
arguments: {
'item': inf.Content(
id: id,
marked: marked,
liked: liked,
likes: likes,
createdAt: createdAt,
title: title,
image: image,
category: category,
tags: tag,
metaCategory: metaCategory ?? '',
keycloakId: keycloakId,
link: link,
),
if (keycloakId != null && keycloakId!.isNotEmpty)
'contentUuid': keycloakId,
'onMarkChanged': (int id, bool value) =>
onMarkChanged(id, value, true),
},
);
}
String _displayCategory() {
final m = metaCategory;
if (m != null && m.isNotEmpty) {
final t = CategoryLabels.label(m);
if (t.isNotEmpty) return t;
}
return category;
}
String _formatDate(String dateStr) {
try {
final date = DateTime.parse(dateStr);
return date.toPersianDateStr();
} catch (e) {
return dateStr;
}
}
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return GestureDetector(
onTap: () => _openDetail(context),
child: Column(
children: [
Container(
height: 120,
margin: const EdgeInsets.only(
bottom: 8,
left: 16,
right: 16,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(6.0),
child: AspectRatio(
aspectRatio: 1.42,
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: SkeletonImage(
imageUrl: image,
width: double.infinity,
height: double.infinity,
borderRadius: BorderRadius.circular(16),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 3, 12, 10),
child: Container(
color: DesignConfig.isDark
? Colors.transparent
: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
DidvanText(
title,
style: textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: DesignConfig.isDark
? const Color.fromARGB(255, 206, 206, 206)
: Colors.black87,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
Row(
children: [
SvgPicture.asset(
'lib/assets/icons/calendar.svg',
color: Theme.of(context).colorScheme.caption,
),
const SizedBox(width: 4),
DidvanText(
_formatDate(createdAt),
style: textTheme.bodySmall?.copyWith(
color:
Theme.of(context).colorScheme.caption,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
const SizedBox(height: 7),
Row(
children: [
// به‌جای آیکن ساعت (مدت زمان پادکست)، آیکن
// دسته‌بندی + نام فارسی دسته نمایش داده می‌شود.
SvgPicture.asset(
'lib/assets/icons/element-2.svg',
color: Theme.of(context).colorScheme.caption,
width: 16,
height: 16,
),
const SizedBox(width: 4),
Expanded(
child: DidvanText(
_displayCategory().toPersianDigit(),
style: textTheme.bodySmall?.copyWith(
color: Theme.of(context)
.colorScheme
.caption,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
),
),
),
],
),
),
),
if (!isLastItem)
Divider(
color: Colors.grey[300],
thickness: 2,
indent: 16,
endIndent: 16,
),
],
),
);
}
}