260 lines
9.0 KiB
Dart
260 lines
9.0 KiB
Dart
import 'package:didvan/config/theme_data.dart';
|
|
import 'package:didvan/constants/app_icons.dart';
|
|
import 'package:didvan/models/enums.dart';
|
|
import 'package:didvan/models/overview_data.dart';
|
|
import 'package:didvan/models/requests/news.dart';
|
|
import 'package:didvan/models/requests/radar.dart';
|
|
import 'package:didvan/models/requests/studio.dart';
|
|
import 'package:didvan/models/view/alert_data.dart';
|
|
import 'package:didvan/routes/routes.dart';
|
|
import 'package:didvan/services/app_initalizer.dart';
|
|
import 'package:didvan/services/content/content_service.dart';
|
|
import 'package:didvan/services/media/media.dart';
|
|
import 'package:didvan/services/network/request.dart';
|
|
import 'package:didvan/utils/action_sheet.dart';
|
|
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
|
|
import 'package:didvan/views/widgets/didvan/card.dart';
|
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
|
import 'package:didvan/views/widgets/shimmer_placeholder.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';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:url_launcher/url_launcher_string.dart';
|
|
|
|
class SearchResultItem extends StatelessWidget {
|
|
final OverviewData item;
|
|
|
|
const SearchResultItem({
|
|
Key? key,
|
|
required this.item,
|
|
}) : super(key: key);
|
|
|
|
get _targetPageArgs {
|
|
if (item.type == 'radar') {
|
|
return const RadarRequestArgs(page: 0);
|
|
}
|
|
if (item.type == 'news') {
|
|
return const NewsRequestArgs(page: 0);
|
|
}
|
|
return StudioRequestArgs(page: 0, type: item.type);
|
|
}
|
|
|
|
String? get _targetPageRouteName {
|
|
if (item.type == 'radar') {
|
|
return Routes.radarDetails;
|
|
}
|
|
if (item.type == 'news') {
|
|
return Routes.newsDetails;
|
|
}
|
|
if (item.type == 'podcast') {
|
|
return Routes.podcasts;
|
|
}
|
|
if (item.type == 'video') {
|
|
return Routes.videocasts;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String get _icon {
|
|
if (item.type == 'radar') {
|
|
return 'lib/assets/icons/Pouyesh_Ofogh_New.svg';
|
|
}
|
|
if (item.type == 'news') {
|
|
return 'lib/assets/icons/Donye_Foolad.svg';
|
|
}
|
|
if (item.type == 'video') {
|
|
return 'lib/assets/icons/video-play.svg';
|
|
}
|
|
if (item.type == 'podcast') {
|
|
return 'lib/assets/icons/play-circle.svg';
|
|
}
|
|
if (item.type == 'delphi') {
|
|
return 'lib/assets/icons/Saha.svg';
|
|
}
|
|
if (item.type == 'infography') {
|
|
return 'lib/assets/icons/hugeicons_chart-02.svg';
|
|
}
|
|
if (item.type == 'swot') {
|
|
return 'lib/assets/icons/Swot_New.svg';
|
|
}
|
|
if (item.type == 'monthly') {
|
|
return 'lib/assets/icons/Monthly.svg';
|
|
}
|
|
// 'content' یا سایر نوعهای جدید — همان آیکن خبر را به طور پیشفرض
|
|
// استفاده میکنیم تا UI شکسته نشود.
|
|
if (item.type == 'content') {
|
|
return 'lib/assets/icons/Donye_Foolad.svg';
|
|
}
|
|
return 'lib/assets/icons/Donye_Foolad.svg';
|
|
}
|
|
|
|
void _openInteractiveViewer(BuildContext context, String image) {
|
|
ActionSheetUtils(context).openInteractiveViewer(context, image, false);
|
|
}
|
|
|
|
/// تبدیل امن تاریخ ISO به تاریخ شمسی. در صورت خالی یا نامعتبر بودن،
|
|
/// رشتهی خالی برمیگرداند تا UI crash نکند.
|
|
String _formatDate(String raw) {
|
|
if (raw.isEmpty) return '';
|
|
final dt = DateTime.tryParse(raw);
|
|
if (dt == null) return '';
|
|
try {
|
|
return dt.toPersianDateStr();
|
|
} catch (_) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return DidvanCard(
|
|
onTap: () async {
|
|
// محتوای جدید (api2.didvan.com/contents) — فعلا read را ثبت میکنیم
|
|
// و خلاصه را نشان میدهیم. صفحهی خواندن در فاز بعد.
|
|
if (item.type == 'content' && item.keycloakId != null) {
|
|
ContentService.instance.markRead(item.keycloakId!);
|
|
ActionSheetUtils(context).showAlert(
|
|
AlertData(
|
|
message:
|
|
item.description.isEmpty ? item.title : item.description,
|
|
aLertType: ALertType.success,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
if (item.type == 'podcast') {
|
|
final state = context.read<StudioDetailsState>();
|
|
await state.getStudioDetails(
|
|
item.id,
|
|
args: const StudioRequestArgs(page: 0, type: 'podcast'),
|
|
);
|
|
MediaService.currentPodcast = state.studio;
|
|
MediaService.handleAudioPlayback(
|
|
audioSource: item.link,
|
|
id: item.id,
|
|
isNetworkAudio: true,
|
|
isVoiceMessage: false,
|
|
);
|
|
return;
|
|
}
|
|
if (item.type == "infography" || item.type == "swot") {
|
|
_openInteractiveViewer(context, item.image);
|
|
} else if (_targetPageRouteName == null && item.link != null) {
|
|
AppInitializer.openWebLink(
|
|
context,
|
|
'${item.link!}?accessToken=${RequestService.token}',
|
|
mode: LaunchMode.inAppWebView,
|
|
);
|
|
return;
|
|
}
|
|
if (_targetPageRouteName != null) {
|
|
Navigator.of(context).pushNamed(
|
|
_targetPageRouteName!,
|
|
arguments: {
|
|
'id': item.id,
|
|
// Pass the new-API UUID so news/radar details uses
|
|
// getContentDetail (new API) instead of getNewsDetails (legacy)
|
|
// — the legacy endpoint doesn't know these hashCode-derived
|
|
// ids and returns 404, showing the "تلاش مجدد" retry state.
|
|
if (item.keycloakId != null && item.keycloakId!.isNotEmpty)
|
|
'contentUuid': item.keycloakId,
|
|
'args': _targetPageArgs,
|
|
// Details pages call these unconditionally — pass no-ops so
|
|
// they don't crash / fall into an error state.
|
|
'onMarkChanged': (int _, bool __) {},
|
|
'hasUnmarkConfirmation': false,
|
|
},
|
|
);
|
|
}
|
|
},
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Stack(
|
|
children: [
|
|
SkeletonImage(imageUrl: item.image, height: 80, width: 80),
|
|
Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
|
decoration: const BoxDecoration(
|
|
color: Color.fromARGB(255, 0, 126, 167),
|
|
borderRadius: BorderRadius.horizontal(
|
|
left: Radius.circular(10),
|
|
),
|
|
),
|
|
child: SvgPicture.asset(
|
|
_icon,
|
|
// ignore: deprecated_member_use
|
|
color: Theme.of(context).colorScheme.white,
|
|
height: 18,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 80,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
DidvanText(
|
|
item.title,
|
|
style: Theme.of(context).textTheme.bodyLarge,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
Row(
|
|
children: [
|
|
const Icon(
|
|
DidvanIcons.calendar_day_light,
|
|
size: 18,
|
|
),
|
|
const SizedBox(width: 4),
|
|
DidvanText(
|
|
_formatDate(item.createdAt),
|
|
style: Theme.of(context).textTheme.labelSmall,
|
|
),
|
|
const Spacer(),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
static Widget get placeholder => const DidvanCard(
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
ShimmerPlaceholder(height: 80, width: 80),
|
|
SizedBox(width: 8),
|
|
SizedBox(
|
|
height: 80,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
ShimmerPlaceholder(height: 18, width: 150),
|
|
SizedBox(height: 8),
|
|
ShimmerPlaceholder(height: 18, width: 100),
|
|
Spacer(),
|
|
ShimmerPlaceholder(height: 14, width: 80),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|