import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../data/models/briefing_feed.dart'; import '../data/models/news_item.dart'; import 'api_client_provider.dart'; // ─── NewsState ──────────────────────────────────────────────────────────────── class NewsState { final List items; final int offset; final bool hasMore; final bool loadingMore; final int? selectedFeedId; final Map reactions; const NewsState({ required this.items, required this.offset, required this.hasMore, required this.loadingMore, required this.selectedFeedId, required this.reactions, }); NewsState copyWith({ List? items, int? offset, bool? hasMore, bool? loadingMore, Object? selectedFeedId = _sentinel, Map? reactions, }) { return NewsState( items: items ?? this.items, offset: offset ?? this.offset, hasMore: hasMore ?? this.hasMore, loadingMore: loadingMore ?? this.loadingMore, selectedFeedId: selectedFeedId == _sentinel ? this.selectedFeedId : selectedFeedId as int?, reactions: reactions ?? this.reactions, ); } } const _sentinel = Object(); // ─── NewsNotifier ───────────────────────────────────────────────────────────── final newsProvider = AsyncNotifierProvider(NewsNotifier.new); class NewsNotifier extends AsyncNotifier { static const _limit = 40; @override Future build() async { final items = await ref.watch(newsApiProvider).getNewsItems( days: 90, limit: _limit, offset: 0, ); return NewsState( items: items, offset: items.length, hasMore: items.length == _limit, loadingMore: false, selectedFeedId: null, reactions: {for (final item in items) item.id: item.reaction}, ); } /// Re-fetch the first page without clearing state (no flicker). Future refresh() async { final current = state.value; final items = await ref.read(newsApiProvider).getNewsItems( days: 90, limit: _limit, offset: 0, feedId: current?.selectedFeedId, ); state = AsyncData(NewsState( items: items, offset: items.length, hasMore: items.length == _limit, loadingMore: false, selectedFeedId: current?.selectedFeedId, reactions: {for (final item in items) item.id: item.reaction}, )); } Future loadMore() async { final current = state.value; if (current == null || current.loadingMore || !current.hasMore) return; state = AsyncData(current.copyWith(loadingMore: true)); try { final items = await ref.read(newsApiProvider).getNewsItems( days: 90, limit: _limit, offset: current.offset, feedId: current.selectedFeedId, ); final updatedReactions = Map.from(current.reactions); for (final item in items) { updatedReactions.putIfAbsent(item.id, () => item.reaction); } state = AsyncData(current.copyWith( items: [...current.items, ...items], offset: current.offset + items.length, hasMore: items.length == _limit, loadingMore: false, reactions: updatedReactions, )); } catch (e) { final recovered = state.value ?? current; state = AsyncData(recovered.copyWith(loadingMore: false)); rethrow; } } Future setFeed(int? feedId) async { state = const AsyncLoading(); try { final items = await ref.read(newsApiProvider).getNewsItems( days: 90, limit: _limit, offset: 0, feedId: feedId, ); state = AsyncData(NewsState( items: items, offset: items.length, hasMore: items.length == _limit, loadingMore: false, selectedFeedId: feedId, reactions: {for (final item in items) item.id: item.reaction}, )); } catch (e, st) { state = AsyncError(e, st); } } void toggleReaction(int itemId, String reaction) { final current = state.value; if (current == null) return; final prev = current.reactions[itemId]; final next = prev == reaction ? null : reaction; state = AsyncData(current.copyWith( reactions: {...current.reactions, itemId: next}, )); final briefingApi = ref.read(briefingApiProvider); final future = next == null ? briefingApi.deleteRssReaction(itemId) : briefingApi.postRssReaction(itemId, next); future.catchError((_) { final s = state.value; if (s != null) { state = AsyncData(s.copyWith( reactions: {...s.reactions, itemId: prev}, )); } }); } } // ─── FeedsNotifier ──────────────────────────────────────────────────────────── final feedsProvider = AsyncNotifierProvider>(FeedsNotifier.new); class FeedsNotifier extends AsyncNotifier> { @override Future> build() async { return ref.watch(newsApiProvider).getFeeds(); } }