Add QoL feed, download and quality improvements

This commit is contained in:
2026-05-24 13:02:06 +03:00
parent 7f79addb14
commit ec29ed21d2
25 changed files with 942 additions and 62 deletions
@@ -84,6 +84,8 @@ class PostDetailsScreen extends ConsumerWidget {
final next = currentIndex >= 0 && currentIndex < feedPosts.length - 1
? feedPosts[currentIndex + 1]
: null;
final qualityMode =
MediaQualityMode.fromName(settings.mediaQualityMode);
if (Responsive.isMobile(context)) {
if (currentIndex >= 0 && feedPosts.length > 1) {
return _MobilePostPager(
@@ -95,6 +97,7 @@ class PostDetailsScreen extends ConsumerWidget {
post,
settings,
favoriteKeys,
qualityMode,
),
);
}
@@ -104,6 +107,7 @@ class PostDetailsScreen extends ConsumerWidget {
post,
settings,
favoriteKeys,
qualityMode,
);
}
return Shortcuts(
@@ -116,6 +120,7 @@ class PostDetailsScreen extends ConsumerWidget {
const _ToggleFavoriteIntent(),
LogicalKeySet(LogicalKeyboardKey.keyC):
const _AddCollectionIntent(),
LogicalKeySet(LogicalKeyboardKey.keyS): const _DownloadIntent(),
LogicalKeySet(LogicalKeyboardKey.escape): const _CloseIntent(),
},
child: Actions(
@@ -150,6 +155,14 @@ class PostDetailsScreen extends ConsumerWidget {
return null;
},
),
_DownloadIntent: CallbackAction<_DownloadIntent>(
onInvoke: (_) {
if (settings.allowDownloads) {
_download(context, ref, post);
}
return null;
},
),
},
child: Focus(
autofocus: true,
@@ -170,7 +183,10 @@ class PostDetailsScreen extends ConsumerWidget {
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 760),
child: PostMediaViewer(post: post),
child: PostMediaViewer(
post: post,
qualityMode: qualityMode,
),
),
),
),
@@ -243,6 +259,7 @@ class PostDetailsScreen extends ConsumerWidget {
Post post,
AppSettings settings,
Set<String> favoriteKeys,
MediaQualityMode qualityMode,
) {
return ListView(
padding: const EdgeInsets.fromLTRB(8, 8, 8, 16),
@@ -251,7 +268,12 @@ class PostDetailsScreen extends ConsumerWidget {
constraints: BoxConstraints(
maxHeight: MediaQuery.sizeOf(context).height * 0.62,
),
child: Center(child: PostMediaViewer(post: post)),
child: Center(
child: PostMediaViewer(
post: post,
qualityMode: qualityMode,
),
),
),
const SizedBox(height: 12),
PostActionBar(
@@ -319,22 +341,12 @@ class PostDetailsScreen extends ConsumerWidget {
}
Future<void> _download(BuildContext context, WidgetRef ref, Post post) async {
if (!context.mounted) return;
await ref.read(downloadManagerServiceProvider).start(post);
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Downloading...')),
const SnackBar(content: Text('Download started')),
);
try {
final saved = await ref.read(downloadServiceProvider).downloadPost(post);
if (!context.mounted || saved == null) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Download complete: $saved')),
);
} catch (error) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Download failed: $error')),
);
}
}
Future<void> _toggleFavorite(
@@ -583,6 +595,10 @@ class _CloseIntent extends Intent {
const _CloseIntent();
}
class _DownloadIntent extends Intent {
const _DownloadIntent();
}
class _CommentsSection extends ConsumerStatefulWidget {
const _CommentsSection({required this.post});
@@ -24,6 +24,7 @@ class PostMediaViewer extends StatefulWidget {
this.initialMuted = false,
this.initialCoverVideo = false,
this.initialHalfVolume = false,
this.qualityMode = MediaQualityMode.auto,
this.onPlaybackSnapshot,
super.key,
});
@@ -36,6 +37,7 @@ class PostMediaViewer extends StatefulWidget {
final bool initialMuted;
final bool initialCoverVideo;
final bool initialHalfVolume;
final MediaQualityMode qualityMode;
final ValueChanged<VideoPlaybackSnapshot>? onPlaybackSnapshot;
@override
@@ -281,18 +283,11 @@ class _PostMediaViewerState extends State<PostMediaViewer> {
}
List<String> _buildImageUrls(Post post) {
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();
return MediaUrlSelector.details(post, mode: widget.qualityMode);
}
List<String> _buildVideoUrls(Post post) {
return {
post.fileUrl,
if (_looksLikeVideoUrl(post.sampleUrl)) post.sampleUrl,
if (_looksLikeVideoUrl(post.previewUrl)) post.previewUrl,
}.where((url) => url.trim().isNotEmpty).toList();
return MediaUrlSelector.video(post);
}
bool _isVideo(Post post) {
@@ -318,13 +313,6 @@ class _PostMediaViewerState extends State<PostMediaViewer> {
return post.fileUrl.trim();
}
bool _looksLikeVideoUrl(String url) {
final value = url.toLowerCase();
return value.contains('.webm') ||
value.contains('.mp4') ||
value.contains('.mov');
}
Map<String, String> _headersFor(Post post) {
return {
'User-Agent': 'RuleGel/0.2 Flutter local booru browser',
@@ -796,6 +784,7 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
initialMuted: widget.muted,
initialHalfVolume: widget.halfVolume,
initialCoverVideo: widget.coverVideo,
qualityMode: MediaQualityMode.highQuality,
onPlaybackSnapshot: (snapshot) => _snapshot = snapshot,
),
),