import 'dart:async'; import 'package:flutter/material.dart'; import '../../../core/network/api_client.dart'; import '../../../core/theme/app_theme.dart'; import '../models/news_article_model.dart'; import '../repositories/news_repository.dart'; import '../widgets/news_card_item.dart'; import '../widgets/advanced_news_filter_bar.dart'; /// Paginated Infinite Scroll Daily News Feed screen with deduplication and strict chronological sorting. class NewsFeedScreen extends StatefulWidget { final ApiClient apiClient; final NewsRepository? repository; const NewsFeedScreen({super.key, required this.apiClient, this.repository}); @override State createState() => _NewsFeedScreenState(); } class _NewsFeedScreenState extends State { late final NewsRepository _repository; final ScrollController _scrollController = ScrollController(); final List _newsItems = []; int _currentPage = 1; static const int _pageSize = 15; bool _isLoading = false; bool _hasMore = true; // Filter States String? _searchQuery; DateTime? _selectedDate; String? _selectedIsin; bool _hasSentimentOnly = false; Timer? _debounceTimer; @override void initState() { super.initState(); _repository = widget.repository ?? NewsRepository(apiClient: widget.apiClient, backendUrl: ApiClient.baseUrl); _loadNews(refresh: true); _scrollController.addListener(() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { _loadNews(); } }); } @override void dispose() { _debounceTimer?.cancel(); _scrollController.dispose(); super.dispose(); } Future _loadNews({bool refresh = false}) async { if (_isLoading) return; if (refresh) { _currentPage = 1; _hasMore = true; _newsItems.clear(); } if (!_hasMore) return; setState(() => _isLoading = true); try { final fetched = await _repository.fetchNews( page: _currentPage, pageSize: _pageSize, date: _selectedDate?.toIso8601String().substring(0, 10), query: _searchQuery?.trim(), isin: _selectedIsin?.trim(), hasSentiment: _hasSentimentOnly, ); setState(() { final existingIds = _newsItems.map((e) => e.id).where((id) => id.isNotEmpty).toSet(); for (final item in fetched) { if (item.id.isEmpty || !existingIds.contains(item.id)) { _newsItems.add(item); if (item.id.isNotEmpty) existingIds.add(item.id); } } _newsItems.sort((a, b) => b.publishedAt.compareTo(a.publishedAt)); _currentPage++; if (fetched.length < _pageSize) { _hasMore = false; } }); } catch (_) { } finally { setState(() => _isLoading = false); } } void _onSearchChanged(String? val) { _searchQuery = val; _debounceTimer?.cancel(); _debounceTimer = Timer(const Duration(milliseconds: 600), () { _loadNews(refresh: true); }); } void _onIsinChanged(String? val) { _selectedIsin = val; _debounceTimer?.cancel(); _debounceTimer = Timer(const Duration(milliseconds: 600), () { _loadNews(refresh: true); }); } void _resetFilters() { _debounceTimer?.cancel(); setState(() { _searchQuery = null; _selectedDate = null; _selectedIsin = null; _hasSentimentOnly = false; }); _loadNews(refresh: true); } @override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Marktnachrichten & Feed', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), const SizedBox(height: 12), AdvancedNewsFilterBar( searchQuery: _searchQuery, selectedDate: _selectedDate, selectedIsin: _selectedIsin, hasSentimentOnly: _hasSentimentOnly, onSearchChanged: _onSearchChanged, onIsinChanged: _onIsinChanged, onDateChanged: (val) { setState(() => _selectedDate = val); _loadNews(refresh: true); }, onSentimentToggleChanged: (val) { setState(() => _hasSentimentOnly = val); _loadNews(refresh: true); }, onResetFilters: _resetFilters, ), const SizedBox(height: 16), Expanded( child: _newsItems.isEmpty && !_isLoading ? Center( child: Text('Keine Nachrichten für diese Filterkriterien gefunden.', style: TextStyle(color: AppTheme.textMuted)), ) : ListView.builder( controller: _scrollController, itemCount: _newsItems.length + (_hasMore ? 1 : 0), itemBuilder: (context, index) { if (index == _newsItems.length) { return Center( child: Padding( padding: const EdgeInsets.all(16), child: CircularProgressIndicator(color: AppTheme.primaryEmerald), ), ); } return NewsCardItem(item: _newsItems[index], apiClient: widget.apiClient); }, ), ), ], ), ), ); } }