Add provider tag suggestions and tag categories
This commit is contained in:
@@ -325,6 +325,9 @@ class _MobilePostPagerState extends State<_MobilePostPager> {
|
||||
super.initState();
|
||||
_currentIndex = widget.initialIndex;
|
||||
_controller = PageController(initialPage: widget.initialIndex);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_prefetchAround(widget.initialIndex);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -337,6 +340,9 @@ class _MobilePostPagerState extends State<_MobilePostPager> {
|
||||
if (_controller.hasClients) {
|
||||
_controller.jumpToPage(widget.initialIndex);
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_prefetchAround(widget.initialIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,6 +359,7 @@ class _MobilePostPagerState extends State<_MobilePostPager> {
|
||||
itemCount: widget.posts.length,
|
||||
onPageChanged: (index) {
|
||||
_currentIndex = index;
|
||||
_prefetchAround(index);
|
||||
final post = widget.posts[index];
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
@@ -363,13 +370,52 @@ class _MobilePostPagerState extends State<_MobilePostPager> {
|
||||
});
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
return KeyedSubtree(
|
||||
key: ValueKey(widget.posts[index].cacheKey),
|
||||
child: widget.buildDetails(context, widget.posts[index]),
|
||||
return _KeepAlivePostPage(
|
||||
child: KeyedSubtree(
|
||||
key: ValueKey(widget.posts[index].cacheKey),
|
||||
child: widget.buildDetails(context, widget.posts[index]),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _prefetchAround(int index) {
|
||||
for (final offset in [-1, 0, 1]) {
|
||||
final target = index + offset;
|
||||
if (target < 0 || target >= widget.posts.length) continue;
|
||||
final post = widget.posts[target];
|
||||
final urls = [
|
||||
post.previewUrl,
|
||||
post.sampleUrl,
|
||||
if (post.fileType.toLowerCase().contains('gif')) post.fileUrl,
|
||||
].where((url) => url.trim().isNotEmpty).toSet();
|
||||
for (final url in urls) {
|
||||
precacheImage(NetworkImage(url), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _KeepAlivePostPage extends StatefulWidget {
|
||||
const _KeepAlivePostPage({required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<_KeepAlivePostPage> createState() => _KeepAlivePostPageState();
|
||||
}
|
||||
|
||||
class _KeepAlivePostPageState extends State<_KeepAlivePostPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return widget.child;
|
||||
}
|
||||
}
|
||||
|
||||
class _CommentsSection extends ConsumerStatefulWidget {
|
||||
|
||||
@@ -214,11 +214,10 @@ class _PostMediaViewerState extends State<PostMediaViewer> {
|
||||
}
|
||||
|
||||
List<String> _buildImageUrls(Post post) {
|
||||
return {
|
||||
post.sampleUrl,
|
||||
post.fileUrl,
|
||||
post.previewUrl,
|
||||
}.where((url) => url.trim().isNotEmpty).toList();
|
||||
final urls = post.fileType.toLowerCase().contains('gif')
|
||||
? [post.fileUrl, post.sampleUrl, post.previewUrl]
|
||||
: [post.sampleUrl, post.fileUrl, post.previewUrl];
|
||||
return urls.where((url) => url.trim().isNotEmpty).toSet().toList();
|
||||
}
|
||||
|
||||
List<String> _buildVideoUrls(Post post) {
|
||||
|
||||
@@ -11,16 +11,66 @@ class PostTagsPanel extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
final groups = _groups(post);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (final tag in post.tags.take(80))
|
||||
TagChip(
|
||||
tag: tag,
|
||||
onTap: () => context.go('/?q=${Uri.encodeQueryComponent(tag)}'),
|
||||
for (final entry in groups.entries) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 6),
|
||||
child: Text(
|
||||
_label(entry.key),
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (final tag in entry.value.take(80))
|
||||
TagChip(
|
||||
tag: tag,
|
||||
onTap: () =>
|
||||
context.go('/?q=${Uri.encodeQueryComponent(tag)}'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, List<String>> _groups(Post post) {
|
||||
if (post.tagGroups.isNotEmpty) {
|
||||
final ordered = <String, List<String>>{};
|
||||
for (final key in [
|
||||
'artist',
|
||||
'character',
|
||||
'copyright',
|
||||
'species',
|
||||
'meta',
|
||||
'general',
|
||||
]) {
|
||||
final tags = post.tagGroups[key];
|
||||
if (tags != null && tags.isNotEmpty) ordered[key] = tags;
|
||||
}
|
||||
for (final entry in post.tagGroups.entries) {
|
||||
ordered.putIfAbsent(entry.key, () => entry.value);
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
return {'general': post.tags};
|
||||
}
|
||||
|
||||
String _label(String key) {
|
||||
return switch (key) {
|
||||
'artist' => 'Artist',
|
||||
'character' => 'Character',
|
||||
'copyright' => 'Copyright / Title',
|
||||
'species' => 'Species',
|
||||
'meta' => 'Meta',
|
||||
'general' => 'General',
|
||||
_ => 'Other',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user