diff --git a/lib/core/enums/view_mode.dart b/lib/core/enums/view_mode.dart index 21099beca..450690041 100644 --- a/lib/core/enums/view_mode.dart +++ b/lib/core/enums/view_mode.dart @@ -1 +1,9 @@ -enum ViewMode { compact, comfortable } +enum ViewMode { + compact(75.0), + comfortable(150.0); + + /// The height of media previews for the given view mode + final double height; + + const ViewMode(this.height); +} diff --git a/lib/post/utils/post.dart b/lib/post/utils/post.dart index fe5c5cf78..04c9d9c46 100644 --- a/lib/post/utils/post.dart +++ b/lib/post/utils/post.dart @@ -299,6 +299,9 @@ Future 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; + } 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; } else if (scrapeMissingPreviews) { // If there is no thumbnail image, we'll see if we should try to fetch the link metadata LinkInfo linkInfo = await getLinkInfo(url); @@ -313,9 +316,9 @@ Future parsePostView(PostView postView, bool fetchImageDimensions Size result = Size(MediaQuery.of(GlobalContext.context).size.width, 200); try { - result = await retrieveImageDimensions(imageUrl: media.mediaUrl); + result = await retrieveImageDimensions(imageUrl: media.mediaUrl ?? media.originalUrl).timeout(const Duration(seconds: 2)); } catch (e) { - debugPrint('$e: Falling back to default image size'); + debugPrint('${media.mediaUrl ?? media.originalUrl} - $e: Falling back to default image size'); } Size size = MediaExtension.getScaledMediaSize(width: result.width, height: result.height, offset: edgeToEdgeImages ? 0 : 24, tabletMode: tabletMode); diff --git a/lib/settings/pages/post_appearance_settings_page.dart b/lib/settings/pages/post_appearance_settings_page.dart index 22f9b3bd4..e4bd45d58 100644 --- a/lib/settings/pages/post_appearance_settings_page.dart +++ b/lib/settings/pages/post_appearance_settings_page.dart @@ -17,6 +17,7 @@ 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/post_body_view_type.dart'; +import 'package:thunder/core/enums/view_mode.dart'; import 'package:thunder/core/models/post_view_media.dart'; import 'package:thunder/core/singletons/preferences.dart'; import 'package:thunder/feed/feed.dart'; @@ -1159,8 +1160,8 @@ class _PostAppearanceSettingsPageState extends State children: [ !showThumbnailPreviewOnRight ? Container( - width: 75, - height: 75, + width: ViewMode.compact.height, + height: ViewMode.compact.height, margin: const EdgeInsets.only(right: 8.0), decoration: BoxDecoration( color: theme.dividerColor, @@ -1212,8 +1213,8 @@ class _PostAppearanceSettingsPageState extends State ), showThumbnailPreviewOnRight ? Container( - width: 75, - height: 75, + width: ViewMode.compact.height, + height: ViewMode.compact.height, margin: const EdgeInsets.only(right: 8.0), decoration: BoxDecoration( color: theme.dividerColor, diff --git a/lib/shared/link_preview_card.dart b/lib/shared/link_preview_card.dart index 82991c4cb..675d593f7 100644 --- a/lib/shared/link_preview_card.dart +++ b/lib/shared/link_preview_card.dart @@ -80,7 +80,7 @@ class LinkPreviewCard extends StatelessWidget { child: ImagePreview( read: read, url: mediaURL ?? originURL!, - height: showFullHeightImages ? mediaHeight : 150, + height: showFullHeightImages ? mediaHeight : ViewMode.comfortable.height, width: mediaWidth ?? MediaQuery.of(context).size.width - (edgeToEdgeImages ? 0 : 24), isExpandable: false, ), @@ -88,13 +88,13 @@ class LinkPreviewCard extends StatelessWidget { : ImagePreview( read: read, url: mediaURL ?? originURL!, - height: showFullHeightImages ? mediaHeight : 150, + height: showFullHeightImages ? mediaHeight : ViewMode.comfortable.height, width: mediaWidth ?? MediaQuery.of(context).size.width - (edgeToEdgeImages ? 0 : 24), isExpandable: false, ) ] else if (scrapeMissingPreviews) SizedBox( - height: 150, + height: ViewMode.comfortable.height, // This is used for external links when Lemmy does not provide a preview thumbnail // and when the user has enabled external scraping. // This is only used in comfortable mode. @@ -161,22 +161,22 @@ class LinkPreviewCard extends StatelessWidget { child: ImagePreview( read: read, url: mediaURL!, - height: 75, - width: 75, + height: ViewMode.compact.height, + width: ViewMode.compact.height, isExpandable: false, ), ) : ImagePreview( read: read, url: mediaURL!, - height: 75, - width: 75, + height: ViewMode.compact.height, + width: ViewMode.compact.height, isExpandable: false, ) : scrapeMissingPreviews ? SizedBox( - height: 75, - width: 75, + height: ViewMode.compact.height, + width: ViewMode.compact.height, // This is used for external links when Lemmy does not provide a preview thumbnail // and when the user has enabled external scraping. // This is only used in compact mode. @@ -201,8 +201,8 @@ class LinkPreviewCard extends StatelessWidget { // This is used for link previews when no thumbnail comes from Lemmy // and the user has disabled scraping. This is only in compact mode. : Container( - height: 75, - width: 75, + height: ViewMode.compact.height, + width: ViewMode.compact.height, color: theme.cardColor.darken(5), child: Icon( hideNsfw ? null : Icons.language, diff --git a/lib/shared/media_view.dart b/lib/shared/media_view.dart index ceaa87632..e457fff8d 100644 --- a/lib/shared/media_view.dart +++ b/lib/shared/media_view.dart @@ -96,8 +96,8 @@ class _MediaViewState extends State with SingleTickerProviderStateMix color: theme.cardColor.darken(5), child: widget.postViewMedia.postView.post.body?.isNotEmpty == true ? SizedBox( - height: 75.0, - width: 75.0, + height: ViewMode.compact.height, + width: ViewMode.compact.height, child: Padding( padding: const EdgeInsets.all(10.0), child: Align( @@ -113,8 +113,8 @@ class _MediaViewState extends State with SingleTickerProviderStateMix ), ) : Container( - height: 75, - width: 75, + height: ViewMode.compact.height, + width: ViewMode.compact.height, color: theme.cardColor.darken(5), child: Icon( Icons.text_fields_rounded, @@ -163,8 +163,29 @@ class _MediaViewState extends State with SingleTickerProviderStateMix }, child: Container( clipBehavior: Clip.hardEdge, - decoration: BoxDecoration(borderRadius: BorderRadius.circular((widget.edgeToEdgeImages ? 0 : 12))), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular((widget.edgeToEdgeImages ? 0 : 12)), + color: theme.colorScheme.primary.withOpacity(0.2), + ), + constraints: BoxConstraints( + maxHeight: switch (widget.viewMode) { + ViewMode.compact => ViewMode.compact.height, + ViewMode.comfortable => widget.showFullHeightImages ? widget.postViewMedia.media.first.height ?? ViewMode.comfortable.height : ViewMode.comfortable.height, + }, + minHeight: switch (widget.viewMode) { + ViewMode.compact => ViewMode.compact.height, + ViewMode.comfortable => widget.showFullHeightImages ? widget.postViewMedia.media.first.height ?? ViewMode.comfortable.height : ViewMode.comfortable.height, + }, + maxWidth: switch (widget.viewMode) { + ViewMode.compact => ViewMode.compact.height, + ViewMode.comfortable => widget.edgeToEdgeImages ? double.infinity : MediaQuery.of(context).size.width, + }, + minWidth: switch (widget.viewMode) { + ViewMode.compact => ViewMode.compact.height, + ViewMode.comfortable => widget.edgeToEdgeImages ? double.infinity : MediaQuery.of(context).size.width, + }), child: Stack( + fit: StackFit.expand, alignment: Alignment.center, children: [ ImageFiltered( @@ -174,6 +195,8 @@ class _MediaViewState extends State with SingleTickerProviderStateMix ), if (blurNSFWPreviews) Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.warning_rounded, size: widget.viewMode != ViewMode.compact ? 55 : 30), if (widget.viewMode != ViewMode.compact) Text(l10n.nsfwWarning, textScaler: const TextScaler.linear(1.5)), @@ -218,8 +241,18 @@ class _MediaViewState extends State with SingleTickerProviderStateMix final theme = Theme.of(context); final state = context.read().state; - double? height = widget.viewMode == ViewMode.compact ? 75 : (widget.showFullHeightImages ? widget.postViewMedia.media.first.height : 150); - double width = widget.viewMode == ViewMode.compact ? 75 : MediaQuery.of(context).size.width - (widget.edgeToEdgeImages ? 0 : 24); + double? width; + double? height; + + switch (widget.viewMode) { + case ViewMode.compact: + width = null; // Setting this to null will use the image's width. This will allow the image to not be stretched or squished. + height = ViewMode.compact.height; + break; + case ViewMode.comfortable: + width = MediaQuery.of(context).size.width - (widget.edgeToEdgeImages ? 0 : 24); + height = widget.showFullHeightImages ? widget.postViewMedia.media.first.height : null; + } return ExtendedImage.network( color: widget.read == true ? const Color.fromRGBO(255, 255, 255, 0.5) : null, @@ -230,9 +263,8 @@ class _MediaViewState extends State with SingleTickerProviderStateMix fit: widget.viewMode == ViewMode.compact ? BoxFit.cover : BoxFit.fitWidth, cache: true, clearMemoryCacheWhenDispose: state.imageCachingMode == ImageCachingMode.relaxed, - cacheWidth: widget.viewMode == ViewMode.compact - ? (75 * View.of(context).devicePixelRatio.ceil()) - : ((MediaQuery.of(context).size.width - (widget.edgeToEdgeImages ? 0 : 24)) * View.of(context).devicePixelRatio.ceil()).toInt(), + cacheWidth: width != null ? (width * View.of(context).devicePixelRatio.ceil()).toInt() : null, + cacheHeight: height != null ? (height * View.of(context).devicePixelRatio.ceil()).toInt() : null, loadStateChanged: (ExtendedImageState state) { switch (state.extendedImageLoadState) { case LoadState.loading: diff --git a/lib/shared/preview_image.dart b/lib/shared/preview_image.dart index f6a549f30..61362b094 100644 --- a/lib/shared/preview_image.dart +++ b/lib/shared/preview_image.dart @@ -47,8 +47,8 @@ class _PreviewImageState extends State with SingleTickerProviderSt final ThunderState state = context.read().state; final useDarkTheme = state.themeType == 'dark'; - double? height = widget.viewMode == ViewMode.compact ? 75 : (widget.showFullHeightImages ? widget.height : 150); - double width = widget.viewMode == ViewMode.compact ? 75 : MediaQuery.of(context).size.width - 24; + double? height = widget.viewMode == ViewMode.compact ? ViewMode.compact.height : (widget.showFullHeightImages ? widget.height : ViewMode.comfortable.height); + double width = widget.viewMode == ViewMode.compact ? ViewMode.compact.height : MediaQuery.of(context).size.width - 24; return ExtendedImage.network( widget.mediaUrl, @@ -57,7 +57,9 @@ class _PreviewImageState extends State with SingleTickerProviderSt fit: widget.viewMode == ViewMode.compact ? BoxFit.cover : BoxFit.fitWidth, cache: true, clearMemoryCacheWhenDispose: state.imageCachingMode == ImageCachingMode.relaxed, - cacheWidth: widget.viewMode == ViewMode.compact ? (75 * View.of(context).devicePixelRatio.ceil()) : ((MediaQuery.of(context).size.width - 24) * View.of(context).devicePixelRatio.ceil()).toInt(), + cacheWidth: widget.viewMode == ViewMode.compact + ? (ViewMode.compact.height * View.of(context).devicePixelRatio.ceil()).toInt() + : ((MediaQuery.of(context).size.width - 24) * View.of(context).devicePixelRatio.ceil()).toInt(), loadStateChanged: (ExtendedImageState state) { switch (state.extendedImageLoadState) { case LoadState.loading: