diff --git a/lib/auth/login/view/login_page.dart b/lib/auth/login/view/login_page.dart index 8339fcf4..d8ce4085 100644 --- a/lib/auth/login/view/login_page.dart +++ b/lib/auth/login/view/login_page.dart @@ -56,11 +56,29 @@ class LoginView extends StatelessWidget { ), ), const Align(child: SignInButton()), - const Padding( - padding: EdgeInsets.symmetric( - vertical: AppSpacing.md, - ), - child: AppDivider(withText: true), + Row( + children: [ + const Expanded( + child: AppDivider( + endIndent: AppSpacing.sm, + indent: AppSpacing.md, + color: AppColors.white, + height: 36, + ), + ), + Text( + 'OR', + style: context.titleMedium, + ), + const Expanded( + child: AppDivider( + color: AppColors.white, + indent: AppSpacing.sm, + endIndent: AppSpacing.md, + height: 36, + ), + ), + ], ), Align( child: AuthProviderSignInButton( diff --git a/lib/chats/chat/view/chat_page.dart b/lib/chats/chat/view/chat_page.dart index 0921a79e..12c80b1b 100644 --- a/lib/chats/chat/view/chat_page.dart +++ b/lib/chats/chat/view/chat_page.dart @@ -456,10 +456,11 @@ class ChatAppBar extends StatelessWidget implements PreferredSizeWidget { title: Text(participant.displayUsername), subtitle: Text(context.l10n.onlineText), leading: UserStoriesAvatar( + resizeHeight: 156, author: participant, enableInactiveBorder: false, withAdaptiveBorder: false, - radius: 26, + radius: 22, ), ), bottom: const PreferredSize( diff --git a/lib/chats/chat/widgets/message_content.dart b/lib/chats/chat/widgets/message_content.dart index 1a7fedd6..686fa3af 100644 --- a/lib/chats/chat/widgets/message_content.dart +++ b/lib/chats/chat/widgets/message_content.dart @@ -236,13 +236,7 @@ class MessageSharedPost extends StatelessWidget { ), Stack( children: [ - AspectRatio( - aspectRatio: 1, - child: ImageAttachmentThumbnail( - image: Attachment(imageUrl: sharedPost.firstMediaUrl), - fit: BoxFit.cover, - ), - ), + MessageSharedPostImage(sharedPost: sharedPost), Positioned( top: AppSpacing.sm, right: AppSpacing.sm, @@ -329,6 +323,31 @@ class MessageSharedPost extends StatelessWidget { } } +class MessageSharedPostImage extends StatelessWidget { + const MessageSharedPostImage({ + required this.sharedPost, + super.key, + }); + + final PostBlock sharedPost; + + @override + Widget build(BuildContext context) { + final screenWidth = (context.screenWidth * .85) - AppSpacing.md * 2; + final pixelRatio = context.devicePixelRatio; + + final thumbnailWidth = (screenWidth * pixelRatio) ~/ 1; + return AspectRatio( + aspectRatio: 1, + child: ImageAttachmentThumbnail( + resizeHeight: thumbnailWidth, + image: Attachment(imageUrl: sharedPost.firstMediaUrl), + fit: BoxFit.cover, + ), + ); + } +} + class MessageSharedReel extends StatelessWidget { const MessageSharedReel({ required this.sharedPost, diff --git a/lib/chats/chat/widgets/message_input_controller.dart b/lib/chats/chat/widgets/message_input_controller.dart index 7e274202..8fdb72ea 100644 --- a/lib/chats/chat/widgets/message_input_controller.dart +++ b/lib/chats/chat/widgets/message_input_controller.dart @@ -144,26 +144,11 @@ class MessageInputController extends ValueNotifier { attachments = [...attachments, attachment]; } - /// Adds a new attachment at the specified [index]. - void addAttachmentAt(int index, Attachment attachment) { - attachments = [...attachments]..insert(index, attachment); - } - /// Removes the specified [attachment] from the message. void removeAttachment(Attachment attachment) { attachments = [...attachments]..remove(attachment); } - /// Remove the attachment with the given [attachmentId]. - void removeAttachmentById(String attachmentId) { - attachments = [...attachments]..removeWhere((it) => it.id == attachmentId); - } - - /// Removes the attachment at the given [index]. - void removeAttachmentAt(int index) { - attachments = [...attachments]..removeAt(index); - } - /// Clears the message attachments. void clearAttachments() { attachments = []; @@ -242,17 +227,12 @@ class MessageInputController extends ValueNotifier { if (attachments .map((e) => e.type.toAttachmentType) .contains(AttachmentType.urlPreview)) { - attachments.retainWhere( - (e) => e.type.toAttachmentType == AttachmentType.urlPreview, - ); - attachments = [...attachments] - ..remove(attachment) - ..insert(0, attachment); - } else { - attachments = [...attachments] - ..remove(attachment) - ..insert(0, attachment); + attachments = attachments + ..removeWhere( + (e) => e.type.toAttachmentType == AttachmentType.urlPreview, + ); } + addAttachment(attachment); _ogAttachment = attachment; } diff --git a/lib/chats/chat/widgets/replied_message_bubble.dart b/lib/chats/chat/widgets/replied_message_bubble.dart index 33f72e23..ddca4a06 100644 --- a/lib/chats/chat/widgets/replied_message_bubble.dart +++ b/lib/chats/chat/widgets/replied_message_bubble.dart @@ -56,6 +56,7 @@ class RepliedMessageBubble extends StatelessWidget { children: [ if (!repliedMessageSharedPostDeleted) ...[ ImageAttachmentThumbnail( + resizeHeight: 138, image: Attachment(imageUrl: replyMessageAttachmentUrl), fit: BoxFit.cover, borderRadius: 4, diff --git a/lib/chats/widgets/chat_inbox_tile.dart b/lib/chats/widgets/chat_inbox_tile.dart index 3ada1a77..0b6168ad 100644 --- a/lib/chats/widgets/chat_inbox_tile.dart +++ b/lib/chats/widgets/chat_inbox_tile.dart @@ -37,6 +37,7 @@ class ChatInboxTile extends StatelessWidget { .add(ChatsDeleteChatRequested(chatId: chat.id, userId: user.id)), ), leading: UserStoriesAvatar( + resizeHeight: 156, author: participant, enableInactiveBorder: false, withAdaptiveBorder: false, diff --git a/lib/comments/comment/view/comment_view.dart b/lib/comments/comment/view/comment_view.dart index cfa069ad..61d5436c 100644 --- a/lib/comments/comment/view/comment_view.dart +++ b/lib/comments/comment/view/comment_view.dart @@ -77,6 +77,7 @@ class CommentGroup extends StatelessWidget { ), avatarBuilder: (context, author, onAvatarTap, radius) => UserStoriesAvatar( + resizeHeight: 108, author: author, onAvatarTap: onAvatarTap, radius: radius, diff --git a/lib/feed/post/view/post_view.dart b/lib/feed/post/view/post_view.dart index d4d54aab..204c3c82 100644 --- a/lib/feed/post/view/post_view.dart +++ b/lib/feed/post/view/post_view.dart @@ -135,6 +135,7 @@ class PostLargeView extends StatelessWidget { likersInFollowings: likersInFollowings, postAuthorAvatarBuilder: (context, author, onAvatarTap) { return UserStoriesAvatar( + resizeHeight: 108, author: author.toUser, onAvatarTap: onAvatarTap, enableInactiveBorder: false, diff --git a/lib/feed/post/widgets/post_popup_dialog.dart b/lib/feed/post/widgets/post_popup_dialog.dart index 3d3c5175..60a3c8ba 100644 --- a/lib/feed/post/widgets/post_popup_dialog.dart +++ b/lib/feed/post/widgets/post_popup_dialog.dart @@ -100,6 +100,7 @@ class PopupDialogHeader extends StatelessWidget { ), horizontalTitleGap: AppSpacing.sm, leading: UserProfileAvatar( + resizeHeight: 108, avatarUrl: block.author.avatarUrl, isLarge: false, enableBorder: false, @@ -134,13 +135,7 @@ class PopupDialogBody extends StatelessWidget { alignment: Alignment.center, children: [ if (block.firstMedia is ImageMedia) - AspectRatio( - aspectRatio: 1, - child: ImageAttachmentThumbnail( - image: Attachment(imageUrl: block.firstMediaUrl), - fit: BoxFit.cover, - ), - ) + PostPopupImage(block: block) else InlineVideo( videoSettings: VideoSettings.build( @@ -172,6 +167,32 @@ class PopupDialogBody extends StatelessWidget { } } +class PostPopupImage extends StatelessWidget { + const PostPopupImage({ + required this.block, + super.key, + }); + + final PostBlock block; + + @override + Widget build(BuildContext context) { + final screenWidth = (context.screenWidth) - AppSpacing.md * 2; + final pixelRatio = context.devicePixelRatio; + + final thumbnailWidth = (screenWidth * pixelRatio) ~/ 1; + + return AspectRatio( + aspectRatio: 1, + child: ImageAttachmentThumbnail( + resizeWidth: thumbnailWidth, + image: Attachment(imageUrl: block.firstMediaUrl), + fit: BoxFit.cover, + ), + ); + } +} + class PopupDialogFooter extends StatelessWidget { const PopupDialogFooter({ required this.likeButtonKey, diff --git a/lib/feed/post/widgets/share_post.dart b/lib/feed/post/widgets/share_post.dart index 1926f1eb..a52b809e 100644 --- a/lib/feed/post/widgets/share_post.dart +++ b/lib/feed/post/widgets/share_post.dart @@ -526,6 +526,7 @@ class UsersListView extends StatelessWidget { Stack( children: [ UserProfileAvatar( + resizeHeight: 252, avatarUrl: user.avatarUrl, enableBorder: false, ), diff --git a/lib/feed/view/feed_page.dart b/lib/feed/view/feed_page.dart index 6ac1558a..e46cfe9e 100644 --- a/lib/feed/view/feed_page.dart +++ b/lib/feed/view/feed_page.dart @@ -234,7 +234,12 @@ class FeedBody extends StatelessWidget { } } if (block is PostBlock) { - return PostView(key: ValueKey(block.id), block: block, postIndex: index); + return PostView( + key: ValueKey(block.id), + block: block, + postIndex: index, + withInViewNotifier: block.isReel, + ); } return Text('Unknown block type: ${block.type}'); diff --git a/lib/reels/reel/view/reel_view.dart b/lib/reels/reel/view/reel_view.dart index 884d1db5..d7a7703f 100644 --- a/lib/reels/reel/view/reel_view.dart +++ b/lib/reels/reel/view/reel_view.dart @@ -404,6 +404,7 @@ class ReelAuthorListTile extends StatelessWidget { return Row( children: [ UserStoriesAvatar( + resizeHeight: 108, author: author.toUser, withAdaptiveBorder: false, enableInactiveBorder: false, diff --git a/lib/search/view/search_page.dart b/lib/search/view/search_page.dart index 7550b2ee..d34cd070 100644 --- a/lib/search/view/search_page.dart +++ b/lib/search/view/search_page.dart @@ -71,6 +71,7 @@ class UserListTile extends StatelessWidget { Widget build(BuildContext context) { return ListTile( leading: UserStoriesAvatar( + resizeHeight: 156, author: user, withAdaptiveBorder: false, enableInactiveBorder: false, diff --git a/lib/stories/widgets/stories_carousel.dart b/lib/stories/widgets/stories_carousel.dart index e06e8962..b9bed7d7 100644 --- a/lib/stories/widgets/stories_carousel.dart +++ b/lib/stories/widgets/stories_carousel.dart @@ -102,6 +102,7 @@ class StoriesListView extends StatelessWidget { onLongPress, ) { return UserStoriesAvatar( + resizeHeight: 252, isLarge: true, stories: stories, author: author, diff --git a/lib/stories/widgets/story_avatar.dart b/lib/stories/widgets/story_avatar.dart index 8ff2e544..580aea29 100644 --- a/lib/stories/widgets/story_avatar.dart +++ b/lib/stories/widgets/story_avatar.dart @@ -99,6 +99,7 @@ class AvatarView extends StatelessWidget { onLongPress, ) ?? UserProfileAvatar( + resizeHeight: 252, withAddButton: isMine, stories: state.stories, onTap: onTap, diff --git a/lib/stories/widgets/user_stories_avatar.dart b/lib/stories/widgets/user_stories_avatar.dart index f9b0874e..66eb23e8 100644 --- a/lib/stories/widgets/user_stories_avatar.dart +++ b/lib/stories/widgets/user_stories_avatar.dart @@ -22,6 +22,8 @@ class UserStoriesAvatar extends StatelessWidget { this.showStories, this.showWhenSeen, this.isLarge = false, + this.resizeHeight, + this.resizeWidth, this.isImagePicker = false, this.enableInactiveBorder = true, this.withShimmerPlaceholder = false, @@ -42,6 +44,8 @@ class UserStoriesAvatar extends StatelessWidget { final bool? showStories; final bool? showWhenSeen; final bool isLarge; + final int? resizeHeight; + final int? resizeWidth; final bool isImagePicker; final bool enableInactiveBorder; final bool withShimmerPlaceholder; @@ -64,6 +68,8 @@ class UserStoriesAvatar extends StatelessWidget { showStories: showStories, showWhenSeen: showWhenSeen, isLarge: isLarge, + resizeHeight: resizeHeight, + resizeWidth: resizeWidth, onImagePick: onImagePick, enableInactiveBorder: enableInactiveBorder, withShimmerPlaceholder: withShimmerPlaceholder, @@ -104,6 +110,8 @@ class ProfileAvatar extends StatelessWidget { required this.onImagePick, required this.onAddButtonTap, required this.withAdaptiveBorder, + required this.resizeHeight, + required this.resizeWidth, super.key, }); @@ -116,6 +124,8 @@ class ProfileAvatar extends StatelessWidget { final bool? showStories; final bool? showWhenSeen; final bool isLarge; + final int? resizeHeight; + final int? resizeWidth; final bool enableInactiveBorder; final bool withShimmerPlaceholder; final bool withAdaptiveBorder; @@ -142,6 +152,8 @@ class ProfileAvatar extends StatelessWidget { showStories: this.showStories ?? showStories, avatarUrl: author.avatarUrl, isLarge: isLarge, + resizeHeight: resizeHeight, + resizeWidth: resizeWidth, onImagePick: onImagePick, withAddButton: withAddButton, onLongPress: onLongPress, diff --git a/lib/timeline/view/timeline_page.dart b/lib/timeline/view/timeline_page.dart index c2f1cbb3..15fb981d 100644 --- a/lib/timeline/view/timeline_page.dart +++ b/lib/timeline/view/timeline_page.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:app_ui/app_ui.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -215,10 +217,7 @@ class _TimelineGridViewState extends State { ); }, ) - : ImageAttachmentThumbnail( - image: Attachment(imageUrl: url), - fit: BoxFit.cover, - ), + : TimelinePostImage(url: url), ), ); }, @@ -226,6 +225,27 @@ class _TimelineGridViewState extends State { } } +class TimelinePostImage extends StatelessWidget { + const TimelinePostImage({required this.url, super.key}); + + final String url; + + @override + Widget build(BuildContext context) { + /// AppSpacing.xxs is the padding of the image. + final screenWidth = (context.screenWidth - AppSpacing.xxs) / 3; + final pixelRatio = context.devicePixelRatio; + + final size = min((screenWidth * pixelRatio) ~/ 1, 1920); + + return ImageAttachmentThumbnail( + resizeHeight: size, + image: Attachment(imageUrl: url), + fit: BoxFit.cover, + ); + } +} + class TimelineLoading extends StatelessWidget { const TimelineLoading({super.key}); diff --git a/lib/user_profile/view/user_profile_page.dart b/lib/user_profile/view/user_profile_page.dart index a349734c..6f168e2f 100644 --- a/lib/user_profile/view/user_profile_page.dart +++ b/lib/user_profile/view/user_profile_page.dart @@ -1,5 +1,7 @@ // ignore_for_file: lines_longer_than_80_chars +import 'dart:math'; + import 'package:app_ui/app_ui.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -214,7 +216,6 @@ class _PostsPageState extends State return SliverGrid.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, - mainAxisExtent: 120, mainAxisSpacing: 2, crossAxisSpacing: 2, ), @@ -232,10 +233,7 @@ class _PostsPageState extends State isReel: block.isReel, multiMedia: multiMedia, mediaUrl: block.firstMediaUrl!, - imageThumbnailBuilder: (_, url) => ImageAttachmentThumbnail( - image: Attachment(imageUrl: url), - fit: BoxFit.cover, - ), + imageThumbnailBuilder: (_, url) => PostSmallImage(url: url), ), ); }, @@ -247,6 +245,27 @@ class _PostsPageState extends State } } +class PostSmallImage extends StatelessWidget { + const PostSmallImage({required this.url, super.key}); + + final String url; + + @override + Widget build(BuildContext context) { + /// AppSpacing.xxs is the padding of the image. + final screenWidth = (context.screenWidth - AppSpacing.xxs) / 3; + final pixelRatio = context.devicePixelRatio; + + final size = min((screenWidth * pixelRatio) ~/ 1, 1920); + return ImageAttachmentThumbnail( + resizeHeight: size, + resizeWidth: size, + image: Attachment(imageUrl: url), + fit: BoxFit.cover, + ); + } +} + class UserProfileMentionedPostsPage extends StatelessWidget { const UserProfileMentionedPostsPage({super.key}); diff --git a/lib/user_profile/widgets/user_profile_create_post.dart b/lib/user_profile/widgets/user_profile_create_post.dart index c40594cd..e18bd752 100644 --- a/lib/user_profile/widgets/user_profile_create_post.dart +++ b/lib/user_profile/widgets/user_profile_create_post.dart @@ -180,6 +180,7 @@ class PublishPostButton extends StatelessWidget { elevation: 0, color: context.reversedAdaptiveColor, padding: EdgeInsets.zero, + height: 90, child: Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/user_profile/widgets/user_profile_header.dart b/lib/user_profile/widgets/user_profile_header.dart index 6a10bc88..8a7f0725 100644 --- a/lib/user_profile/widgets/user_profile_header.dart +++ b/lib/user_profile/widgets/user_profile_header.dart @@ -50,6 +50,7 @@ class UserProfileHeader extends StatelessWidget { Row( children: [ UserStoriesAvatar( + resizeHeight: 252, author: user, onLongPress: (avatarUrl) => avatarUrl == null ? null diff --git a/lib/user_profile/widgets/user_profile_list_tile.dart b/lib/user_profile/widgets/user_profile_list_tile.dart index 473fe827..8ff1db13 100644 --- a/lib/user_profile/widgets/user_profile_list_tile.dart +++ b/lib/user_profile/widgets/user_profile_list_tile.dart @@ -73,6 +73,7 @@ class _UserProfileListTileState extends State { child: Row( children: [ UserStoriesAvatar( + resizeHeight: 156, author: widget.user, withAdaptiveBorder: false, enableInactiveBorder: false, diff --git a/packages/app_ui/lib/src/widgets/app_divider.dart b/packages/app_ui/lib/src/widgets/app_divider.dart index 748e0a4a..bf685c5c 100644 --- a/packages/app_ui/lib/src/widgets/app_divider.dart +++ b/packages/app_ui/lib/src/widgets/app_divider.dart @@ -8,62 +8,60 @@ class AppDivider extends StatelessWidget { /// {@macro app_divider} const AppDivider({ super.key, - this.padding, - this.withText = false, + this.height, + this.indent, + this.endIndent, + this.color, }); - /// The optional padding for the divider. - final double? padding; + /// The divider's height extent. + /// + /// The divider itself is always drawn as a horizontal line that is centered + /// within the height specified by this value. + /// + /// If this is null, then the [DividerThemeData.space] is used. If that is + /// also null, then this defaults to 16.0. + final double? height; - /// Whether the divider should divide with text, e.g `OR`. - final bool withText; + /// The amount of empty space to the leading edge of the divider. + /// + /// If this is null, then the [DividerThemeData.indent] is used. If that is + /// also null, then this defaults to 0.0. + final double? indent; + + /// The amount of empty space to the trailing edge of the divider. + /// + /// If this is null, then the [DividerThemeData.endIndent] is used. If that is + /// also null, then this defaults to 0.0. + final double? endIndent; + + /// The color to use when painting the line. + /// + /// If this is null, then the [DividerThemeData.color] is used. If that is + /// also null, then [ThemeData.dividerColor] is used. + /// + /// {@tool snippet} + /// + /// ```dart + /// const Divider( + /// color: Colors.deepOrange, + /// ) + /// ``` + /// {@end-tool} + final Color? color; @override Widget build(BuildContext context) { - final dividerColor = context.customReversedAdaptiveColor( + final effectiveColor = context.customReversedAdaptiveColor( dark: AppColors.emphasizeDarkGrey, light: AppColors.brightGrey, ); - if (!withText) { - return Container( - margin: - padding != null ? EdgeInsets.symmetric(horizontal: padding!) : null, - height: 1, - color: dividerColor, - ); - } - return Row( - children: [ - Expanded( - child: Container( - margin: const EdgeInsets.only( - left: AppSpacing.md, - right: AppSpacing.sm, - ), - child: const Divider( - color: AppColors.white, - height: 36, - ), - ), - ), - Text( - 'OR', - style: context.titleMedium, - ), - Expanded( - child: Container( - margin: const EdgeInsets.only( - left: AppSpacing.sm, - right: AppSpacing.md, - ), - child: const Divider( - color: AppColors.white, - height: 36, - ), - ), - ), - ], + return Divider( + height: height, + indent: indent, + endIndent: endIndent, + color: color ?? effectiveColor, ); } } @@ -75,20 +73,57 @@ class AppSliverDivider extends StatelessWidget { /// {@macro app_sliver_divider} const AppSliverDivider({ super.key, - this.padding, - this.withText = false, + this.height, + this.indent, + this.endIndent, + this.color, }); - /// The optional padding of the divider. - final double? padding; + /// The divider's height extent. + /// + /// The divider itself is always drawn as a horizontal line that is centered + /// within the height specified by this value. + /// + /// If this is null, then the [DividerThemeData.space] is used. If that is + /// also null, then this defaults to 16.0. + final double? height; + + /// The amount of empty space to the leading edge of the divider. + /// + /// If this is null, then the [DividerThemeData.indent] is used. If that is + /// also null, then this defaults to 0.0. + final double? indent; + + /// The amount of empty space to the trailing edge of the divider. + /// + /// If this is null, then the [DividerThemeData.endIndent] is used. If that is + /// also null, then this defaults to 0.0. + final double? endIndent; - /// Whether should be displayed with dividing text. - final bool withText; + /// The color to use when painting the line. + /// + /// If this is null, then the [DividerThemeData.color] is used. If that is + /// also null, then [ThemeData.dividerColor] is used. + /// + /// {@tool snippet} + /// + /// ```dart + /// const Divider( + /// color: Colors.deepOrange, + /// ) + /// ``` + /// {@end-tool} + final Color? color; @override Widget build(BuildContext context) { return SliverToBoxAdapter( - child: AppDivider(padding: padding, withText: withText), + child: AppDivider( + color: color, + endIndent: endIndent, + indent: indent, + height: height, + ), ); } } diff --git a/packages/instagram_blocks_ui/lib/src/attachments/view/url_attachment.dart b/packages/instagram_blocks_ui/lib/src/attachments/view/url_attachment.dart index cc814d54..655cf56b 100644 --- a/packages/instagram_blocks_ui/lib/src/attachments/view/url_attachment.dart +++ b/packages/instagram_blocks_ui/lib/src/attachments/view/url_attachment.dart @@ -83,19 +83,42 @@ class UrlAttachment extends StatelessWidget { ), Padding( padding: const EdgeInsets.only(top: AppSpacing.xxs), - child: AspectRatio( - aspectRatio: urlAttachment.aspectRatio, - child: ImageAttachmentThumbnail( - height: urlAttachment.originalHeight?.toDouble(), - width: urlAttachment.originalWidth?.toDouble(), - image: urlAttachment, - borderRadius: 4, - fit: BoxFit.cover, - ), - ), + child: UrlAttachmentImage(urlAttachment: urlAttachment), ), ], ), ); } } + +class UrlAttachmentImage extends StatelessWidget { + const UrlAttachmentImage({ + required this.urlAttachment, + super.key, + }); + + final Attachment urlAttachment; + + @override + Widget build(BuildContext context) { + // AppSpacing.md * 5 and AppSpacing.lg is the overall padding on the left + // and right of the message content. + final screenWidth = + (context.screenWidth * .85) - (AppSpacing.md * 5) - AppSpacing.lg; + final pixelRatio = context.devicePixelRatio; + + final thumbnailWidth = (screenWidth * pixelRatio) ~/ 1; + final thumbnailHeight = (thumbnailWidth * (9 / 16)).toInt(); + + return AspectRatio( + aspectRatio: 16 / 9, + child: ImageAttachmentThumbnail( + resizeWidth: thumbnailWidth, + resizeHeight: thumbnailHeight, + image: urlAttachment, + borderRadius: 4, + fit: BoxFit.cover, + ), + ); + } +} diff --git a/packages/instagram_blocks_ui/lib/src/attachments/widgets/thumbnail/image_attachment_thumbnail.dart b/packages/instagram_blocks_ui/lib/src/attachments/widgets/thumbnail/image_attachment_thumbnail.dart index 2528975d..17cfd86f 100644 --- a/packages/instagram_blocks_ui/lib/src/attachments/widgets/thumbnail/image_attachment_thumbnail.dart +++ b/packages/instagram_blocks_ui/lib/src/attachments/widgets/thumbnail/image_attachment_thumbnail.dart @@ -23,6 +23,8 @@ class ImageAttachmentThumbnail extends StatelessWidget { this.height, this.memCacheHeight, this.memCacheWidth, + this.resizeHeight, + this.resizeWidth, this.fit, this.filterQuality = FilterQuality.low, this.withPlaceholder = true, @@ -47,6 +49,12 @@ class ImageAttachmentThumbnail extends StatelessWidget { /// Memory height of the attachment image thumbnail. final int? memCacheHeight; + /// Resize width of the attachment image thumbnail. + final int? resizeWidth; + + /// Resize height of the attachment image thumbnail. + final int? resizeHeight; + /// The border radius of the image. final double? borderRadius; @@ -75,6 +83,8 @@ class ImageAttachmentThumbnail extends StatelessWidget { StackTrace? stackTrace, { double? height, double? width, + int? resizeHeight, + int? resizeWidth, double? borderRadius, }) { return ThumbnailError( @@ -82,6 +92,8 @@ class ImageAttachmentThumbnail extends StatelessWidget { stackTrace: stackTrace, height: height ?? double.infinity, width: width ?? double.infinity, + resizeWidth: resizeWidth, + resizeHeight: resizeHeight, borderRadius: borderRadius, fit: BoxFit.cover, ); @@ -99,6 +111,8 @@ class ImageAttachmentThumbnail extends StatelessWidget { file: file, width: width, height: height, + cacheWidth: memCacheWidth, + cacheHeight: memCacheHeight, borderRadius: borderRadius, withPlaceholder: withPlaceholder, withAdaptiveColors: withAdaptiveColors, @@ -116,6 +130,8 @@ class ImageAttachmentThumbnail extends StatelessWidget { height: height, memCacheHeight: memCacheHeight, memCacheWidth: memCacheWidth, + resizeHeight: resizeHeight, + resizeWidth: resizeWidth, withPlaceholder: withPlaceholder, withAdaptiveColors: withAdaptiveColors, borderRadius: borderRadius, @@ -130,8 +146,10 @@ class ImageAttachmentThumbnail extends StatelessWidget { context, 'Image attachment is not valid', StackTrace.current, - height: height, width: width, + height: height, + resizeWidth: resizeWidth, + resizeHeight: resizeHeight, borderRadius: borderRadius, ); } @@ -143,6 +161,8 @@ class LocalImageAttachment extends StatelessWidget { required this.fit, this.width, this.height, + this.cacheWidth, + this.cacheHeight, this.borderRadius, this.withPlaceholder = true, this.file, @@ -158,6 +178,8 @@ class LocalImageAttachment extends StatelessWidget { final File? imageFile; final double? width; final double? height; + final int? cacheWidth; + final int? cacheHeight; final double? borderRadius; final BoxFit? fit; final FilterQuality filterQuality; @@ -175,8 +197,8 @@ class LocalImageAttachment extends StatelessWidget { bytes: bytes, height: height, width: width, - cacheHeight: height?.toInt(), - cacheWidth: width?.toInt(), + cacheHeight: cacheHeight, + cacheWidth: cacheWidth, errorBuilder: errorBuilder, filterQuality: filterQuality, placeholder: !withPlaceholder @@ -227,6 +249,8 @@ class NetworkImageAttachment extends StatelessWidget { required this.fit, required this.memCacheWidth, required this.memCacheHeight, + required this.resizeHeight, + required this.resizeWidth, required this.filterQuality, super.key, }); @@ -236,6 +260,8 @@ class NetworkImageAttachment extends StatelessWidget { final double? height; final int? memCacheWidth; final int? memCacheHeight; + final int? resizeHeight; + final int? resizeWidth; final double? borderRadius; final BoxBorder? border; final BoxFit? fit; @@ -249,8 +275,8 @@ class NetworkImageAttachment extends StatelessWidget { return CachedNetworkImage( imageUrl: url, cacheKey: url, - memCacheHeight: memCacheHeight ?? height?.toInt(), - memCacheWidth: memCacheWidth ?? width?.toInt(), + memCacheHeight: memCacheHeight, + memCacheWidth: memCacheWidth, imageBuilder: (context, imageProvider) { return Container( height: height, @@ -261,7 +287,11 @@ class NetworkImageAttachment extends StatelessWidget { ? null : BorderRadius.all(Radius.circular(borderRadius!)), image: DecorationImage( - image: imageProvider, + image: ResizeImage.resizeIfNeeded( + resizeWidth, + resizeHeight, + imageProvider, + ), fit: fit, filterQuality: filterQuality, ), @@ -271,8 +301,8 @@ class NetworkImageAttachment extends StatelessWidget { placeholder: !withPlaceholder ? null : (context, __) => ShimmerPlaceholder( - height: height, width: width, + height: height, withAdaptiveColors: withAdaptiveColors, borderRadius: borderRadius, ), @@ -283,6 +313,8 @@ class NetworkImageAttachment extends StatelessWidget { StackTrace.current, height: height, width: width, + resizeWidth: resizeWidth, + resizeHeight: resizeHeight, borderRadius: borderRadius, ); }, diff --git a/packages/instagram_blocks_ui/lib/src/attachments/widgets/thumbnail/thumbnail_error.dart b/packages/instagram_blocks_ui/lib/src/attachments/widgets/thumbnail/thumbnail_error.dart index a05b81a2..44dc9549 100644 --- a/packages/instagram_blocks_ui/lib/src/attachments/widgets/thumbnail/thumbnail_error.dart +++ b/packages/instagram_blocks_ui/lib/src/attachments/widgets/thumbnail/thumbnail_error.dart @@ -7,6 +7,8 @@ typedef ThumbnailErrorBuilder = Widget Function( StackTrace? stackTrace, { double? height, double? width, + int? resizeWidth, + int? resizeHeight, double? borderRadius, }); @@ -21,6 +23,8 @@ class ThumbnailError extends StatelessWidget { this.stackTrace, this.width, this.height, + this.resizeWidth, + this.resizeHeight, this.borderRadius, this.fit, }); @@ -31,6 +35,12 @@ class ThumbnailError extends StatelessWidget { /// The height of the thumbnail. final double? height; + /// The resize width of the thumbnail. + final int? resizeWidth; + + /// The resize height of the thumbnail. + final int? resizeHeight; + /// The border radius of the thumbnail. final double? borderRadius; @@ -45,13 +55,6 @@ class ThumbnailError extends StatelessWidget { @override Widget build(BuildContext context) { - if (borderRadius == null) { - return Assets.images.placeholder.image( - width: width, - height: height, - fit: fit, - ); - } return SizedBox( height: height, width: width, @@ -63,7 +66,11 @@ class ThumbnailError extends StatelessWidget { image: DecorationImage( fit: fit, filterQuality: FilterQuality.high, - image: Assets.images.placeholder.provider(), + image: ResizeImage.resizeIfNeeded( + resizeWidth, + resizeHeight, + Assets.images.placeholder.provider(), + ), ), ), ), diff --git a/packages/instagram_blocks_ui/lib/src/post_large/post_footer.dart b/packages/instagram_blocks_ui/lib/src/post_large/post_footer.dart index 1c7f9cb3..cc2e5948 100644 --- a/packages/instagram_blocks_ui/lib/src/post_large/post_footer.dart +++ b/packages/instagram_blocks_ui/lib/src/post_large/post_footer.dart @@ -58,7 +58,6 @@ class PostFooter extends StatelessWidget { imageUrl: block.firstMedia?.url, onTap: () => onAvatarTap.call(author.avatarUrl), ), - const AppDivider(padding: AppSpacing.md), const Gap.v(AppSpacing.sm), Padding( padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), @@ -227,7 +226,10 @@ class LikersInFollowings extends StatelessWidget { padding: const EdgeInsets.all(AppSpacing.xxs), child: CircleAvatar( backgroundColor: AppColors.white, - foregroundImage: Assets.images.profilePhoto.provider(), + foregroundImage: ResizeImage( + Assets.images.profilePhoto.provider(), + height: 72, + ), ), ), ), @@ -243,7 +245,8 @@ class LikersInFollowings extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(AppSpacing.xxs), child: CircleAvatar( - backgroundImage: imageProvider, + backgroundImage: + ResizeImage(imageProvider, height: 72), backgroundColor: context.theme.colorScheme.surface, ), ), diff --git a/packages/instagram_blocks_ui/lib/src/post_large/post_header.dart b/packages/instagram_blocks_ui/lib/src/post_large/post_header.dart index 4db8357d..704c5e39 100644 --- a/packages/instagram_blocks_ui/lib/src/post_large/post_header.dart +++ b/packages/instagram_blocks_ui/lib/src/post_large/post_header.dart @@ -84,6 +84,7 @@ class PostHeader extends StatelessWidget { onAvatarTap, ) ?? UserProfileAvatar( + resizeHeight: 108, userId: author.id, isLarge: false, avatarUrl: author.avatarUrl, diff --git a/packages/instagram_blocks_ui/lib/src/post_small/post_small.dart b/packages/instagram_blocks_ui/lib/src/post_small/post_small.dart index 30fceeb7..24fa1a42 100644 --- a/packages/instagram_blocks_ui/lib/src/post_small/post_small.dart +++ b/packages/instagram_blocks_ui/lib/src/post_small/post_small.dart @@ -62,8 +62,6 @@ class _PostThumbnailImage extends StatelessWidget { final thumbnailImage = imageThumbnailBuilder?.call(context, mediaUrl) ?? ImageAttachmentThumbnail( image: Attachment(imageUrl: mediaUrl), - memCacheHeight: 225, - memCacheWidth: 225, fit: BoxFit.cover, ); diff --git a/packages/instagram_blocks_ui/lib/src/user_profile/user_profile_avatar.dart b/packages/instagram_blocks_ui/lib/src/user_profile/user_profile_avatar.dart index 370087ea..9501690b 100644 --- a/packages/instagram_blocks_ui/lib/src/user_profile/user_profile_avatar.dart +++ b/packages/instagram_blocks_ui/lib/src/user_profile/user_profile_avatar.dart @@ -19,6 +19,8 @@ class UserProfileAvatar extends StatelessWidget { super.key, this.avatarUrl, this.radius, + this.resizeHeight, + this.resizeWidth, this.isLarge = true, this.onTapPickImage = false, this.strokeWidth, @@ -42,6 +44,8 @@ class UserProfileAvatar extends StatelessWidget { final String? avatarUrl; final double? radius; final double? strokeWidth; + final int? resizeHeight; + final int? resizeWidth; final bool isLarge; final bool onTapPickImage; final bool withShimmerPlaceholder; @@ -191,7 +195,11 @@ class UserProfileAvatar extends StatelessWidget { final circleAvatar = CircleAvatar( radius: radius, backgroundColor: AppColors.white, - foregroundImage: Assets.images.profilePhoto.provider(), + foregroundImage: ResizeImage.resizeIfNeeded( + resizeWidth, + resizeHeight, + Assets.images.profilePhoto.provider(), + ), ); if (!withAdaptiveBorder) { avatar = GradientCircleContainer( @@ -233,11 +241,19 @@ class UserProfileAvatar extends StatelessWidget { errorWidget: (_, __, ___) => CircleAvatar( backgroundColor: AppColors.white, radius: radius, - foregroundImage: Assets.images.profilePhoto.provider(), + foregroundImage: ResizeImage.resizeIfNeeded( + resizeWidth, + resizeHeight, + Assets.images.profilePhoto.provider(), + ), ), imageBuilder: (context, imageProvider) => CircleAvatar( radius: radius, - backgroundImage: imageProvider, + backgroundImage: ResizeImage.resizeIfNeeded( + resizeWidth, + resizeHeight, + imageProvider, + ), ), ); if (!withAdaptiveBorder) { diff --git a/packages/instagram_blocks_ui/lib/src/widgets/media_carousel.dart b/packages/instagram_blocks_ui/lib/src/widgets/media_carousel.dart index 4952a1eb..69024bc9 100644 --- a/packages/instagram_blocks_ui/lib/src/widgets/media_carousel.dart +++ b/packages/instagram_blocks_ui/lib/src/widgets/media_carousel.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:app_ui/app_ui.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:carousel_slider/carousel_slider.dart'; @@ -202,14 +204,18 @@ class _MediaCarouselImage extends StatelessWidget { @override Widget build(BuildContext context) { - final size = (1080 * context.devicePixelRatio).round(); + final screenWidth = context.screenWidth; + final pixelRatio = context.devicePixelRatio; + + final thumbnailWidth = min((screenWidth * pixelRatio) ~/ 1, 1920); + final thumbnailHeight = min((thumbnailWidth * (16 / 9)).toInt(), 1080); return OctoImage.fromSet( key: ValueKey(media.id), + fit: settings.fit, + memCacheHeight: thumbnailHeight, + memCacheWidth: thumbnailWidth, image: CachedNetworkImageProvider(url, cacheKey: media.id), octoSet: OctoBlurHashPlaceholder(blurHash: blurHash, fit: settings.fit), - fit: settings.fit, - memCacheHeight: size, - memCacheWidth: size, ); } } diff --git a/packages/instagram_blocks_ui/lib/src/widgets/promo_floating_action.dart b/packages/instagram_blocks_ui/lib/src/widgets/promo_floating_action.dart index 0230c12f..f56ce138 100644 --- a/packages/instagram_blocks_ui/lib/src/widgets/promo_floating_action.dart +++ b/packages/instagram_blocks_ui/lib/src/widgets/promo_floating_action.dart @@ -40,6 +40,7 @@ class PromoFloatingAction extends StatelessWidget { onTap: () => launchUrl(Uri.parse(url)), child: ListTile( leading: ImageAttachmentThumbnail( + resizeHeight: 126, image: Attachment( imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Instagram_icon.png/600px-Instagram_icon.png', diff --git a/packages/instagram_blocks_ui/lib/src/widgets/shimmer_placeholder.dart b/packages/instagram_blocks_ui/lib/src/widgets/shimmer_placeholder.dart index e2bca326..ac8e6df7 100644 --- a/packages/instagram_blocks_ui/lib/src/widgets/shimmer_placeholder.dart +++ b/packages/instagram_blocks_ui/lib/src/widgets/shimmer_placeholder.dart @@ -35,29 +35,24 @@ class ShimmerPlaceholder extends StatelessWidget { final Widget? child; final PlaceholderImageBuilder? placeholderImageBuilder; - static Widget _defaultPlaceholderImage({ - required double width, - required double height, - }) => + Widget _defaultPlaceholderImage() => Assets.images.placeholder.image( - width: width, - height: height, + width: width ?? double.infinity, + height: height ?? double.infinity, fit: BoxFit.cover, ); - static Widget _defaultCircularPlaceholderImage(double radius) => CircleAvatar( + Widget _defaultCircularPlaceholderImage() => CircleAvatar( backgroundImage: Assets.images.placeholder.provider(), radius: radius, ); @override Widget build(BuildContext context) { - final width = this.width ?? double.infinity; - final height = this.height ?? double.infinity; final image = placeholderImageBuilder?.call(width, height) ?? (radius != null - ? _defaultCircularPlaceholderImage(radius!) - : _defaultPlaceholderImage(width: width, height: height)); + ? _defaultCircularPlaceholderImage() + : _defaultPlaceholderImage()); final baseColor = withAdaptiveColors ? context.customReversedAdaptiveColor( dark: this.baseColor, diff --git a/packages/posts_repository/lib/src/posts_repository.dart b/packages/posts_repository/lib/src/posts_repository.dart index 3eb03f0f..c76f6f1a 100644 --- a/packages/posts_repository/lib/src/posts_repository.dart +++ b/packages/posts_repository/lib/src/posts_repository.dart @@ -184,11 +184,10 @@ class PostsRepository implements PostsBaseRepository { ), ), media: [ - VideoMedia( + ImageMedia( id: uuid.v4(), - firstFrameUrl: '', url: - 'https://player.vimeo.com/progressive_redirect/playback/903856061/rendition/540p/file.mp4?loc=external&oauth2_token_id=1747418641&signature=1bf8c7fcb5788b45eb5b8b30519f1eb872eb5be562ef9b0e04191ee44d53acff', + 'https://images.unsplash.com/photo-1722861315999-5de71ce7cdda?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw0fHx8ZW58MHx8fHx8', ), ], caption: 'Hello world!', @@ -299,11 +298,10 @@ class PostsRepository implements PostsBaseRepository { ), ), media: [ - VideoMedia( + ImageMedia( id: uuid.v4(), - firstFrameUrl: '', url: - 'https://player.vimeo.com/progressive_redirect/playback/899246570/rendition/540p/file.mp4?loc=external&oauth2_token_id=1747418641&signature=40dde4d43100a4ef1b77b713dee18a003757a7748ffab1cfbddce2818c818283', + 'https://images.unsplash.com/photo-1722648404028-6454d2a93602?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHwxMHx8fGVufDB8fHx8fA%3D%3D', ), ], caption: 'Hello world!',