Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to select thumbnail quality #1329

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/core/enums/local_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ enum LocalSettings {
useCompactView(name: 'setting_general_use_compact_view', key: 'compactView', category: LocalSettingsCategories.posts, subCategory: LocalSettingsSubCategories.posts),
showPostTitleFirst(name: 'setting_general_show_title_first', key: 'showPostTitleFirst', category: LocalSettingsCategories.posts, subCategory: LocalSettingsSubCategories.posts),
hideThumbnails(name: 'setting_general_hide_thumbnails', key: 'hideThumbnails', category: LocalSettingsCategories.posts, subCategory: LocalSettingsSubCategories.feed),
thumbnailQuality(name: 'setting_general_thumbnail_quality', key: 'thumbnailQuality', category: LocalSettingsCategories.posts, subCategory: LocalSettingsSubCategories.feed),
showThumbnailPreviewOnRight(
name: 'setting_compact_show_thumbnail_on_right', key: 'showThumbnailPreviewOnRight', category: LocalSettingsCategories.posts, subCategory: LocalSettingsSubCategories.posts),
showTextPostIndicator(name: 'setting_compact_show_text_post_indicator', key: 'showTextPostIndicator', category: LocalSettingsCategories.posts, subCategory: LocalSettingsSubCategories.posts),
Expand Down Expand Up @@ -334,6 +335,7 @@ extension LocalizationExt on AppLocalizations {
'compactView': compactView,
'showPostTitleFirst': showPostTitleFirst,
'hideThumbnails': hideThumbnails,
'thumbnailQuality': thumbnailQuality,
'showThumbnailPreviewOnRight': showThumbnailPreviewOnRight,
'showTextPostIndicator': showTextPostIndicator,
'tappableAuthorCommunity': tappableAuthorCommunity,
Expand Down
20 changes: 20 additions & 0 deletions lib/core/enums/media_type.dart
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
enum MediaType { image, video, link, text }

enum MediaQuality {
full,
high,
medium,
low;

get size {
switch (this) {
case MediaQuality.full:
return null;
case MediaQuality.high:
return 1080;
case MediaQuality.medium:
return 720;
case MediaQuality.low:
return 480;
}
}
}
4 changes: 4 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1799,6 +1799,10 @@
"@thickness": {
"description": "Describes a thickness (e.g., divider)"
},
"thumbnailQuality": "Thumbnail Quality",
"@thumbnailQuality": {
"description": "Setting for thumbnail quality"
},
"thunderHasBeenUpdated": "Thunder updated to {version}!",
"@thunderHasBeenUpdated": {
"description": "Heading for changelog when Thunder has been updated"
Expand Down
16 changes: 13 additions & 3 deletions lib/post/utils/post.dart
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ Future<List<PostViewMedia>> parsePostViews(List<PostView> postViews, {String? re
bool tabletMode = prefs.getBool(LocalSettings.useTabletMode.name) ?? false;
bool hideNsfwPosts = prefs.getBool(LocalSettings.hideNsfwPosts.name) ?? false;
bool scrapeMissingPreviews = prefs.getBool(LocalSettings.scrapeMissingPreviews.name) ?? false;
MediaQuality thumbnailQuality = MediaQuality.values.byName(prefs.getString(LocalSettings.thumbnailQuality.name) ?? MediaQuality.medium.name);

List<PostView> postViewsFinal = [];

Expand All @@ -261,7 +262,7 @@ Future<List<PostViewMedia>> parsePostViews(List<PostView> postViews, {String? re
Iterable<Future<PostViewMedia>> postFutures = postViewsFinal
.expand(
(post) => [
if (!hideNsfwPosts || (!post.post.nsfw && hideNsfwPosts)) parsePostView(post, fetchImageDimensions, edgeToEdgeImages, tabletMode, scrapeMissingPreviews),
if (!hideNsfwPosts || (!post.post.nsfw && hideNsfwPosts)) parsePostView(post, fetchImageDimensions, edgeToEdgeImages, tabletMode, scrapeMissingPreviews, thumbnailQuality),
],
)
.toList();
Expand All @@ -270,7 +271,7 @@ Future<List<PostViewMedia>> parsePostViews(List<PostView> postViews, {String? re
return posts;
}

Future<PostViewMedia> parsePostView(PostView postView, bool fetchImageDimensions, bool edgeToEdgeImages, bool tabletMode, bool scrapeMissingPreviews) async {
Future<PostViewMedia> parsePostView(PostView postView, bool fetchImageDimensions, bool edgeToEdgeImages, bool tabletMode, bool scrapeMissingPreviews, MediaQuality thumbnailQuality) async {
List<Media> mediaList = [];

// There are three sources of URLs: the main url attached to the post, the thumbnail url attached to the post, and the video url attached to the post
Expand Down Expand Up @@ -299,6 +300,15 @@ Future<PostViewMedia> parsePostView(PostView postView, bool fetchImageDimensions
if (thumbnailUrl != null && thumbnailUrl.isNotEmpty) {
// Now check to see if there is a thumbnail image. If there is, we'll use that for the image
media.mediaUrl = thumbnailUrl;

// The thumbnail is typically from the /pictrs/ endpoint, so we can specify the height of the image. This will reduce resolution, but will speed up loading
if (isPictrsEndpoint(thumbnailUrl)) {
if (thumbnailQuality != MediaQuality.full) {
media.mediaUrl = '$thumbnailUrl?thumbnail=${thumbnailQuality.size}&format=png';
} else {
media.mediaUrl = '$thumbnailUrl?format=png';
}
}
} else if (isImage) {
// If there is no thumbnail image, but the url is an image, we'll use that for the mediaUrl
media.mediaUrl = url;
Expand All @@ -316,7 +326,7 @@ Future<PostViewMedia> parsePostView(PostView postView, bool fetchImageDimensions
Size result = Size(MediaQuery.of(GlobalContext.context).size.width, 200);

try {
result = await retrieveImageDimensions(imageUrl: media.mediaUrl ?? media.originalUrl).timeout(const Duration(seconds: 2));
result = await retrieveImageDimensions(imageUrl: media.mediaUrl ?? media.originalUrl).timeout(const Duration(seconds: 4));
} catch (e) {
debugPrint('${media.mediaUrl ?? media.originalUrl} - $e: Falling back to default image size');
}
Expand Down
26 changes: 26 additions & 0 deletions lib/settings/pages/post_appearance_settings_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:thunder/community/widgets/post_card_view_compact.dart';
import 'package:thunder/core/enums/custom_theme_type.dart';
import 'package:thunder/core/enums/feed_card_divider_thickness.dart';
import 'package:thunder/core/enums/local_settings.dart';
import 'package:thunder/core/enums/media_type.dart';
import 'package:thunder/core/enums/post_body_view_type.dart';
import 'package:thunder/core/enums/view_mode.dart';
import 'package:thunder/core/models/post_view_media.dart';
Expand Down Expand Up @@ -54,6 +55,9 @@ class _PostAppearanceSettingsPageState extends State<PostAppearanceSettingsPage>
/// When enabled, the thumbnails in compact/card mode will be hidden
bool hideThumbnails = false;

/// The quality of the thumbnails
MediaQuality thumbnailQuality = MediaQuality.medium;

/// When enabled, the thumbnail previews will be shown on the right. By default, they are shown on the left
bool showThumbnailPreviewOnRight = false;

Expand Down Expand Up @@ -143,6 +147,7 @@ class _PostAppearanceSettingsPageState extends State<PostAppearanceSettingsPage>
useCompactView = prefs.getBool(LocalSettings.useCompactView.name) ?? false;
hideNsfwPreviews = prefs.getBool(LocalSettings.hideNsfwPreviews.name) ?? true;
hideThumbnails = prefs.getBool(LocalSettings.hideThumbnails.name) ?? false;
thumbnailQuality = MediaQuality.values.byName(prefs.getString(LocalSettings.thumbnailQuality.name) ?? MediaQuality.medium.name);
showPostAuthor = prefs.getBool(LocalSettings.showPostAuthor.name) ?? false;
useDisplayNames = prefs.getBool(LocalSettings.useDisplayNamesForUsers.name) ?? true;
postShowUserInstance = prefs.getBool(LocalSettings.postShowUserInstance.name) ?? false;
Expand Down Expand Up @@ -194,6 +199,10 @@ class _PostAppearanceSettingsPageState extends State<PostAppearanceSettingsPage>
await prefs.setBool(LocalSettings.hideThumbnails.name, value);
setState(() => hideThumbnails = value);
break;
case LocalSettings.thumbnailQuality:
await prefs.setString(LocalSettings.thumbnailQuality.name, (value as MediaQuality).name);
setState(() => thumbnailQuality = value);
break;
case LocalSettings.showPostAuthor:
await prefs.setBool(LocalSettings.showPostAuthor.name, value);
setState(() => showPostAuthor = value);
Expand Down Expand Up @@ -304,6 +313,7 @@ class _PostAppearanceSettingsPageState extends State<PostAppearanceSettingsPage>
await prefs.remove(LocalSettings.useCompactView.name);
await prefs.remove(LocalSettings.hideNsfwPreviews.name);
await prefs.remove(LocalSettings.hideThumbnails.name);
await prefs.remove(LocalSettings.thumbnailQuality.name);
await prefs.remove(LocalSettings.showPostAuthor.name);
await prefs.remove(LocalSettings.useDisplayNamesForUsers.name);
await prefs.remove(LocalSettings.postShowUserInstance.name);
Expand Down Expand Up @@ -600,6 +610,22 @@ class _PostAppearanceSettingsPageState extends State<PostAppearanceSettingsPage>
highlightKey: settingToHighlight == LocalSettings.hideThumbnails ? settingToHighlightKey : null,
),
),
SliverToBoxAdapter(
child: ListOption(
description: l10n.thumbnailQuality,
subtitle: "Only supported for thumbnails hosted on instance",
value: ListPickerItem(label: thumbnailQuality.name, icon: Icons.high_quality_rounded, payload: thumbnailQuality),
options: [
ListPickerItem(icon: Icons.star_rounded, label: MediaQuality.full.name, payload: MediaQuality.full),
ListPickerItem(icon: Icons.expand_less_rounded, label: MediaQuality.high.name, payload: MediaQuality.high),
ListPickerItem(icon: Icons.fit_screen, label: MediaQuality.medium.name, payload: MediaQuality.medium),
ListPickerItem(icon: Icons.expand_more_rounded, label: MediaQuality.low.name, payload: MediaQuality.low),
],
icon: Icons.high_quality_rounded,
onChanged: (value) async => setPreferences(LocalSettings.thumbnailQuality, value.payload),
highlightKey: settingToHighlight == LocalSettings.thumbnailQuality ? settingToHighlightKey : null,
),
),
SliverToBoxAdapter(
child: ToggleOption(
description: l10n.showPostAuthor,
Expand Down
3 changes: 3 additions & 0 deletions lib/thunder/bloc/thunder_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'package:thunder/core/enums/font_scale.dart';
import 'package:thunder/core/enums/full_name.dart';
import 'package:thunder/core/enums/image_caching_mode.dart';
import 'package:thunder/core/enums/local_settings.dart';
import 'package:thunder/core/enums/media_type.dart';
import 'package:thunder/core/enums/nested_comment_indicator.dart';
import 'package:thunder/notification/enums/notification_type.dart';
import 'package:thunder/core/enums/post_body_view_type.dart';
Expand Down Expand Up @@ -142,6 +143,7 @@ class ThunderBloc extends Bloc<ThunderEvent, ThunderState> {
bool useCompactView = prefs.getBool(LocalSettings.useCompactView.name) ?? false;
bool showTitleFirst = prefs.getBool(LocalSettings.showPostTitleFirst.name) ?? false;
bool hideThumbnails = prefs.getBool(LocalSettings.hideThumbnails.name) ?? false;
MediaQuality thumbnailQuality = MediaQuality.values.byName(prefs.getString(LocalSettings.thumbnailQuality.name) ?? MediaQuality.medium.name);
bool showThumbnailPreviewOnRight = prefs.getBool(LocalSettings.showThumbnailPreviewOnRight.name) ?? false;
bool showTextPostIndicator = prefs.getBool(LocalSettings.showTextPostIndicator.name) ?? false;
bool tappableAuthorCommunity = prefs.getBool(LocalSettings.tappableAuthorCommunity.name) ?? false;
Expand Down Expand Up @@ -299,6 +301,7 @@ class ThunderBloc extends Bloc<ThunderEvent, ThunderState> {
useCompactView: useCompactView,
showTitleFirst: showTitleFirst,
hideThumbnails: hideThumbnails,
thumbnailQuality: thumbnailQuality,
showThumbnailPreviewOnRight: showThumbnailPreviewOnRight,
showTextPostIndicator: showTextPostIndicator,
tappableAuthorCommunity: tappableAuthorCommunity,
Expand Down
5 changes: 5 additions & 0 deletions lib/thunder/bloc/thunder_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class ThunderState extends Equatable {
this.useCompactView = false,
this.showTitleFirst = false,
this.hideThumbnails = false,
this.thumbnailQuality = MediaQuality.medium,
this.showThumbnailPreviewOnRight = false,
this.showTextPostIndicator = false,
this.tappableAuthorCommunity = false,
Expand Down Expand Up @@ -207,6 +208,7 @@ class ThunderState extends Equatable {
final bool useCompactView;
final bool showTitleFirst;
final bool hideThumbnails;
final MediaQuality thumbnailQuality;
final bool showThumbnailPreviewOnRight;
final bool showTextPostIndicator;
final bool tappableAuthorCommunity;
Expand Down Expand Up @@ -368,6 +370,7 @@ class ThunderState extends Equatable {
bool? useCompactView,
bool? showTitleFirst,
bool? hideThumbnails,
MediaQuality? thumbnailQuality,
bool? showThumbnailPreviewOnRight,
bool? showTextPostIndicator,
bool? tappableAuthorCommunity,
Expand Down Expand Up @@ -524,6 +527,7 @@ class ThunderState extends Equatable {
useCompactView: useCompactView ?? this.useCompactView,
showTitleFirst: showTitleFirst ?? this.showTitleFirst,
hideThumbnails: hideThumbnails ?? this.hideThumbnails,
thumbnailQuality: thumbnailQuality ?? this.thumbnailQuality,
showThumbnailPreviewOnRight: showThumbnailPreviewOnRight ?? this.showThumbnailPreviewOnRight,
showTextPostIndicator: showTextPostIndicator ?? this.showTextPostIndicator,
tappableAuthorCommunity: tappableAuthorCommunity ?? this.tappableAuthorCommunity,
Expand Down Expand Up @@ -683,6 +687,7 @@ class ThunderState extends Equatable {
useCompactView,
showTitleFirst,
hideThumbnails,
thumbnailQuality,
showThumbnailPreviewOnRight,
showTextPostIndicator,
tappableAuthorCommunity,
Expand Down
13 changes: 10 additions & 3 deletions lib/utils/media/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import 'package:thunder/account/models/account.dart';
import 'package:thunder/shared/image_viewer.dart';
import 'package:thunder/shared/snackbar.dart';

String generateRandomHeroString({int? len}) {
Random r = Random();
return String.fromCharCodes(List.generate(len ?? 32, (index) => r.nextInt(33) + 89));
bool isPictrsEndpoint(String url) {
return url.contains('/pictrs/image/');
}

bool isImageUrl(String url) {
Expand All @@ -26,6 +25,7 @@ bool isImageUrl(String url) {
} catch (e) {
return false;
}

final path = uri.path.toLowerCase();

for (final extension in imageExtensions) {
Expand Down Expand Up @@ -70,13 +70,15 @@ Future<Size> retrieveImageDimensions({String? imageUrl, Uint8List? imageBytes})
// are all PNGs.
return getPNGImageDimensions(imageBytes);
}

// We know imageUrl is not null here due to the assertion.
else {
bool isImage = isImageUrl(imageUrl!);
if (!isImage) throw Exception('The URL provided was not an image');

final uri = Uri.parse(imageUrl);
final path = uri.path.toLowerCase();
final query = uri.queryParameters;

// We'll just retrieve the first part of the image
final rangeResponse = await http.get(
Expand All @@ -87,6 +89,11 @@ Future<Size> retrieveImageDimensions({String? imageUrl, Uint8List? imageBytes})
// Read the response body as bytes
final imageData = rangeResponse.bodyBytes;

// Override the image type if it's a Pictrs endpoint since we specify the format
if (isPictrsEndpoint(imageUrl) && query.containsKey("format") && query["format"] == "png") {
return getPNGImageDimensions(imageData);
}

// Get the image dimensions
if (path.endsWith('jpg') || path.endsWith('jpeg')) {
return getJPEGImageDimensions(imageData);
Expand Down
Loading