From 618141f1f2845c5a4c33c73257d592175b174732 Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su <30667958+hjiangsu@users.noreply.github.com> Date: Thu, 28 Mar 2024 23:50:16 -0700 Subject: [PATCH 1/6] fix issue where full image height generates jank --- lib/post/utils/post.dart | 8 +++++++- lib/shared/media_view.dart | 10 ++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/post/utils/post.dart b/lib/post/utils/post.dart index fe5c5cf78..90dc74e27 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,15 +316,18 @@ 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); } catch (e) { debugPrint('$e: Falling back to default image size'); } + // debugPrint('${media.mediaUrl} image size: ${result.width}x${result.height}'); Size size = MediaExtension.getScaledMediaSize(width: result.width, height: result.height, offset: edgeToEdgeImages ? 0 : 24, tabletMode: tabletMode); media.width = size.width; media.height = size.height; + + // debugPrint('${media.mediaUrl} scaled image size: ${media.width}x${media.height}\n'); } mediaList.add(media); diff --git a/lib/shared/media_view.dart b/lib/shared/media_view.dart index ceaa87632..ad681187e 100644 --- a/lib/shared/media_view.dart +++ b/lib/shared/media_view.dart @@ -218,9 +218,12 @@ 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 height = widget.viewMode == ViewMode.compact ? 75 : (widget.showFullHeightImages ? widget.postViewMedia.media.first.height ?? 150 : 150); double width = widget.viewMode == ViewMode.compact ? 75 : MediaQuery.of(context).size.width - (widget.edgeToEdgeImages ? 0 : 24); + debugPrint(widget.postViewMedia.media.firstOrNull?.toString()); + debugPrint("${widget.postViewMedia.media.first.mediaUrl ?? widget.postViewMedia.media.first.originalUrl!} Height: $height, Width: $width\n\n"); + return ExtendedImage.network( color: widget.read == true ? const Color.fromRGBO(255, 255, 255, 0.5) : null, colorBlendMode: widget.read == true ? BlendMode.modulate : null, @@ -230,9 +233,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: widget.viewMode == ViewMode.compact ? (75 * View.of(context).devicePixelRatio.ceil()) : (width * View.of(context).devicePixelRatio.ceil()).toInt(), + cacheHeight: widget.viewMode == ViewMode.compact ? (75 * View.of(context).devicePixelRatio.ceil()) : (height * View.of(context).devicePixelRatio.ceil()).toInt(), loadStateChanged: (ExtendedImageState state) { switch (state.extendedImageLoadState) { case LoadState.loading: From 77483f30cb75d8b4c9454fad6ea653892b671fe6 Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su <30667958+hjiangsu@users.noreply.github.com> Date: Fri, 29 Mar 2024 10:19:16 -0700 Subject: [PATCH 2/6] fixed issue where images would be squished or stretched when not in full height image mode --- lib/core/enums/view_mode.dart | 10 +++++++++- lib/shared/media_view.dart | 36 +++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 9 deletions(-) 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/shared/media_view.dart b/lib/shared/media_view.dart index ad681187e..f6a1c3ef5 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, @@ -164,6 +164,16 @@ class _MediaViewState extends State with SingleTickerProviderStateMix child: Container( clipBehavior: Clip.hardEdge, decoration: BoxDecoration(borderRadius: BorderRadius.circular((widget.edgeToEdgeImages ? 0 : 12))), + 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, + }, + ), child: Stack( alignment: Alignment.center, children: [ @@ -218,8 +228,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 : 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 = ViewMode.compact.height; + height = null; // Setting this to null will use the image's height. This will allow the image to not be stretched or squished. + break; + case ViewMode.comfortable: + width = MediaQuery.of(context).size.width - (widget.edgeToEdgeImages ? 0 : 24); + height = widget.showFullHeightImages ? widget.postViewMedia.media.first.height : null; + } debugPrint(widget.postViewMedia.media.firstOrNull?.toString()); debugPrint("${widget.postViewMedia.media.first.mediaUrl ?? widget.postViewMedia.media.first.originalUrl!} Height: $height, Width: $width\n\n"); @@ -233,8 +253,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()) : (width * View.of(context).devicePixelRatio.ceil()).toInt(), - cacheHeight: widget.viewMode == ViewMode.compact ? (75 * View.of(context).devicePixelRatio.ceil()) : (height * View.of(context).devicePixelRatio.ceil()).toInt(), + cacheWidth: (width * View.of(context).devicePixelRatio.ceil()).toInt(), + cacheHeight: height != null ? (height * View.of(context).devicePixelRatio.ceil()).toInt() : null, loadStateChanged: (ExtendedImageState state) { switch (state.extendedImageLoadState) { case LoadState.loading: From 8f2338f0fa143b8b4f7b2cb77fb8da3ee2a715eb Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su <30667958+hjiangsu@users.noreply.github.com> Date: Fri, 29 Mar 2024 10:38:24 -0700 Subject: [PATCH 3/6] switched constants to use view mode heights --- .../pages/post_appearance_settings_page.dart | 9 ++++---- lib/shared/link_preview_card.dart | 22 +++++++++---------- lib/shared/preview_image.dart | 8 ++++--- 3 files changed, 21 insertions(+), 18 deletions(-) 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/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: From 64e403393fc05509e2ec79333aec7d4a62f2aa34 Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su <30667958+hjiangsu@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:17:37 -0700 Subject: [PATCH 4/6] added timeout for retrieving image dimensions, misc changes --- lib/post/utils/post.dart | 4 ++-- lib/shared/media_view.dart | 37 ++++++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/post/utils/post.dart b/lib/post/utils/post.dart index 90dc74e27..52ee97e30 100644 --- a/lib/post/utils/post.dart +++ b/lib/post/utils/post.dart @@ -316,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 ?? media.originalUrl); + 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'); } // debugPrint('${media.mediaUrl} image size: ${result.width}x${result.height}'); diff --git a/lib/shared/media_view.dart b/lib/shared/media_view.dart index f6a1c3ef5..b52883b12 100644 --- a/lib/shared/media_view.dart +++ b/lib/shared/media_view.dart @@ -163,18 +163,29 @@ class _MediaViewState extends State with SingleTickerProviderStateMix }, child: Container( clipBehavior: Clip.hardEdge, - decoration: BoxDecoration(borderRadius: BorderRadius.circular((widget.edgeToEdgeImages ? 0 : 12))), - 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, - }, + 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( @@ -233,8 +244,8 @@ class _MediaViewState extends State with SingleTickerProviderStateMix switch (widget.viewMode) { case ViewMode.compact: - width = ViewMode.compact.height; - height = null; // Setting this to null will use the image's height. This will allow the image to not be stretched or squished. + 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); @@ -253,7 +264,7 @@ class _MediaViewState extends State with SingleTickerProviderStateMix fit: widget.viewMode == ViewMode.compact ? BoxFit.cover : BoxFit.fitWidth, cache: true, clearMemoryCacheWhenDispose: state.imageCachingMode == ImageCachingMode.relaxed, - cacheWidth: (width * 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) { From 25b7f0ae0e091c0026b54cca67bc223496b2262c Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su <30667958+hjiangsu@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:34:07 -0700 Subject: [PATCH 5/6] remove extra debug prints --- lib/post/utils/post.dart | 3 --- lib/shared/media_view.dart | 3 --- 2 files changed, 6 deletions(-) diff --git a/lib/post/utils/post.dart b/lib/post/utils/post.dart index 52ee97e30..04c9d9c46 100644 --- a/lib/post/utils/post.dart +++ b/lib/post/utils/post.dart @@ -321,13 +321,10 @@ Future parsePostView(PostView postView, bool fetchImageDimensions debugPrint('${media.mediaUrl ?? media.originalUrl} - $e: Falling back to default image size'); } - // debugPrint('${media.mediaUrl} image size: ${result.width}x${result.height}'); Size size = MediaExtension.getScaledMediaSize(width: result.width, height: result.height, offset: edgeToEdgeImages ? 0 : 24, tabletMode: tabletMode); media.width = size.width; media.height = size.height; - - // debugPrint('${media.mediaUrl} scaled image size: ${media.width}x${media.height}\n'); } mediaList.add(media); diff --git a/lib/shared/media_view.dart b/lib/shared/media_view.dart index b52883b12..05c708521 100644 --- a/lib/shared/media_view.dart +++ b/lib/shared/media_view.dart @@ -252,9 +252,6 @@ class _MediaViewState extends State with SingleTickerProviderStateMix height = widget.showFullHeightImages ? widget.postViewMedia.media.first.height : null; } - debugPrint(widget.postViewMedia.media.firstOrNull?.toString()); - debugPrint("${widget.postViewMedia.media.first.mediaUrl ?? widget.postViewMedia.media.first.originalUrl!} Height: $height, Width: $width\n\n"); - return ExtendedImage.network( color: widget.read == true ? const Color.fromRGBO(255, 255, 255, 0.5) : null, colorBlendMode: widget.read == true ? BlendMode.modulate : null, From 897da8dbad821a1081e094381e071749e7f42a7f Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su <30667958+hjiangsu@users.noreply.github.com> Date: Sat, 30 Mar 2024 11:01:24 -0700 Subject: [PATCH 6/6] fixed positioning of nsfw warning --- lib/shared/media_view.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/shared/media_view.dart b/lib/shared/media_view.dart index 05c708521..e457fff8d 100644 --- a/lib/shared/media_view.dart +++ b/lib/shared/media_view.dart @@ -195,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)),