import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../app/app.dart'; import '../../../app/responsive.dart'; import '../../../backend/backend.dart'; import '../../../shared/widgets/adaptive_scaffold.dart'; import '../../../shared/widgets/empty_view.dart'; import '../../../shared/widgets/error_view.dart'; import '../../../shared/widgets/post_masonry_grid.dart'; import 'favorites_controller.dart'; class FavoritesScreen extends ConsumerStatefulWidget { const FavoritesScreen({super.key}); @override ConsumerState createState() => _FavoritesScreenState(); } class _FavoritesScreenState extends ConsumerState { int _tabIndex = 0; String? _selectedArtist; @override Widget build(BuildContext context) { final state = ref.watch(favoritesControllerProvider); final settings = ref.watch(appSettingsProvider).value ?? AppSettings.defaults; final posts = state.value?.posts ?? const []; final downloaded = ref .watch(downloadedMediaByKeysProvider( posts.map((post) => post.cacheKey).toList(growable: false), )) .value ?? const {}; return AdaptiveScaffold( title: settings.languageCode == 'ru' ? 'Избранное' : 'Favorites', body: state.when( loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => ErrorView(message: error.toString()), data: (data) => data.posts.isEmpty ? const EmptyView(title: 'No favorites yet') : Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(12, 8, 12, 6), child: SegmentedButton( segments: const [ ButtonSegment( value: 0, icon: Icon(Icons.favorite_rounded), label: Text('All'), ), ButtonSegment( value: 1, icon: Icon(Icons.offline_pin_rounded), label: Text('Offline'), ), ButtonSegment( value: 2, icon: Icon(Icons.groups_rounded), label: Text('Artists'), ), ], selected: {_tabIndex}, onSelectionChanged: (value) { setState(() { _tabIndex = value.first; _selectedArtist = null; }); }, ), ), Expanded( child: switch (_tabIndex) { 1 => _postsGrid( data.posts .where((post) => downloaded.containsKey(post.cacheKey)) .toList(), settings, downloaded.keys.toSet(), ), 2 => _artistAlbums( data.posts, settings, downloaded.keys.toSet(), ), _ => _postsGrid( data.posts, settings, downloaded.keys.toSet(), ), }, ), ], ), ), ); } Widget _postsGrid( List posts, AppSettings settings, Set downloadedKeys, ) { if (posts.isEmpty) return const EmptyView(title: 'Nothing here yet'); return PostMasonryGrid( posts: posts, columns: Responsive.columnsFor( context, mobileColumns: settings.mobileColumns, desktopColumns: settings.desktopColumns, ), blurExplicit: settings.blurExplicitContent, showBadges: settings.showPostBadges, nsfwEnabled: settings.nsfwEnabled, mediaQualityMode: MediaQualityMode.fromName(settings.mediaQualityMode), favoriteKeys: posts.map((post) => post.cacheKey).toSet(), downloadedKeys: downloadedKeys, onOpen: (post) => context.push( '/post/${post.providerId}/${post.id}', extra: post, ), onFavorite: (post) => ref.read(favoritesControllerProvider.notifier).remove(post), ); } Widget _artistAlbums( List posts, AppSettings settings, Set downloadedKeys, ) { final groups = favoriteArtistAlbums(posts); final selected = _selectedArtist; if (selected != null) { return Column( children: [ Align( alignment: Alignment.centerLeft, child: TextButton.icon( onPressed: () => setState(() => _selectedArtist = null), icon: const Icon(Icons.arrow_back_rounded), label: Text(selected), ), ), Expanded( child: _postsGrid( groups[selected] ?? const [], settings, downloadedKeys, ), ), ], ); } return ListView( padding: const EdgeInsets.fromLTRB(12, 8, 12, 24), children: [ for (final entry in groups.entries) Card( child: ListTile( leading: const Icon(Icons.person_rounded), title: Text(entry.key), subtitle: Text('${entry.value.length} posts'), trailing: const Icon(Icons.chevron_right_rounded), onTap: () => setState(() => _selectedArtist = entry.key), ), ), ], ); } } Map> favoriteArtistAlbums(List posts) { final groups = >{}; for (final post in posts) { final artists = post.tagGroups['artist'] ?? post.tags.where((tag) => tag.startsWith('artist:')).toList(); final names = artists.isEmpty ? const ['Unknown artist'] : artists.map((tag) => tag.replaceFirst('artist:', '')).toList(); for (final name in names) { groups.putIfAbsent(name, () => []).add(post); } } return Map.fromEntries( groups.entries.toList() ..sort((a, b) => a.key.toLowerCase().compareTo(b.key.toLowerCase())), ); }