From 67706a6ae76ea6d71aa5dd2ca5c64a3eeaf9514d Mon Sep 17 00:00:00 2001 From: --global Date: Mon, 6 May 2024 15:22:55 +0700 Subject: [PATCH 1/5] TW-1650: remove unnecessary animation and Image.memory in web --- lib/pages/chat/events/image_bubble.dart | 141 +++++++++++-------- lib/pages/chat/events/image_builder_web.dart | 118 ++++++++++++++++ 2 files changed, 203 insertions(+), 56 deletions(-) create mode 100644 lib/pages/chat/events/image_builder_web.dart diff --git a/lib/pages/chat/events/image_bubble.dart b/lib/pages/chat/events/image_bubble.dart index 122291e036..a2e1212071 100644 --- a/lib/pages/chat/events/image_bubble.dart +++ b/lib/pages/chat/events/image_bubble.dart @@ -1,8 +1,10 @@ import 'dart:typed_data'; +import 'package:fluffychat/pages/chat/events/image_builder_web.dart'; import 'package:fluffychat/pages/chat/events/message_content_style.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; import 'package:flutter_blurhash/flutter_blurhash.dart'; import 'package:matrix/matrix.dart'; @@ -49,7 +51,89 @@ class ImageBubble extends StatelessWidget { static const animationSwitcherDuration = Duration(seconds: 1); - Widget _buildPlaceholder(BuildContext context) { + @override + Widget build(BuildContext context) { + final bubbleWidth = MessageContentStyle.imageBubbleWidth(width); + final bubbleHeight = MessageContentStyle.imageBubbleWidth(height); + return Container( + decoration: BoxDecoration( + borderRadius: rounded + ? MessageContentStyle.borderRadiusBubble + : BorderRadius.zero, + ), + constraints: maxSize + ? BoxConstraints( + maxWidth: bubbleWidth, + maxHeight: bubbleHeight, + ) + : null, + child: ClipRRect( + borderRadius: rounded + ? MessageContentStyle.borderRadiusBubble + : BorderRadius.zero, + child: Stack( + alignment: Alignment.center, + children: [ + SizedBox( + width: bubbleWidth, + height: bubbleHeight, + child: const BlurHash(hash: MessageContentStyle.defaultBlurHash), + ), + PlatformInfos.isWeb + ? ImageBuilderWeb( + event: event, + isThumbnail: thumbnailOnly, + width: width, + height: height, + onTapPreview: onTapPreview, + onTapSelectMode: onTapSelectMode, + fit: fit, + ) + : MxcImage( + event: event, + width: width, + height: height, + fit: fit, + animated: animated, + isThumbnail: thumbnailOnly, + placeholder: (context) => ImagePlaceholder( + event: event, + width: width, + height: height, + fit: fit, + ), + onTapPreview: onTapPreview, + onTapSelectMode: onTapSelectMode, + imageData: imageData, + isPreview: isPreview, + animationDuration: animationDuration, + cacheKey: thumbnailCacheKey, + cacheMap: thumbnailCacheMap, + noResize: noResizeThumbnail, + ), + ], + ), + ), + ); + } +} + +class ImagePlaceholder extends StatelessWidget { + const ImagePlaceholder({ + super.key, + required this.event, + required this.width, + required this.height, + required this.fit, + }); + + final Event event; + final double width; + final double height; + final BoxFit fit; + + @override + Widget build(BuildContext context) { if (event.messageType == MessageTypes.Sticker) { return const Center( child: CircularProgressIndicator.adaptive(), @@ -81,59 +165,4 @@ class ImageBubble extends StatelessWidget { ), ); } - - @override - Widget build(BuildContext context) { - final bubbleWidth = MessageContentStyle.imageBubbleWidth(width); - final bubbleHeight = MessageContentStyle.imageBubbleWidth(height); - return AnimatedSwitcher( - duration: animationSwitcherDuration, - child: Container( - decoration: BoxDecoration( - borderRadius: rounded - ? MessageContentStyle.borderRadiusBubble - : BorderRadius.zero, - ), - constraints: maxSize - ? BoxConstraints( - maxWidth: bubbleWidth, - maxHeight: bubbleHeight, - ) - : null, - child: ClipRRect( - borderRadius: rounded - ? MessageContentStyle.borderRadiusBubble - : BorderRadius.zero, - child: Stack( - alignment: Alignment.center, - children: [ - SizedBox( - width: bubbleWidth, - height: bubbleHeight, - child: - const BlurHash(hash: MessageContentStyle.defaultBlurHash), - ), - MxcImage( - event: event, - width: width, - height: height, - fit: fit, - animated: animated, - isThumbnail: thumbnailOnly, - placeholder: _buildPlaceholder, - onTapPreview: onTapPreview, - onTapSelectMode: onTapSelectMode, - imageData: imageData, - isPreview: isPreview, - animationDuration: animationDuration, - cacheKey: thumbnailCacheKey, - cacheMap: thumbnailCacheMap, - noResize: noResizeThumbnail, - ), - ], - ), - ), - ), - ); - } } diff --git a/lib/pages/chat/events/image_builder_web.dart b/lib/pages/chat/events/image_builder_web.dart new file mode 100644 index 0000000000..eb83cb26b0 --- /dev/null +++ b/lib/pages/chat/events/image_builder_web.dart @@ -0,0 +1,118 @@ +import 'package:fluffychat/pages/chat/events/image_bubble.dart'; +import 'package:fluffychat/pages/chat/events/message_content_style.dart'; +import 'package:fluffychat/pages/image_viewer/image_viewer.dart'; +import 'package:fluffychat/presentation/enum/chat/media_viewer_popup_result_enum.dart'; +import 'package:fluffychat/utils/interactive_viewer_gallery.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/hero_page_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blurhash/flutter_blurhash.dart'; +import 'package:matrix/matrix.dart'; + +class ImageBuilderWeb extends StatelessWidget { + final Event event; + + final bool isThumbnail; + + final double width; + + final double height; + + final BoxFit fit; + + final VoidCallback? closeRightColumn; + + final void Function()? onTapPreview; + + final void Function()? onTapSelectMode; + + const ImageBuilderWeb({ + super.key, + required this.event, + this.isThumbnail = false, + this.width = 256, + this.height = 300, + this.fit = BoxFit.cover, + this.onTapSelectMode, + this.onTapPreview, + this.closeRightColumn, + }); + + @override + Widget build(BuildContext context) { + return Hero( + tag: event.eventId, + child: Material( + child: InkWell( + mouseCursor: SystemMouseCursors.click, + borderRadius: BorderRadius.circular(12.0), + onTap: onTapPreview != null || onTapSelectMode != null + ? () => _onTap(context) + : null, + child: Image.network( + event + .attachmentOrThumbnailMxcUrl(getThumbnail: isThumbnail)! + .getDownloadLink(event.room.client) + .toString(), + frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { + if (wasSynchronouslyLoaded) { + return child; + } + return AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: frame != null + ? child + : ImagePlaceholder( + event: event, + width: width, + height: height, + fit: fit, + ), + ); + }, + fit: fit, + width: width, + height: height, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return SizedBox( + width: width, + height: height, + child: + const BlurHash(hash: MessageContentStyle.defaultBlurHash), + ); + }, + ), + ), + ), + ); + } + + void _onTap(BuildContext context) async { + if (onTapPreview != null) { + onTapPreview!(); + final result = + await Navigator.of(context, rootNavigator: PlatformInfos.isWeb).push( + HeroPageRoute( + builder: (context) { + return InteractiveViewerGallery( + itemBuilder: ImageViewer( + event: event, + ), + ); + }, + ), + ); + if (result == MediaViewerPopupResultEnum.closeRightColumnFlag) { + closeRightColumn?.call(); + } + } else if (onTapSelectMode != null) { + onTapSelectMode!(); + return; + } else { + return; + } + } +} From 6da6a67ac028729cdd4ae30f80daa31316422af0 Mon Sep 17 00:00:00 2001 From: --global Date: Mon, 6 May 2024 17:05:33 +0700 Subject: [PATCH 2/5] TW-1650: use Image.memory for encrypted image --- lib/pages/chat/events/image_bubble.dart | 8 +- lib/pages/chat/events/image_builder_web.dart | 96 ++++++++++++------- .../event_extension.dart | 4 + 3 files changed, 69 insertions(+), 39 deletions(-) diff --git a/lib/pages/chat/events/image_bubble.dart b/lib/pages/chat/events/image_bubble.dart index a2e1212071..7a72c45415 100644 --- a/lib/pages/chat/events/image_bubble.dart +++ b/lib/pages/chat/events/image_bubble.dart @@ -1,5 +1,3 @@ -import 'dart:typed_data'; - import 'package:fluffychat/pages/chat/events/image_builder_web.dart'; import 'package:fluffychat/pages/chat/events/message_content_style.dart'; import 'package:fluffychat/config/app_config.dart'; @@ -22,7 +20,6 @@ class ImageBubble extends StatelessWidget { final void Function()? onTapPreview; final void Function()? onTapSelectMode; final bool isPreview; - final Uint8List? imageData; final Duration animationDuration; final String? thumbnailCacheKey; @@ -31,7 +28,6 @@ class ImageBubble extends StatelessWidget { const ImageBubble( this.event, { - this.imageData, this.maxSize = true, this.fit = BoxFit.cover, this.thumbnailOnly = true, @@ -79,7 +75,8 @@ class ImageBubble extends StatelessWidget { height: bubbleHeight, child: const BlurHash(hash: MessageContentStyle.defaultBlurHash), ), - PlatformInfos.isWeb + PlatformInfos.isWeb && + event.isEventEncrypted(isThumbnail: thumbnailOnly) ? ImageBuilderWeb( event: event, isThumbnail: thumbnailOnly, @@ -104,7 +101,6 @@ class ImageBubble extends StatelessWidget { ), onTapPreview: onTapPreview, onTapSelectMode: onTapSelectMode, - imageData: imageData, isPreview: isPreview, animationDuration: animationDuration, cacheKey: thumbnailCacheKey, diff --git a/lib/pages/chat/events/image_builder_web.dart b/lib/pages/chat/events/image_builder_web.dart index eb83cb26b0..1a8a587c20 100644 --- a/lib/pages/chat/events/image_builder_web.dart +++ b/lib/pages/chat/events/image_builder_web.dart @@ -3,6 +3,7 @@ import 'package:fluffychat/pages/chat/events/message_content_style.dart'; import 'package:fluffychat/pages/image_viewer/image_viewer.dart'; import 'package:fluffychat/presentation/enum/chat/media_viewer_popup_result_enum.dart'; import 'package:fluffychat/utils/interactive_viewer_gallery.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/hero_page_route.dart'; import 'package:flutter/material.dart'; @@ -49,41 +50,12 @@ class ImageBuilderWeb extends StatelessWidget { onTap: onTapPreview != null || onTapSelectMode != null ? () => _onTap(context) : null, - child: Image.network( - event - .attachmentOrThumbnailMxcUrl(getThumbnail: isThumbnail)! - .getDownloadLink(event.room.client) - .toString(), - frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { - if (wasSynchronouslyLoaded) { - return child; - } - return AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: frame != null - ? child - : ImagePlaceholder( - event: event, - width: width, - height: height, - fit: fit, - ), - ); - }, - fit: fit, + child: UnencryptedImageWidget( + event: event, + isThumbnail: isThumbnail, width: width, height: height, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) { - return child; - } - return SizedBox( - width: width, - height: height, - child: - const BlurHash(hash: MessageContentStyle.defaultBlurHash), - ); - }, + fit: fit, ), ), ), @@ -116,3 +88,61 @@ class ImageBuilderWeb extends StatelessWidget { } } } + +class UnencryptedImageWidget extends StatelessWidget { + const UnencryptedImageWidget({ + super.key, + required this.event, + required this.isThumbnail, + required this.width, + required this.height, + required this.fit, + }); + + final Event event; + final bool isThumbnail; + final double width; + final double height; + final BoxFit fit; + + @override + Widget build(BuildContext context) { + return Image.network( + event + .attachmentOrThumbnailMxcUrl(getThumbnail: isThumbnail)! + .getDownloadLink(event.room.client) + .toString(), + frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { + if (wasSynchronouslyLoaded) { + return child; + } + return AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: frame != null + ? child + : ImagePlaceholder( + event: event, + width: width, + height: height, + fit: fit, + ), + ); + }, + fit: fit, + width: width, + height: height, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return SizedBox( + width: width, + height: height, + child: BlurHash( + hash: event.blurHash ?? MessageContentStyle.defaultBlurHash, + ), + ); + }, + ); + } +} diff --git a/lib/utils/matrix_sdk_extensions/event_extension.dart b/lib/utils/matrix_sdk_extensions/event_extension.dart index 9c78247ab3..2102cd33a1 100644 --- a/lib/utils/matrix_sdk_extensions/event_extension.dart +++ b/lib/utils/matrix_sdk_extensions/event_extension.dart @@ -224,4 +224,8 @@ extension LocalizedBody on Event { hideReply: true, ); } + + bool isEventEncrypted({bool isThumbnail = true}) { + return isThumbnail ? isThumbnailEncrypted : isAttachmentEncrypted; + } } From c0e6e7f37ad4f40b6d8edd88d5a8c8f19e6e4931 Mon Sep 17 00:00:00 2001 From: --global Date: Tue, 7 May 2024 18:29:45 +0700 Subject: [PATCH 3/5] TW-1650: fix memory overuse in avatar --- lib/pages/chat/events/image_bubble.dart | 46 ++++++++++---------- lib/pages/chat/events/image_builder_web.dart | 13 ++++-- lib/widgets/avatar/avatar.dart | 29 +++++++----- lib/widgets/mxc_image.dart | 22 +++++++--- 4 files changed, 67 insertions(+), 43 deletions(-) diff --git a/lib/pages/chat/events/image_bubble.dart b/lib/pages/chat/events/image_bubble.dart index 7a72c45415..fb1e21b9e3 100644 --- a/lib/pages/chat/events/image_bubble.dart +++ b/lib/pages/chat/events/image_bubble.dart @@ -67,26 +67,28 @@ class ImageBubble extends StatelessWidget { borderRadius: rounded ? MessageContentStyle.borderRadiusBubble : BorderRadius.zero, - child: Stack( - alignment: Alignment.center, - children: [ - SizedBox( - width: bubbleWidth, - height: bubbleHeight, - child: const BlurHash(hash: MessageContentStyle.defaultBlurHash), - ), - PlatformInfos.isWeb && - event.isEventEncrypted(isThumbnail: thumbnailOnly) - ? ImageBuilderWeb( - event: event, - isThumbnail: thumbnailOnly, - width: width, - height: height, - onTapPreview: onTapPreview, - onTapSelectMode: onTapSelectMode, - fit: fit, - ) - : MxcImage( + child: (PlatformInfos.isWeb && + !event.isEventEncrypted(isThumbnail: thumbnailOnly)) + ? UnencryptedImageBuilderWeb( + event: event, + isThumbnail: thumbnailOnly, + width: width, + height: height, + onTapPreview: onTapPreview, + onTapSelectMode: onTapSelectMode, + fit: fit, + ) + : Stack( + alignment: Alignment.center, + children: [ + SizedBox( + width: bubbleWidth, + height: bubbleHeight, + child: const BlurHash( + hash: MessageContentStyle.defaultBlurHash, + ), + ), + MxcImage( event: event, width: width, height: height, @@ -107,8 +109,8 @@ class ImageBubble extends StatelessWidget { cacheMap: thumbnailCacheMap, noResize: noResizeThumbnail, ), - ], - ), + ], + ), ), ); } diff --git a/lib/pages/chat/events/image_builder_web.dart b/lib/pages/chat/events/image_builder_web.dart index 1a8a587c20..882ac0058a 100644 --- a/lib/pages/chat/events/image_builder_web.dart +++ b/lib/pages/chat/events/image_builder_web.dart @@ -10,7 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_blurhash/flutter_blurhash.dart'; import 'package:matrix/matrix.dart'; -class ImageBuilderWeb extends StatelessWidget { +class UnencryptedImageBuilderWeb extends StatelessWidget { final Event event; final bool isThumbnail; @@ -27,10 +27,10 @@ class ImageBuilderWeb extends StatelessWidget { final void Function()? onTapSelectMode; - const ImageBuilderWeb({ + const UnencryptedImageBuilderWeb({ super.key, required this.event, - this.isThumbnail = false, + this.isThumbnail = true, this.width = 256, this.height = 300, this.fit = BoxFit.cover, @@ -131,6 +131,13 @@ class UnencryptedImageWidget extends StatelessWidget { fit: fit, width: width, height: height, + cacheWidth: (width * MediaQuery.of(context).devicePixelRatio).toInt(), + filterQuality: FilterQuality.none, + errorBuilder: (context, error, stackTrace) { + return BlurHash( + hash: event.blurHash ?? MessageContentStyle.defaultBlurHash, + ); + }, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) { return child; diff --git a/lib/widgets/avatar/avatar.dart b/lib/widgets/avatar/avatar.dart index f3e0b677bd..8c6e2dd46a 100644 --- a/lib/widgets/avatar/avatar.dart +++ b/lib/widgets/avatar/avatar.dart @@ -32,7 +32,6 @@ class Avatar extends StatelessWidget { @override Widget build(BuildContext context) { - final fallbackLetters = name?.getShortcutNameForAvatar() ?? '@'; return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(size / 2), @@ -44,23 +43,29 @@ class Avatar extends StatelessWidget { fit: BoxFit.cover, width: size, height: size, + cacheWidth: (size * MediaQuery.of(context).devicePixelRatio).toInt(), cacheKey: mxContent.toString(), - placeholder: (context) => RoundAvatar( - size: size, - text: fallbackLetters, - boxShadows: boxShadows, - textStyle: TextStyle( - fontSize: fontSize, - color: textColor ?? AvatarStyle.defaultTextColor(_havePicture), - fontFamily: AvatarStyle.fontFamily, - fontWeight: AvatarStyle.fontWeight, - ), - ), + placeholder: (context) => _fallbackAvatar(), ), ), ); } + Widget _fallbackAvatar() { + final fallbackLetters = name?.getShortcutNameForAvatar() ?? '@'; + return RoundAvatar( + size: size, + text: fallbackLetters, + boxShadows: boxShadows, + textStyle: TextStyle( + fontSize: fontSize, + color: textColor ?? AvatarStyle.defaultTextColor(_havePicture), + fontFamily: AvatarStyle.fontFamily, + fontWeight: AvatarStyle.fontWeight, + ), + ); + } + bool get _havePicture { return mxContent == null || mxContent.toString().isEmpty || diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index c0ede95b7a..aae43264fc 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -43,6 +43,8 @@ class MxcImage extends StatefulWidget { final VoidCallback? closeRightColumn; + final int? cacheWidth; + const MxcImage({ this.uri, this.event, @@ -65,6 +67,7 @@ class MxcImage extends StatefulWidget { this.cacheMap, this.noResize = false, this.closeRightColumn, + this.cacheWidth, Key? key, }) : super(key: key); @@ -303,6 +306,7 @@ class _MxcImageState extends State { height: widget.height, fit: widget.fit, needResize: needResize, + cacheWidth: widget.cacheWidth, imageErrorWidgetBuilder: (context, __, ___) { _isCached = false; _imageData = null; @@ -322,6 +326,7 @@ class _ImageWidget extends StatelessWidget { final bool needResize; final BoxFit? fit; final ImageErrorWidgetBuilder imageErrorWidgetBuilder; + final int? cacheWidth; const _ImageWidget({ this.filePath, @@ -331,6 +336,7 @@ class _ImageWidget extends StatelessWidget { required this.needResize, this.fit, required this.imageErrorWidgetBuilder, + this.cacheWidth, }); @override @@ -341,9 +347,11 @@ class _ImageWidget extends StatelessWidget { File(filePath!), width: width, height: height, - cacheWidth: (width != null && needResize) - ? (width! * devicePixelRatio).toInt() - : null, + cacheWidth: cacheWidth != null + ? cacheWidth! + : (width != null && needResize) + ? (width! * devicePixelRatio).toInt() + : null, cacheHeight: (height != null && needResize) ? (height! * devicePixelRatio).toInt() : null, @@ -356,9 +364,11 @@ class _ImageWidget extends StatelessWidget { data!, width: width, height: height, - cacheWidth: (width != null && needResize) - ? (width! * devicePixelRatio).toInt() - : null, + cacheWidth: cacheWidth != null + ? cacheWidth! + : (width != null && needResize) + ? (width! * devicePixelRatio).toInt() + : null, cacheHeight: (height != null && needResize) ? (height! * devicePixelRatio).toInt() : null, From e99132b877ef3c2034bb752537995d11b66fc81e Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 8 May 2024 09:57:41 +0700 Subject: [PATCH 4/5] TW-1650: reorganize image_builder folder --- lib/pages/chat/events/event_video_player.dart | 2 +- lib/pages/chat/events/html_message.dart | 2 +- .../{ => images_builder}/image_bubble.dart | 58 ++---------- .../image_builder_web.dart | 78 +--------------- .../images_builder/image_placeholder.dart | 55 +++++++++++ .../message_content_image_builder.dart | 91 +++++++++++++++++++ .../sending_image_info_widget.dart | 44 ++++++--- .../unencrypted_image_builder_web.dart | 72 +++++++++++++++ lib/pages/chat/events/message_content.dart | 79 +--------------- .../chat/events/message_content_style.dart | 3 + lib/pages/chat/events/sticker.dart | 2 +- .../media_page_view_widget.dart | 3 + lib/pages/chat/sticker_picker_dialog.dart | 2 +- lib/pages/image_viewer/image_viewer.dart | 6 ++ lib/pages/image_viewer/image_viewer_view.dart | 24 ++++- .../extension/build_context_extension.dart | 14 ++- lib/widgets/avatar/avatar.dart | 2 +- lib/widgets/mxc_image.dart | 33 ++++--- 18 files changed, 329 insertions(+), 241 deletions(-) rename lib/pages/chat/events/{ => images_builder}/image_bubble.dart (74%) rename lib/pages/chat/events/{ => images_builder}/image_builder_web.dart (50%) create mode 100644 lib/pages/chat/events/images_builder/image_placeholder.dart create mode 100644 lib/pages/chat/events/images_builder/message_content_image_builder.dart rename lib/pages/chat/events/{ => images_builder}/sending_image_info_widget.dart (69%) create mode 100644 lib/pages/chat/events/images_builder/unencrypted_image_builder_web.dart diff --git a/lib/pages/chat/events/event_video_player.dart b/lib/pages/chat/events/event_video_player.dart index f87faf3dde..05a115fefe 100644 --- a/lib/pages/chat/events/event_video_player.dart +++ b/lib/pages/chat/events/event_video_player.dart @@ -9,7 +9,7 @@ import 'package:flutter_blurhash/flutter_blurhash.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/pages/chat/events/image_bubble.dart'; +import 'package:fluffychat/pages/chat/events/images_builder/image_bubble.dart'; import 'package:linagora_design_flutter/extensions/duration_extension.dart'; typedef DownloadVideoEventCallback = Future Function(Event event); diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index d83b98b89d..d72ff4120f 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -84,7 +84,7 @@ class HtmlMessage extends StatelessWidget { double? height, { bool? animated = false, }) { - final ratio = MediaQuery.of(context).devicePixelRatio; + final ratio = MediaQuery.devicePixelRatioOf(context); return Uri.parse(mxc) .getThumbnail( matrix.client, diff --git a/lib/pages/chat/events/image_bubble.dart b/lib/pages/chat/events/images_builder/image_bubble.dart similarity index 74% rename from lib/pages/chat/events/image_bubble.dart rename to lib/pages/chat/events/images_builder/image_bubble.dart index fb1e21b9e3..d3b34a31ce 100644 --- a/lib/pages/chat/events/image_bubble.dart +++ b/lib/pages/chat/events/images_builder/image_bubble.dart @@ -1,6 +1,8 @@ -import 'package:fluffychat/pages/chat/events/image_builder_web.dart'; +import 'dart:typed_data'; + +import 'package:fluffychat/pages/chat/events/images_builder/image_builder_web.dart'; +import 'package:fluffychat/pages/chat/events/images_builder/image_placeholder.dart'; import 'package:fluffychat/pages/chat/events/message_content_style.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; @@ -21,6 +23,7 @@ class ImageBubble extends StatelessWidget { final void Function()? onTapSelectMode; final bool isPreview; final Duration animationDuration; + final Uint8List? imageData; final String? thumbnailCacheKey; final Map? thumbnailCacheMap; @@ -42,6 +45,7 @@ class ImageBubble extends StatelessWidget { this.thumbnailCacheMap, this.noResizeThumbnail = false, this.isPreview = true, + this.imageData, Key? key, }) : super(key: key); @@ -108,6 +112,7 @@ class ImageBubble extends StatelessWidget { cacheKey: thumbnailCacheKey, cacheMap: thumbnailCacheMap, noResize: noResizeThumbnail, + imageData: imageData, ), ], ), @@ -115,52 +120,3 @@ class ImageBubble extends StatelessWidget { ); } } - -class ImagePlaceholder extends StatelessWidget { - const ImagePlaceholder({ - super.key, - required this.event, - required this.width, - required this.height, - required this.fit, - }); - - final Event event; - final double width; - final double height; - final BoxFit fit; - - @override - Widget build(BuildContext context) { - if (event.messageType == MessageTypes.Sticker) { - return const Center( - child: CircularProgressIndicator.adaptive(), - ); - } - final String blurHashString = - event.blurHash ?? AppConfig.defaultImageBlurHash; - final ratio = event.infoMap['w'] is int && event.infoMap['h'] is int - ? event.infoMap['w'] / event.infoMap['h'] - : 1.0; - var width = 32; - var height = 32; - if (ratio > 1.0) { - height = (width / ratio).round(); - } else { - width = (height * ratio).round(); - } - return ClipRRect( - borderRadius: BorderRadius.zero, - child: SizedBox( - width: this.width, - height: this.height, - child: BlurHash( - hash: blurHashString, - decodingWidth: width, - decodingHeight: height, - imageFit: fit, - ), - ), - ); - } -} diff --git a/lib/pages/chat/events/image_builder_web.dart b/lib/pages/chat/events/images_builder/image_builder_web.dart similarity index 50% rename from lib/pages/chat/events/image_builder_web.dart rename to lib/pages/chat/events/images_builder/image_builder_web.dart index 882ac0058a..205667fc32 100644 --- a/lib/pages/chat/events/image_builder_web.dart +++ b/lib/pages/chat/events/images_builder/image_builder_web.dart @@ -1,13 +1,11 @@ -import 'package:fluffychat/pages/chat/events/image_bubble.dart'; +import 'package:fluffychat/pages/chat/events/images_builder/unencrypted_image_builder_web.dart'; import 'package:fluffychat/pages/chat/events/message_content_style.dart'; import 'package:fluffychat/pages/image_viewer/image_viewer.dart'; import 'package:fluffychat/presentation/enum/chat/media_viewer_popup_result_enum.dart'; import 'package:fluffychat/utils/interactive_viewer_gallery.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/hero_page_route.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_blurhash/flutter_blurhash.dart'; import 'package:matrix/matrix.dart'; class UnencryptedImageBuilderWeb extends StatelessWidget { @@ -31,8 +29,8 @@ class UnencryptedImageBuilderWeb extends StatelessWidget { super.key, required this.event, this.isThumbnail = true, - this.width = 256, - this.height = 300, + this.width = MessageContentStyle.imageBubbleWidthForMobileAndTablet, + this.height = MessageContentStyle.imageBubbleHeightForMobileAndTable, this.fit = BoxFit.cover, this.onTapSelectMode, this.onTapPreview, @@ -46,7 +44,7 @@ class UnencryptedImageBuilderWeb extends StatelessWidget { child: Material( child: InkWell( mouseCursor: SystemMouseCursors.click, - borderRadius: BorderRadius.circular(12.0), + borderRadius: MessageContentStyle.borderRadiusBubble, onTap: onTapPreview != null || onTapSelectMode != null ? () => _onTap(context) : null, @@ -82,74 +80,6 @@ class UnencryptedImageBuilderWeb extends StatelessWidget { } } else if (onTapSelectMode != null) { onTapSelectMode!(); - return; - } else { - return; } } } - -class UnencryptedImageWidget extends StatelessWidget { - const UnencryptedImageWidget({ - super.key, - required this.event, - required this.isThumbnail, - required this.width, - required this.height, - required this.fit, - }); - - final Event event; - final bool isThumbnail; - final double width; - final double height; - final BoxFit fit; - - @override - Widget build(BuildContext context) { - return Image.network( - event - .attachmentOrThumbnailMxcUrl(getThumbnail: isThumbnail)! - .getDownloadLink(event.room.client) - .toString(), - frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { - if (wasSynchronouslyLoaded) { - return child; - } - return AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: frame != null - ? child - : ImagePlaceholder( - event: event, - width: width, - height: height, - fit: fit, - ), - ); - }, - fit: fit, - width: width, - height: height, - cacheWidth: (width * MediaQuery.of(context).devicePixelRatio).toInt(), - filterQuality: FilterQuality.none, - errorBuilder: (context, error, stackTrace) { - return BlurHash( - hash: event.blurHash ?? MessageContentStyle.defaultBlurHash, - ); - }, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) { - return child; - } - return SizedBox( - width: width, - height: height, - child: BlurHash( - hash: event.blurHash ?? MessageContentStyle.defaultBlurHash, - ), - ); - }, - ); - } -} diff --git a/lib/pages/chat/events/images_builder/image_placeholder.dart b/lib/pages/chat/events/images_builder/image_placeholder.dart new file mode 100644 index 0000000000..e3ff53528e --- /dev/null +++ b/lib/pages/chat/events/images_builder/image_placeholder.dart @@ -0,0 +1,55 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pages/chat/events/message_content_style.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blurhash/flutter_blurhash.dart'; +import 'package:matrix/matrix.dart'; + +class ImagePlaceholder extends StatelessWidget { + const ImagePlaceholder({ + super.key, + required this.event, + required this.width, + required this.height, + required this.fit, + }); + + final Event event; + final double width; + final double height; + final BoxFit fit; + + @override + Widget build(BuildContext context) { + if (event.messageType == MessageTypes.Sticker) { + return const Center( + child: CircularProgressIndicator.adaptive(), + ); + } + final String blurHashString = + event.blurHash ?? AppConfig.defaultImageBlurHash; + final ratio = event.infoMap['w'] is int && event.infoMap['h'] is int + ? event.infoMap['w'] / event.infoMap['h'] + : 1.0; + var width = MessageContentStyle.blurhashSize; + var height = MessageContentStyle.blurhashSize; + if (ratio > 1.0) { + height = (width / ratio).round(); + } else { + width = (height * ratio).round(); + } + return ClipRRect( + borderRadius: BorderRadius.zero, + child: SizedBox( + width: this.width, + height: this.height, + child: BlurHash( + hash: blurHashString, + decodingWidth: width, + decodingHeight: height, + imageFit: fit, + ), + ), + ); + } +} diff --git a/lib/pages/chat/events/images_builder/message_content_image_builder.dart b/lib/pages/chat/events/images_builder/message_content_image_builder.dart new file mode 100644 index 0000000000..1599d3941b --- /dev/null +++ b/lib/pages/chat/events/images_builder/message_content_image_builder.dart @@ -0,0 +1,91 @@ +import 'package:fluffychat/pages/chat/events/images_builder/image_bubble.dart'; +import 'package:fluffychat/pages/chat/events/message_content_style.dart'; +import 'package:fluffychat/pages/chat/events/images_builder/sending_image_info_widget.dart'; +import 'package:fluffychat/presentation/model/file/display_image_info.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; +import 'package:fluffychat/utils/extension/image_size_extension.dart'; + +class MessageImageBuilder extends StatelessWidget { + final Event event; + + final void Function()? onTapPreview; + + final void Function()? onTapSelectMode; + + const MessageImageBuilder({ + super.key, + required this.event, + this.onTapPreview, + this.onTapSelectMode, + }); + + @override + Widget build(BuildContext context) { + final matrixFile = event.getMatrixFile(); + + DisplayImageInfo? displayImageInfo = + event.getOriginalResolution()?.getDisplayImageInfo(context); + + if (isSendingImageInMobile(matrixFile)) { + final file = matrixFile as MatrixImageFile; + displayImageInfo = Size( + file.width?.toDouble() ?? MessageContentStyle.imageWidth(context), + file.height?.toDouble() ?? MessageContentStyle.imageHeight(context), + ).getDisplayImageInfo(context); + return SendingImageInfoWidget( + key: ValueKey(event.eventId), + matrixFile: file, + event: event, + onTapPreview: onTapPreview, + displayImageInfo: displayImageInfo, + ); + } + displayImageInfo ??= DisplayImageInfo( + size: Size( + MessageContentStyle.imageWidth(context), + MessageContentStyle.imageHeight(context), + ), + hasBlur: true, + ); + if (isSendingImageInWeb(matrixFile)) { + final file = matrixFile as MatrixImageFile; + displayImageInfo = Size( + file.width?.toDouble() ?? MessageContentStyle.imageWidth(context), + file.height?.toDouble() ?? MessageContentStyle.imageHeight(context), + ).getDisplayImageInfo(context); + return SendingImageInfoWidget( + key: ValueKey(event.eventId), + matrixFile: file, + event: event, + onTapPreview: onTapPreview, + displayImageInfo: displayImageInfo, + ); + } + return ImageBubble( + event, + width: displayImageInfo.size.width, + height: displayImageInfo.size.height, + fit: BoxFit.cover, + onTapSelectMode: onTapSelectMode, + onTapPreview: onTapPreview, + animated: true, + thumbnailOnly: true, + ); + } + + bool isSendingImageInWeb(MatrixFile? matrixFile) { + return matrixFile != null && + matrixFile.bytes != null && + matrixFile is MatrixImageFile; + } + + bool isSendingImageInMobile(MatrixFile? matrixFile) { + return matrixFile != null && + matrixFile.filePath != null && + matrixFile is MatrixImageFile && + !PlatformInfos.isWeb; + } +} diff --git a/lib/pages/chat/events/sending_image_info_widget.dart b/lib/pages/chat/events/images_builder/sending_image_info_widget.dart similarity index 69% rename from lib/pages/chat/events/sending_image_info_widget.dart rename to lib/pages/chat/events/images_builder/sending_image_info_widget.dart index 1350b3d459..8ab17bc500 100644 --- a/lib/pages/chat/events/sending_image_info_widget.dart +++ b/lib/pages/chat/events/images_builder/sending_image_info_widget.dart @@ -3,8 +3,10 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/events/message_content_style.dart'; import 'package:fluffychat/pages/image_viewer/image_viewer.dart'; import 'package:fluffychat/presentation/model/file/display_image_info.dart'; +import 'package:fluffychat/utils/extension/build_context_extension.dart'; import 'package:fluffychat/utils/interactive_viewer_gallery.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/hero_page_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_blurhash/flutter_blurhash.dart'; @@ -32,7 +34,7 @@ class SendingImageInfoWidget extends StatelessWidget { void _onTap(BuildContext context) async { if (onTapPreview != null) { - Navigator.of(context).push( + Navigator.of(context, rootNavigator: PlatformInfos.isWeb).push( HeroPageRoute( builder: (context) { return InteractiveViewerGallery( @@ -53,7 +55,6 @@ class SendingImageInfoWidget extends StatelessWidget { event.status == EventStatus.synced) { sendingFileProgressNotifier.value = 1; } - final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; return Hero( tag: event.eventId, @@ -87,7 +88,8 @@ class SendingImageInfoWidget extends StatelessWidget { child: Stack( alignment: Alignment.center, children: [ - if (displayImageInfo.hasBlur) + if (matrixFile.bytes?.isNotEmpty != true || + matrixFile.filePath == null) SizedBox( width: MessageContentStyle.imageBubbleWidth( displayImageInfo.size.width, @@ -99,18 +101,30 @@ class SendingImageInfoWidget extends StatelessWidget { hash: event.blurHash ?? AppConfig.defaultImageBlurHash, ), ), - Image.file( - File(matrixFile.filePath!), - width: displayImageInfo.size.width, - height: displayImageInfo.size.height, - cacheHeight: - (displayImageInfo.size.height * devicePixelRatio) - .toInt(), - cacheWidth: (displayImageInfo.size.width * devicePixelRatio) - .toInt(), - fit: BoxFit.cover, - filterQuality: FilterQuality.low, - ), + if (!PlatformInfos.isWeb && matrixFile.filePath != null) + Image.file( + File(matrixFile.filePath!), + width: displayImageInfo.size.width, + height: displayImageInfo.size.height, + cacheHeight: + context.getCacheSize(displayImageInfo.size.height), + cacheWidth: + context.getCacheSize(displayImageInfo.size.width), + fit: BoxFit.cover, + filterQuality: FilterQuality.low, + ), + if (matrixFile.bytes?.isNotEmpty == true) + Image.memory( + matrixFile.bytes!, + width: displayImageInfo.size.width, + height: displayImageInfo.size.height, + cacheHeight: + context.getCacheSize(displayImageInfo.size.height), + cacheWidth: + context.getCacheSize(displayImageInfo.size.width), + fit: BoxFit.cover, + filterQuality: FilterQuality.none, + ), ], ), ), diff --git a/lib/pages/chat/events/images_builder/unencrypted_image_builder_web.dart b/lib/pages/chat/events/images_builder/unencrypted_image_builder_web.dart new file mode 100644 index 0000000000..df50019f02 --- /dev/null +++ b/lib/pages/chat/events/images_builder/unencrypted_image_builder_web.dart @@ -0,0 +1,72 @@ +import 'package:fluffychat/pages/chat/events/images_builder/image_placeholder.dart'; +import 'package:fluffychat/pages/chat/events/message_content_style.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blurhash/flutter_blurhash.dart'; +import 'package:matrix/matrix.dart'; + +class UnencryptedImageWidget extends StatelessWidget { + const UnencryptedImageWidget({ + super.key, + required this.event, + required this.isThumbnail, + required this.width, + required this.height, + required this.fit, + }); + + final Event event; + final bool isThumbnail; + final double width; + final double height; + final BoxFit fit; + + @override + Widget build(BuildContext context) { + return Image.network( + event + .attachmentOrThumbnailMxcUrl(getThumbnail: isThumbnail)! + .getDownloadLink(event.room.client) + .toString(), + frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { + if (wasSynchronouslyLoaded) { + return child; + } + return AnimatedSwitcher( + duration: MessageContentStyle.animationSwitcherDuration, + child: frame != null + ? child + : ImagePlaceholder( + event: event, + width: width, + height: height, + fit: fit, + ), + ); + }, + fit: fit, + width: width, + height: height, + cacheWidth: (width * MediaQuery.devicePixelRatioOf(context)).round(), + cacheHeight: (height * MediaQuery.devicePixelRatioOf(context)).round(), + filterQuality: FilterQuality.medium, + errorBuilder: (context, error, stackTrace) { + return BlurHash( + hash: event.blurHash ?? MessageContentStyle.defaultBlurHash, + ); + }, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return SizedBox( + width: width, + height: height, + child: BlurHash( + hash: event.blurHash ?? MessageContentStyle.defaultBlurHash, + ), + ); + }, + ); + } +} diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 9e367c14d8..5067a8bb55 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -1,13 +1,13 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/events/call_invite_content.dart'; import 'package:fluffychat/pages/chat/events/encrypted_content.dart'; +import 'package:fluffychat/pages/chat/events/images_builder/message_content_image_builder.dart'; import 'package:fluffychat/pages/chat/events/message_content_style.dart'; import 'package:fluffychat/pages/chat/events/message_download_content_web.dart'; import 'package:fluffychat/pages/chat/events/formatted_text_widget.dart'; import 'package:fluffychat/pages/chat/events/message_video_download_content.dart'; import 'package:fluffychat/pages/chat/events/message_video_download_content_web.dart'; import 'package:fluffychat/pages/chat/events/redacted_content.dart'; -import 'package:fluffychat/pages/chat/events/sending_image_info_widget.dart'; import 'package:fluffychat/pages/chat/events/sending_video_widget.dart'; import 'package:fluffychat/pages/chat/events/unknown_content.dart'; import 'package:fluffychat/presentation/model/file/display_image_info.dart'; @@ -26,7 +26,6 @@ import 'package:matrix/matrix.dart' hide Visibility; import 'audio_player.dart'; import 'cute_events.dart'; -import 'image_bubble.dart'; import 'map_bubble.dart'; import 'message_download_content.dart'; import 'sticker.dart'; @@ -62,7 +61,7 @@ class MessageContent extends StatelessWidget case EventTypes.Sticker: switch (event.messageType) { case MessageTypes.Image: - return _MessageImageBuilder( + return MessageImageBuilder( event: event, onTapPreview: onTapPreview, onTapSelectMode: onTapSelectMode, @@ -247,80 +246,6 @@ class MessageContent extends StatelessWidget } } -class _MessageImageBuilder extends StatelessWidget { - final Event event; - - final void Function()? onTapPreview; - - final void Function()? onTapSelectMode; - - const _MessageImageBuilder({ - required this.event, - this.onTapPreview, - this.onTapSelectMode, - }); - - @override - Widget build(BuildContext context) { - final matrixFile = event.getMatrixFile(); - - DisplayImageInfo? displayImageInfo = - event.getOriginalResolution()?.getDisplayImageInfo(context); - - if (isSendingImageInMobile(matrixFile)) { - final file = matrixFile as MatrixImageFile; - displayImageInfo = Size( - file.width?.toDouble() ?? MessageContentStyle.imageWidth(context), - file.height?.toDouble() ?? MessageContentStyle.imageHeight(context), - ).getDisplayImageInfo(context); - return SendingImageInfoWidget( - key: ValueKey(event.eventId), - matrixFile: file, - event: event, - onTapPreview: onTapPreview, - displayImageInfo: displayImageInfo, - ); - } - displayImageInfo ??= DisplayImageInfo( - size: Size( - MessageContentStyle.imageWidth(context), - MessageContentStyle.imageHeight(context), - ), - hasBlur: true, - ); - if (isSendingImageInWeb(matrixFile)) { - final file = matrixFile as MatrixImageFile; - displayImageInfo = Size( - file.width?.toDouble() ?? MessageContentStyle.imageWidth(context), - file.height?.toDouble() ?? MessageContentStyle.imageHeight(context), - ).getDisplayImageInfo(context); - } - return ImageBubble( - event, - width: displayImageInfo.size.width, - height: displayImageInfo.size.height, - fit: BoxFit.cover, - onTapSelectMode: onTapSelectMode, - onTapPreview: onTapPreview, - animated: true, - thumbnailOnly: true, - ); - } - - bool isSendingImageInWeb(MatrixFile? matrixFile) { - return matrixFile != null && - matrixFile.bytes != null && - matrixFile is MatrixImageFile; - } - - bool isSendingImageInMobile(MatrixFile? matrixFile) { - return matrixFile != null && - matrixFile.filePath != null && - matrixFile is MatrixImageFile && - !PlatformInfos.isWeb; - } -} - class _MessageVideoBuilder extends StatelessWidget { final Event event; diff --git a/lib/pages/chat/events/message_content_style.dart b/lib/pages/chat/events/message_content_style.dart index e12a46d20a..76dd06cf28 100644 --- a/lib/pages/chat/events/message_content_style.dart +++ b/lib/pages/chat/events/message_content_style.dart @@ -10,6 +10,7 @@ class MessageContentStyle { static const int maxLengthTextInline = 180; static const double appBarFontSize = 16.0; + static Duration animationSwitcherDuration = const Duration(milliseconds: 300); static double imageWidth(BuildContext context) { if (responsiveUtils.isDesktop(context)) { @@ -83,4 +84,6 @@ class MessageContentStyle { Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.secondary, ); + + static const blurhashSize = 32; } diff --git a/lib/pages/chat/events/sticker.dart b/lib/pages/chat/events/sticker.dart index fe9dd0f534..5d573ecd48 100644 --- a/lib/pages/chat/events/sticker.dart +++ b/lib/pages/chat/events/sticker.dart @@ -5,7 +5,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import '../../../config/app_config.dart'; -import 'image_bubble.dart'; +import 'images_builder/image_bubble.dart'; class Sticker extends StatefulWidget { final Event event; diff --git a/lib/pages/chat/send_file_dialog/media_page_view_widget.dart b/lib/pages/chat/send_file_dialog/media_page_view_widget.dart index 33e21eb7cb..8061be8a9b 100644 --- a/lib/pages/chat/send_file_dialog/media_page_view_widget.dart +++ b/lib/pages/chat/send_file_dialog/media_page_view_widget.dart @@ -61,6 +61,9 @@ class MediaPageViewWidget extends StatelessWidget { thumbnails[firstFile]?.bytes ?? Uint8List(0), fit: BoxFit.cover, + cacheWidth: (SendFileDialogStyle.imageSize * + MediaQuery.devicePixelRatioOf(context)) + .round(), ); }, ), diff --git a/lib/pages/chat/sticker_picker_dialog.dart b/lib/pages/chat/sticker_picker_dialog.dart index 73efe95da7..00246bd056 100644 --- a/lib/pages/chat/sticker_picker_dialog.dart +++ b/lib/pages/chat/sticker_picker_dialog.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; -import 'events/image_bubble.dart'; +import 'events/images_builder/image_bubble.dart'; class StickerPickerDialog extends StatefulWidget { final Room room; diff --git a/lib/pages/image_viewer/image_viewer.dart b/lib/pages/image_viewer/image_viewer.dart index eaa40b8a17..d654443575 100644 --- a/lib/pages/image_viewer/image_viewer.dart +++ b/lib/pages/image_viewer/image_viewer.dart @@ -16,12 +16,16 @@ class ImageViewer extends StatefulWidget { final Event? event; final Uint8List? imageData; final String? filePath; + final double? width; + final double? height; const ImageViewer({ Key? key, this.event, this.imageData, this.filePath, + this.width, + this.height, }) : super(key: key); @override @@ -135,5 +139,7 @@ class ImageViewerController extends State { this, imageData: widget.imageData, filePath: widget.filePath, + width: widget.width, + height: widget.height, ); } diff --git a/lib/pages/image_viewer/image_viewer_view.dart b/lib/pages/image_viewer/image_viewer_view.dart index 58742ba159..dcdac7e06d 100644 --- a/lib/pages/image_viewer/image_viewer_view.dart +++ b/lib/pages/image_viewer/image_viewer_view.dart @@ -15,12 +15,16 @@ class ImageViewerView extends StatelessWidget { final ImageViewerController controller; final Uint8List? imageData; final String? filePath; + final double? width; + final double? height; const ImageViewerView( this.controller, { Key? key, this.imageData, this.filePath, + this.width, + this.height, }) : super(key: key); @override @@ -36,6 +40,8 @@ class ImageViewerView extends StatelessWidget { imageWidget = _ImageWidget( event: controller.widget.event!, controller: controller, + width: width, + height: height, ); } else if (imageData != null) { imageWidget = Image.memory( @@ -97,7 +103,16 @@ class _ImageWidget extends StatelessWidget { final Event event; - const _ImageWidget({required this.event, required this.controller}); + final double? width; + + final double? height; + + const _ImageWidget({ + required this.event, + required this.controller, + this.width, + this.height, + }); @override Widget build(BuildContext context) { @@ -110,7 +125,12 @@ class _ImageWidget extends StatelessWidget { if (snapshot.data == null || snapshot.data!.bytes?.isEmpty != false) { return const CircularProgressIndicator(); } - return Image.memory(snapshot.data!.bytes!); + return Image.memory( + snapshot.data!.bytes!, + cacheWidth: width != null + ? (width! * MediaQuery.devicePixelRatioOf(context)).toInt() + : null, + ); }, ); } else { diff --git a/lib/utils/extension/build_context_extension.dart b/lib/utils/extension/build_context_extension.dart index 4e7aaec9a0..bf07c743c1 100644 --- a/lib/utils/extension/build_context_extension.dart +++ b/lib/utils/extension/build_context_extension.dart @@ -71,19 +71,19 @@ extension ContextExtensionss on BuildContext { TextTheme get textTheme => Theme.of(this).textTheme; /// similar to [MediaQuery.of(context).padding] - EdgeInsets get mediaQueryPadding => MediaQuery.of(this).padding; + EdgeInsets get mediaQueryPadding => MediaQuery.paddingOf(this); /// similar to [MediaQuery.of(context).padding] MediaQueryData get mediaQuery => MediaQuery.of(this); /// similar to [MediaQuery.of(context).viewPadding] - EdgeInsets get mediaQueryViewPadding => MediaQuery.of(this).viewPadding; + EdgeInsets get mediaQueryViewPadding => MediaQuery.viewPaddingOf(this); /// similar to [MediaQuery.of(context).viewInsets] - EdgeInsets get mediaQueryViewInsets => MediaQuery.of(this).viewInsets; + EdgeInsets get mediaQueryViewInsets => MediaQuery.viewInsetsOf(this); /// similar to [MediaQuery.of(context).orientation] - Orientation get orientation => MediaQuery.of(this).orientation; + Orientation get orientation => MediaQuery.orientationOf(this); /// check if device is on landscape mode bool get isLandscape => orientation == Orientation.landscape; @@ -92,7 +92,7 @@ extension ContextExtensionss on BuildContext { bool get isPortrait => orientation == Orientation.portrait; /// similar to [MediaQuery.of(this).devicePixelRatio] - double get devicePixelRatio => MediaQuery.of(this).devicePixelRatio; + double get devicePixelRatio => MediaQuery.devicePixelRatioOf(this); /// get the shortestSide from screen double get mediaQueryShortestSide => mediaQuerySize.shortestSide; @@ -192,4 +192,8 @@ extension ContextExtensionss on BuildContext { void goToRoomWithEvent(String roomId, String eventId) { go('/rooms/$roomId?event=$eventId'); } + + int getCacheSize(double size) { + return (MediaQuery.devicePixelRatioOf(this) * size).round(); + } } diff --git a/lib/widgets/avatar/avatar.dart b/lib/widgets/avatar/avatar.dart index 8c6e2dd46a..ce22db6d0a 100644 --- a/lib/widgets/avatar/avatar.dart +++ b/lib/widgets/avatar/avatar.dart @@ -43,7 +43,7 @@ class Avatar extends StatelessWidget { fit: BoxFit.cover, width: size, height: size, - cacheWidth: (size * MediaQuery.of(context).devicePixelRatio).toInt(), + cacheWidth: (size * MediaQuery.devicePixelRatioOf(context)).round(), cacheKey: mxContent.toString(), placeholder: (context) => _fallbackAvatar(), ), diff --git a/lib/widgets/mxc_image.dart b/lib/widgets/mxc_image.dart index aae43264fc..d52d329cbc 100644 --- a/lib/widgets/mxc_image.dart +++ b/lib/widgets/mxc_image.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:fluffychat/pages/image_viewer/image_viewer.dart'; import 'package:fluffychat/presentation/enum/chat/media_viewer_popup_result_enum.dart'; +import 'package:fluffychat/utils/extension/build_context_extension.dart'; import 'package:fluffychat/utils/interactive_viewer_gallery.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/download_file_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; @@ -45,6 +46,8 @@ class MxcImage extends StatefulWidget { final int? cacheWidth; + final int? cacheHeight; + const MxcImage({ this.uri, this.event, @@ -68,6 +71,7 @@ class MxcImage extends StatefulWidget { this.noResize = false, this.closeRightColumn, this.cacheWidth, + this.cacheHeight, Key? key, }) : super(key: key); @@ -120,11 +124,10 @@ class _MxcImageState extends State { final event = widget.event; if (uri != null) { - final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; final width = widget.width; - final realWidth = width == null ? null : width * devicePixelRatio; + final realWidth = width == null ? null : context.getCacheSize(width); final height = widget.height; - final realHeight = height == null ? null : height * devicePixelRatio; + final realHeight = height == null ? null : context.getCacheSize(height); final httpUri = widget.isThumbnail ? uri.getThumbnail( @@ -307,6 +310,7 @@ class _MxcImageState extends State { fit: widget.fit, needResize: needResize, cacheWidth: widget.cacheWidth, + cacheHeight: widget.cacheHeight, imageErrorWidgetBuilder: (context, __, ___) { _isCached = false; _imageData = null; @@ -327,6 +331,7 @@ class _ImageWidget extends StatelessWidget { final BoxFit? fit; final ImageErrorWidgetBuilder imageErrorWidgetBuilder; final int? cacheWidth; + final int? cacheHeight; const _ImageWidget({ this.filePath, @@ -337,11 +342,11 @@ class _ImageWidget extends StatelessWidget { this.fit, required this.imageErrorWidgetBuilder, this.cacheWidth, + this.cacheHeight, }); @override Widget build(BuildContext context) { - final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; return filePath != null && filePath!.isNotEmpty ? Image.file( File(filePath!), @@ -350,11 +355,13 @@ class _ImageWidget extends StatelessWidget { cacheWidth: cacheWidth != null ? cacheWidth! : (width != null && needResize) - ? (width! * devicePixelRatio).toInt() + ? context.getCacheSize(width!) + : null, + cacheHeight: cacheHeight != null + ? cacheHeight! + : (height != null && needResize) + ? context.getCacheSize(height!) : null, - cacheHeight: (height != null && needResize) - ? (height! * devicePixelRatio).toInt() - : null, fit: fit, filterQuality: FilterQuality.medium, errorBuilder: imageErrorWidgetBuilder, @@ -367,11 +374,13 @@ class _ImageWidget extends StatelessWidget { cacheWidth: cacheWidth != null ? cacheWidth! : (width != null && needResize) - ? (width! * devicePixelRatio).toInt() + ? context.getCacheSize(width!) + : null, + cacheHeight: cacheHeight != null + ? cacheHeight! + : (height != null && needResize) + ? context.getCacheSize(height!) : null, - cacheHeight: (height != null && needResize) - ? (height! * devicePixelRatio).toInt() - : null, fit: fit, filterQuality: FilterQuality.medium, errorBuilder: imageErrorWidgetBuilder, From 7103f98f7ddf0f4921f6b84482296e6ddee5ec6d Mon Sep 17 00:00:00 2001 From: --global Date: Mon, 13 May 2024 11:14:00 +0700 Subject: [PATCH 5/5] hot-fix: rollback to fix the issue related to timestamp when scroll --- lib/pages/chat/chat.dart | 3 ++- lib/pages/chat/events/message/message.dart | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 86679377dc..b277c76ce5 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -16,6 +16,7 @@ import 'package:fluffychat/widgets/mixins/popup_menu_widget_style.dart'; import 'package:fluffychat/widgets/mixins/twake_context_menu_mixin.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:fluffychat/utils/extension/global_key_extension.dart'; +import 'package:inview_notifier_list/inview_notifier_list.dart'; import 'package:universal_html/html.dart' as html; import 'package:adaptive_dialog/adaptive_dialog.dart'; @@ -1930,7 +1931,7 @@ class ChatController extends State pinnedMessageScrollController.dispose(); onUpdateEventStreamSubcription?.cancel(); keyboardVisibilitySubscription?.cancel(); - + InViewNotifierListCustom.of(context)?.dispose(); replyEventNotifier.dispose(); super.dispose(); } diff --git a/lib/pages/chat/events/message/message.dart b/lib/pages/chat/events/message/message.dart index 3bfec62c5a..c5c9fea97a 100644 --- a/lib/pages/chat/events/message/message.dart +++ b/lib/pages/chat/events/message/message.dart @@ -113,7 +113,6 @@ class _MessageState extends State { void dispose() { inViewState?.removeContext(context: context); inViewState?.removeListener(_inviewStateListener); - inViewState?.dispose(); inviewNotifier.dispose(); super.dispose(); }