import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../data/api/report_data_source.dart'; import '../../data/content_providers.dart'; import '../../data/models/models.dart'; import '../../routing/app_routes.dart'; import '../../theme/yanting_tokens.dart'; import '../../widgets/badges.dart'; import '../../widgets/mini_player.dart'; import '../../widgets/page_header.dart'; import '../../widgets/states.dart'; import '../shared/report_card_widget.dart'; class FeedPage extends HookConsumerWidget { const FeedPage({ required this.dataSource, required this.onPlay, this.player = const PlayerStateModel(), this.onStartModuleAudio, this.onToggleAudio, this.onSeekAudio, this.onSpeed, super.key, }); final ReportDataSource dataSource; final void Function(AudioItem item) onPlay; final PlayerStateModel player; final void Function( String audioId, String reportId, String title, int durationSec, )? onStartModuleAudio; final VoidCallback? onToggleAudio; final void Function(int delta)? onSeekAudio; final VoidCallback? onSpeed; @override Widget build(BuildContext context, WidgetRef ref) { final topic = useState('全部'); final snapshot = ref.watch(recommendedReportsProvider); return snapshot.when( loading: () => const LoadingState(), error: (error, _) => ErrorState( message: error.toString(), onRetry: () => ref.invalidate(recommendedReportsProvider), ), data: (items) { final currentTopic = topic.value; final topics = [ '全部', ...{for (final item in items) ...item.topics}, ]; final visible = currentTopic == '全部' ? items : items .where((item) => item.topics.contains(currentTopic)) .toList(); if (items.isEmpty) { return const EmptyState(title: '暂无可推荐的研报解读', message: '稍后再来看看最新内容'); } return ListView( padding: const EdgeInsets.fromLTRB( YantingSpacing.screenX, 4, YantingSpacing.screenX, 16, ), children: [ const PageHeader(title: '研听', subtitle: '全球机构研报中文解读'), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ for (final t in topics) Padding( padding: const EdgeInsets.only(right: YantingSpacing.x2), child: AppChip( label: t, selected: t == currentTopic, onTap: () => topic.value = t, ), ), ], ), ), const SizedBox(height: YantingSpacing.cardGap), if (visible.isEmpty) const EmptyState( title: '暂无可推荐的研报解读', message: '换个主题,或去研报页看看全部内容', icon: Icons.filter_alt_off, ) else ...[ ReportCardWidget( report: visible.first, hero: true, onTap: () => openReportDetail( context, dataSource, visible.first, player: player, onStartAudio: onStartModuleAudio, onToggleAudio: onToggleAudio, onSeekAudio: onSeekAudio, onSpeed: onSpeed, ), onPlayTap: () => _playFromReport(onPlay, visible.first), ), const SizedBox(height: YantingSpacing.sectionGap), const SectionTitle(title: '最新解读', icon: Icons.chevron_right), for (final report in visible.skip(1)) ...[ ReportCardWidget( report: report, onTap: () => openReportDetail( context, dataSource, report, player: player, onStartAudio: onStartModuleAudio, onToggleAudio: onToggleAudio, onSeekAudio: onSeekAudio, onSpeed: onSpeed, ), onPlayTap: () => _playFromReport(onPlay, report), ), const SizedBox(height: YantingSpacing.x3), ], ], ], ); }, ); } } void _playFromReport( void Function(AudioItem item) onPlay, ReportCardModel report, ) { onPlay( AudioItem( audioId: 'local_${report.id}', reportId: report.id, titleCn: report.titleCn, reportTitleCn: report.titleCn, durationSec: 180, institution: report.institution, ), ); }