import 'package:flutter/material.dart'; import '../../data/api/report_data_source.dart'; import '../../data/models/models.dart'; import '../../routing/app_routes.dart'; import '../../theme/wise_tokens.dart'; import '../../widgets/app_buttons.dart'; import '../../widgets/badges.dart'; import '../../widgets/mini_player.dart'; import '../../widgets/states.dart'; import '../shared/report_card_widget.dart'; class ReportsPage extends StatefulWidget { const ReportsPage({ 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 State createState() => _ReportsPageState(); } class _ReportsPageState extends State { late Future> future = widget.dataSource.reports(); String query = ''; String topic = ''; bool hasAudio = false; @override Widget build(BuildContext context) { return FutureBuilder>( future: future, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) return const LoadingState(label: '正在搜索研报'); if (snapshot.hasError) return ErrorState(message: snapshot.error.toString(), onRetry: () => setState(() => future = widget.dataSource.reports())); final items = applyFilters(snapshot.data ?? const []); return ListView( padding: const EdgeInsets.all(WiseSpacing.x4), children: [ TextField( decoration: InputDecoration( hintText: '搜索标题、机构或主题', prefixIcon: const Icon(Icons.search), suffixIcon: query.isEmpty ? null : IconButton(onPressed: () => setState(() => query = ''), icon: const Icon(Icons.close)), filled: true, fillColor: WiseColors.surface, border: OutlineInputBorder(borderRadius: BorderRadius.circular(WiseRadius.pill), borderSide: BorderSide.none), ), onChanged: (value) => setState(() => query = value.trim()), ), const SizedBox(height: WiseSpacing.x3), Row( children: [ AppButton(label: '筛选', icon: Icons.tune, kind: AppButtonKind.ghost, onPressed: openFilterSheet), const SizedBox(width: WiseSpacing.x2), AppChip(label: '有音频', selected: hasAudio, onTap: () => setState(() => hasAudio = !hasAudio)), ], ), const SizedBox(height: WiseSpacing.x3), Text('共 ${items.length} 篇研报解读${query.isNotEmpty || topic.isNotEmpty || hasAudio ? '(已筛选)' : ''}', style: Theme.of(context).textTheme.bodySmall), const SizedBox(height: WiseSpacing.x3), if (items.isEmpty) EmptyState( title: query.isNotEmpty ? '未找到相关研报' : '当前筛选下暂无研报', message: query.isNotEmpty ? '换个关键词试试' : '调整筛选条件后再试', actionLabel: '清除筛选', onAction: () => setState(() { query = ''; topic = ''; hasAudio = false; }), ) else for (final report in items) ...[ ReportCardWidget( report: report, onTap: () => openReportDetail( context, widget.dataSource, report, player: widget.player, onStartAudio: widget.onStartModuleAudio, onToggleAudio: widget.onToggleAudio, onSeekAudio: widget.onSeekAudio, onSpeed: widget.onSpeed, ), onPlayTap: () => widget.onPlay(AudioItem(audioId: 'local_${report.id}', reportId: report.id, titleCn: report.titleCn, reportTitleCn: report.titleCn, durationSec: 180, institution: report.institution)), ), const SizedBox(height: WiseSpacing.x3), ], ], ); }, ); } List applyFilters(List items) { return items.where((item) { final hay = '${item.titleCn} ${item.institution.nameCn} ${item.topics.join(' ')}'.toLowerCase(); if (query.isNotEmpty && !hay.contains(query.toLowerCase())) return false; if (topic.isNotEmpty && !item.topics.contains(topic)) return false; if (hasAudio && !item.hasAudio) return false; return true; }).toList(); } void openFilterSheet() { widget.dataSource.reports().then((items) { if (!mounted) return; final topics = {for (final item in items) ...item.topics}.toList(); showModalBottomSheet( context: context, showDragHandle: true, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(WiseRadius.lg))), builder: (context) => Padding( padding: const EdgeInsets.fromLTRB(20, 4, 20, 28), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('筛选研报', style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: WiseSpacing.x3), Wrap( spacing: WiseSpacing.x2, runSpacing: WiseSpacing.x2, children: [ AppChip(label: '全部主题', selected: topic.isEmpty, onTap: () => selectTopic('')), for (final t in topics) AppChip(label: t, selected: topic == t, onTap: () => selectTopic(t)), ], ), const SizedBox(height: WiseSpacing.x4), AppButton(label: '完成', expand: true, onPressed: () => Navigator.pop(context)), ], ), ), ); }); } void selectTopic(String value) { setState(() => topic = value); } }