import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../backend/backend.dart'; import '../../../core/utils/result.dart'; import 'feed_state.dart'; final feedControllerProvider = AsyncNotifierProvider(FeedController.new); class FeedController extends AsyncNotifier { @override Future build() async { final providers = await _loadProviders(); final settings = await _settings(); final providerIds = providers.map((provider) => provider.id).toSet(); final initial = FeedState( providers: providers, ratingFilter: settings.defaultRatingFilter, topPeriodFilter: TopPeriodFilter.values.firstWhere( (value) => value.name == settings.defaultTopPeriodFilter, orElse: () => TopPeriodFilter.none, ), selectedProviderIds: settings.selectedFeedProviderIds.where(providerIds.contains).toList(), ); state = AsyncData(initial); await loadInitial(); return state.value ?? initial; } Future loadInitial() async { final current = state.value ?? const FeedState(); state = const AsyncLoading(); final posts = await _load(refresh: true, current: current); state = AsyncData(current.copyWith(posts: posts, clearError: true)); } Future refresh() async { final current = state.value ?? const FeedState(); state = AsyncData(current.copyWith(isLoadingMore: true, clearError: true)); final posts = await _load(refresh: true, current: current); state = AsyncData(current.copyWith(posts: posts, isLoadingMore: false)); } Future loadNextPage() async { final current = state.value; if (current == null || current.isLoadingMore || !current.hasMore) return; state = AsyncData(current.copyWith(isLoadingMore: true, clearError: true)); final posts = await _load(refresh: false, current: current); state = AsyncData( current.copyWith( posts: [...current.posts, ...posts], isLoadingMore: false, hasMore: posts.isNotEmpty, ), ); } Future search(String query) async { final tags = ref.read(searchServiceProvider).parseTags(query); final current = state.value ?? const FeedState(); state = AsyncData(current.copyWith(selectedTags: tags, posts: [])); await ref.read(searchServiceProvider).saveSearch(query, 0); await refresh(); final count = state.value?.posts.length ?? 0; await ref.read(searchServiceProvider).saveSearch(query, count); } Future updateTagSuggestions(String query) async { final current = state.value ?? const FeedState(); if (query.trim().isEmpty) { state = AsyncData(current.copyWith(tagSuggestions: [])); return; } final lastToken = query.trim().split(RegExp(r'\s+')).last; final result = await ref.read(searchServiceProvider).autocomplete(lastToken); final suggestions = result is Success> ? result.data : const []; state = AsyncData(current.copyWith(tagSuggestions: suggestions)); } Future setProviders(List providerIds) async { final current = state.value ?? const FeedState(); state = AsyncData( current.copyWith(selectedProviderIds: providerIds, posts: [])); final settings = await _settings(); await ref.read(settingsServiceProvider).updateSettings( settings.copyWith(selectedFeedProviderIds: providerIds)); await refresh(); } Future setTopPeriod(TopPeriodFilter period) async { final current = state.value ?? const FeedState(); state = AsyncData(current.copyWith(topPeriodFilter: period, posts: [])); final settings = await _settings(); await ref.read(settingsServiceProvider).updateSettings( settings.copyWith(defaultTopPeriodFilter: period.name), ); await refresh(); } Future setRating(String? rating) async { final current = state.value ?? const FeedState(); state = AsyncData( current.copyWith( ratingFilter: rating, clearRating: rating == null, posts: []), ); await refresh(); } Future clearFilters() async { final current = state.value ?? const FeedState(); state = AsyncData( current.copyWith( selectedTags: [], selectedProviderIds: [], tagSuggestions: [], clearRating: true, posts: [], ), ); final settings = await _settings(); await ref .read(settingsServiceProvider) .updateSettings(settings.copyWith(selectedFeedProviderIds: [])); await refresh(); } Future> _load({ required bool refresh, required FeedState current, }) async { final service = ref.read(feedServiceProvider); final providerIds = current.selectedProviderIds.isEmpty ? [null] : current.selectedProviderIds; final posts = []; for (final providerId in providerIds) { final result = refresh ? await service.refresh( tags: current.selectedTags, rating: current.ratingFilter, providerId: providerId, topPeriod: current.topPeriodFilter, ) : await service.loadNextPage( tags: current.selectedTags, rating: current.ratingFilter, providerId: providerId, topPeriod: current.topPeriodFilter, ); if (result is Success>) posts.addAll(result.data); } return posts; } Future> _loadProviders() async { final result = await ref.read(providerManagerProvider).loadConfigs(enabledOnly: true); return result is Success> ? result.data : const []; } Future _settings() async { final result = await ref.read(settingsServiceProvider).getSettings(); return result is Success ? result.data : AppSettings.defaults; } }