import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/webfeed.dart'; import 'package:island/pods/network.dart'; import 'package:island/pods/paging.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/paging/pagination_list.dart'; import 'package:island/widgets/web_article_card.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'articles.g.dart'; part 'articles.freezed.dart'; @freezed sealed class ArticleListQuery with _$ArticleListQuery { const factory ArticleListQuery({String? feedId, String? publisherId}) = _ArticleListQuery; } final articlesListNotifierProvider = AsyncNotifierProvider.family.autoDispose( ArticlesListNotifier.new, ); class ArticlesListNotifier extends AsyncNotifier> with AsyncPaginationController { static const int pageSize = 20; final ArticleListQuery arg; ArticlesListNotifier(this.arg); @override Future> fetch() async { final client = ref.read(apiClientProvider); final queryParams = {'limit': pageSize, 'offset': fetchedCount.toString()}; try { final response = await client.get( '/sphere/feeds/articles', queryParameters: queryParams, ); final articles = response.data .map( (json) => SnWebArticle.fromJson(json as Map), ) .cast() .toList(); totalCount = int.tryParse(response.headers.value('X-Total') ?? '0') ?? 0; return articles; } catch (e) { debugPrint('Error fetching articles: $e'); rethrow; } } } class SliverArticlesList extends ConsumerWidget { final String? feedId; final String? publisherId; final Color? backgroundColor; final EdgeInsets? padding; final Function? onRefresh; const SliverArticlesList({ super.key, this.feedId, this.publisherId, this.backgroundColor, this.padding, this.onRefresh, }); @override Widget build(BuildContext context, WidgetRef ref) { final provider = articlesListNotifierProvider( ArticleListQuery(feedId: feedId, publisherId: publisherId), ); return PaginationList( provider: provider, notifier: provider.notifier, isRefreshable: false, isSliver: true, itemBuilder: (context, index, article) { return WebArticleCard(article: article, showDetails: true); }, ); } } @riverpod Future> subscribedFeeds(Ref ref) async { final client = ref.watch(apiClientProvider); final response = await client.get('/sphere/feeds/subscribed'); final data = response.data as List; return data.map((json) => SnWebFeed.fromJson(json)).toList(); } class ArticlesScreen extends ConsumerWidget { const ArticlesScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final subscribedFeedsAsync = ref.watch(subscribedFeedsProvider); return subscribedFeedsAsync.when( data: (feeds) { return DefaultTabController( length: feeds.length + 1, child: AppScaffold( isNoBackground: false, appBar: AppBar( title: const Text('Articles'), bottom: TabBar( isScrollable: true, tabs: [ Tab( child: Text( 'All', textAlign: TextAlign.center, style: TextStyle( color: Theme.of(context).appBarTheme.foregroundColor!, ), ), ), ...feeds.map( (feed) => Tab( child: Text( feed.title, textAlign: TextAlign.center, style: TextStyle( color: Theme.of(context).appBarTheme.foregroundColor!, ), ), ), ), ], ), ), body: TabBarView( children: [ Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 560), child: CustomScrollView( slivers: [ SliverPadding( padding: const EdgeInsets.only( top: 12, left: 8, right: 8, ), sliver: SliverArticlesList(), ), ], ), ), ), ...feeds.map((feed) { return Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 560), child: CustomScrollView( slivers: [ SliverPadding( padding: const EdgeInsets.only( top: 8, left: 8, right: 8, ), sliver: SliverArticlesList(feedId: feed.id), ), ], ), ), ); }), ], ), ), ); }, loading: () => AppScaffold( isNoBackground: false, appBar: AppBar(title: const Text('Articles')), body: const Center(child: CircularProgressIndicator()), ), error: (err, stack) => AppScaffold( isNoBackground: false, appBar: AppBar(title: const Text('Articles')), body: Center(child: Text('Error: $err')), ), ); } }