feat: add HTML providers for Realbooru and Paheal, hidden posts screen, and UI/search enhancements
This commit is contained in:
@@ -11,11 +11,19 @@ import '../../../shared/widgets/error_view.dart';
|
||||
import '../../../shared/widgets/post_masonry_grid.dart';
|
||||
import 'favorites_controller.dart';
|
||||
|
||||
class FavoritesScreen extends ConsumerWidget {
|
||||
class FavoritesScreen extends ConsumerStatefulWidget {
|
||||
const FavoritesScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
ConsumerState<FavoritesScreen> createState() => _FavoritesScreenState();
|
||||
}
|
||||
|
||||
class _FavoritesScreenState extends ConsumerState<FavoritesScreen> {
|
||||
int _tabIndex = 0;
|
||||
String? _selectedArtist;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = ref.watch(favoritesControllerProvider);
|
||||
final settings =
|
||||
ref.watch(appSettingsProvider).value ?? AppSettings.defaults;
|
||||
@@ -33,28 +41,153 @@ class FavoritesScreen extends ConsumerWidget {
|
||||
error: (error, _) => ErrorView(message: error.toString()),
|
||||
data: (data) => data.posts.isEmpty
|
||||
? const EmptyView(title: 'No favorites yet')
|
||||
: PostMasonryGrid(
|
||||
posts: data.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: data.posts.map((post) => post.cacheKey).toSet(),
|
||||
downloadedKeys: downloaded.keys.toSet(),
|
||||
onOpen: (post) => context.push(
|
||||
'/post/${post.providerId}/${post.id}',
|
||||
extra: post,
|
||||
),
|
||||
onFavorite: (post) =>
|
||||
ref.read(favoritesControllerProvider.notifier).remove(post),
|
||||
: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 6),
|
||||
child: SegmentedButton<int>(
|
||||
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<Post> posts,
|
||||
AppSettings settings,
|
||||
Set<String> 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<Post> posts,
|
||||
AppSettings settings,
|
||||
Set<String> 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<String, List<Post>> favoriteArtistAlbums(List<Post> posts) {
|
||||
final groups = <String, List<Post>>{};
|
||||
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())),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user