import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:worldhopper/l10n/app_localizations.dart'; import 'package:worldhopper/config/constants.dart'; import 'package:worldhopper/helpers/responsive_helper.dart'; import 'package:worldhopper/models/downloaded_series.dart'; import 'package:worldhopper/providers/download_provider.dart'; import 'package:worldhopper/providers/reading_progress_provider.dart'; import 'package:worldhopper/providers/server_provider.dart'; import 'package:worldhopper/widgets/downloaded_series_card.dart'; import 'package:worldhopper/widgets/worldhopper_app_bar.dart'; import 'package:worldhopper/widgets/recently_read_card.dart'; /// Screen displaying the user's reading history and library class LibraryScreen extends ConsumerWidget { const LibraryScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context); final recentlyReadAsync = ref.watch(recentlyReadProvider); final downloadedSeriesAsync = ref.watch(downloadedSeriesListProvider); return Scaffold( appBar: WorldhopperAppBar( title: Text(l10n.libraryTitle), ), body: RefreshIndicator( onRefresh: () async { ref.invalidate(recentlyReadProvider); ref.invalidate(downloadedSeriesListProvider); }, child: recentlyReadAsync.when( data: (recentlyRead) { final downloadedSeries = downloadedSeriesAsync.valueOrNull ?? []; if (recentlyRead.isEmpty && downloadedSeries.isEmpty) { return _buildEmptyState(context, ref, l10n); } return LayoutBuilder( builder: (context, constraints) { return CustomScrollView( slivers: [ // Recently read section if (recentlyRead.isNotEmpty) ...[ SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0), child: Text( l10n.libraryRecentlyRead, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), ), ), SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16.0), sliver: SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: gridColumnCount(constraints.maxWidth), childAspectRatio: 0.7, crossAxisSpacing: 16, mainAxisSpacing: 16, ), delegate: SliverChildBuilderDelegate( (context, index) { final progress = recentlyRead[index]; return RecentlyReadCard(progress: progress); }, childCount: recentlyRead.length, ), ), ), ], // Downloaded section if (downloadedSeries.isNotEmpty) ...[ SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(16.0, 24.0, 16.0, 8.0), child: Text( l10n.libraryDownloaded, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), ), ), SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16.0), sliver: SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: gridColumnCount(constraints.maxWidth), childAspectRatio: 0.7, crossAxisSpacing: 16, mainAxisSpacing: 16, ), delegate: SliverChildBuilderDelegate( (context, index) { final series = downloadedSeries[index]; return DownloadedSeriesCard( series: series, onTap: () { context.push( AppConstants.routeDownloadedSeriesDetail, extra: {'seriesId': series.id}, ); }, onLongPress: () => _confirmDeleteSeries(context, ref, series), ); }, childCount: downloadedSeries.length, ), ), ), ], const SliverToBoxAdapter( child: SizedBox(height: 16), ), ], ); }, ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) => _buildErrorState(context, l10n, error), ), ), ); } Future _confirmDeleteSeries( BuildContext context, WidgetRef ref, DownloadedSeries series, ) async { final l10n = AppLocalizations.of(context); final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: Text(l10n.downloadDeleteSeries), content: Text(l10n.downloadDeleteSeriesConfirmation(series.title)), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: Text(l10n.commonCancel), ), FilledButton( onPressed: () => Navigator.of(context).pop(true), style: FilledButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.error, ), child: Text(l10n.commonDelete), ), ], ), ); if (confirmed == true) { await ref.read(downloadNotifierProvider.notifier).deleteSeries(series.id); } } Widget _buildEmptyState( BuildContext context, WidgetRef ref, AppLocalizations l10n) { final serversAsync = ref.watch(serverListProvider); return serversAsync.when( data: (servers) { // Case 1: No servers configured if (servers.isEmpty) { return _buildNoServersState(context, l10n); } // Case 2: Servers exist but no reading history return _buildNoHistoryState(context, l10n); }, loading: () => const Center(child: CircularProgressIndicator()), error: (_, __) => _buildNoHistoryState(context, l10n), // Fallback ); } Widget _buildNoServersState(BuildContext context, AppLocalizations l10n) { return Center( child: Padding( padding: const EdgeInsets.all(32.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.dns_outlined, size: 80, color: Theme.of(context).colorScheme.outline, ), const SizedBox(height: 24), Text( l10n.libraryNoServers, style: Theme.of(context).textTheme.headlineSmall, textAlign: TextAlign.center, ), const SizedBox(height: 12), Text( l10n.libraryNoServersSubtitle, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context) .colorScheme .onSurface .withValues(alpha: 0.6), ), textAlign: TextAlign.center, ), const SizedBox(height: 24), FilledButton.icon( onPressed: () => context.go(AppConstants.routeServerList), icon: const Icon(Icons.add), label: Text(l10n.libraryAddServer), ), ], ), ), ); } Widget _buildNoHistoryState(BuildContext context, AppLocalizations l10n) { return Center( child: Padding( padding: const EdgeInsets.all(32.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.auto_stories_outlined, size: 80, color: Theme.of(context).colorScheme.outline, ), const SizedBox(height: 24), Text( l10n.libraryNoHistory, style: Theme.of(context).textTheme.headlineSmall, textAlign: TextAlign.center, ), const SizedBox(height: 12), Text( l10n.libraryNoHistorySubtitle, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context) .colorScheme .onSurface .withValues(alpha: 0.6), ), textAlign: TextAlign.center, ), const SizedBox(height: 24), FilledButton.icon( onPressed: () => context.go(AppConstants.routeServerList), icon: const Icon(Icons.dns), label: Text(l10n.libraryBrowseServers), ), ], ), ), ); } Widget _buildErrorState( BuildContext context, AppLocalizations l10n, Object error) { return Center( child: Padding( padding: const EdgeInsets.all(32.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, size: 80, color: Theme.of(context).colorScheme.error, ), const SizedBox(height: 24), Text( l10n.libraryErrorLoading, style: Theme.of(context).textTheme.headlineSmall, textAlign: TextAlign.center, ), const SizedBox(height: 12), Text( error.toString(), style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center, ), ], ), ), ); } }