From beb561cad98a47903f3131e5e59b35a641fff23f Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Fri, 27 Mar 2026 17:17:05 +0100 Subject: [PATCH 1/2] Update sizing of media attachments in messages --- .../api/stream-chat-android-compose.api | 16 ++++--- .../content/MediaAttachmentContent.kt | 44 +++++++++++------- .../ui/components/messages/MessageContent.kt | 6 ++- .../ui/messages/list/MessageContainer.kt | 1 + .../compose/ui/theme/ChatComponentFactory.kt | 1 + .../ui/theme/ChatComponentFactoryParams.kt | 3 ++ ...ntTest_single_media_attachment_content.png | Bin 15549 -> 15787 bytes 7 files changed, 47 insertions(+), 24 deletions(-) diff --git a/stream-chat-android-compose/api/stream-chat-android-compose.api b/stream-chat-android-compose/api/stream-chat-android-compose.api index bc9deeeae86..8ba1cf99b19 100644 --- a/stream-chat-android-compose/api/stream-chat-android-compose.api +++ b/stream-chat-android-compose/api/stream-chat-android-compose.api @@ -1666,7 +1666,7 @@ public final class io/getstream/chat/android/compose/ui/components/messages/Mess } public final class io/getstream/chat/android/compose/ui/components/messages/MessageContentKt { - public static final fun MessageContent (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;III)V + public static final fun MessageContent (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lio/getstream/chat/android/compose/state/messages/MessageAlignment;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;III)V } public final class io/getstream/chat/android/compose/ui/components/messages/MessageFooterKt { @@ -5729,20 +5729,22 @@ public final class io/getstream/chat/android/compose/ui/theme/MessageReactionsPi public final class io/getstream/chat/android/compose/ui/theme/MessageRegularContentParams { public static final field $stable I - public fun (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V - public synthetic fun (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Lio/getstream/chat/android/compose/state/messages/MessageAlignment;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Lio/getstream/chat/android/compose/state/messages/MessageAlignment;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/getstream/chat/android/models/Message; public final fun component2 ()Lio/getstream/chat/android/models/User; - public final fun component3 ()Lkotlin/jvm/functions/Function1; + public final fun component3 ()Lio/getstream/chat/android/compose/state/messages/MessageAlignment; public final fun component4 ()Lkotlin/jvm/functions/Function1; public final fun component5 ()Lkotlin/jvm/functions/Function1; public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun component7 ()Lkotlin/jvm/functions/Function2; - public final fun copy (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lio/getstream/chat/android/compose/ui/theme/MessageRegularContentParams; - public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/theme/MessageRegularContentParams;Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/MessageRegularContentParams; + public final fun component7 ()Lkotlin/jvm/functions/Function1; + public final fun component8 ()Lkotlin/jvm/functions/Function2; + public final fun copy (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Lio/getstream/chat/android/compose/state/messages/MessageAlignment;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lio/getstream/chat/android/compose/ui/theme/MessageRegularContentParams; + public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/theme/MessageRegularContentParams;Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Lio/getstream/chat/android/compose/state/messages/MessageAlignment;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/MessageRegularContentParams; public fun equals (Ljava/lang/Object;)Z public final fun getCurrentUser ()Lio/getstream/chat/android/models/User; public final fun getMessage ()Lio/getstream/chat/android/models/Message; + public final fun getMessageAlignment ()Lio/getstream/chat/android/compose/state/messages/MessageAlignment; public final fun getOnLinkClick ()Lkotlin/jvm/functions/Function2; public final fun getOnLongItemClick ()Lkotlin/jvm/functions/Function1; public final fun getOnMediaGalleryPreviewResult ()Lkotlin/jvm/functions/Function1; diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentContent.kt index 8c0ffb8e724..815f763b3a7 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentContent.kt @@ -64,6 +64,7 @@ import androidx.compose.ui.semantics.testTag import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import coil3.ColorImage import coil3.annotation.ExperimentalCoilApi @@ -217,7 +218,6 @@ internal fun SingleMediaAttachment( onContentItemClick: (MediaAttachmentClickData) -> Unit, overlayContent: @Composable (attachmentType: String?) -> Unit, ) { - val isVideo = attachment.isVideo() // Depending on the CDN, images might not contain their original dimensions val ratio: Float by remember(key1 = attachment.originalWidth, key2 = attachment.originalHeight) { derivedStateOf { @@ -237,10 +237,7 @@ internal fun SingleMediaAttachment( attachment = attachment, modifier = modifier .applyIf(!shouldBeFullSize) { padding(MessageStyling.messageSectionPadding) } - .size( - width = 250.dp, - height = singleMediaAttachmentHeight(isVideo, ratio), - ), + .size(singleMediaAttachmentSize(ratio)), shape = if (shouldBeFullSize) null else RoundedCornerShape(StreamTokens.radiusLg), message = message, onMediaGalleryPreviewResult = onMediaGalleryPreviewResult, @@ -639,20 +636,35 @@ public data class MediaAttachmentClickData internal constructor( ) /** - * Calculates the actual single-media attachment height, based on the configurable width, maxHeight and the aspectRatio. + * Calculates the single-media attachment size using orientation-based bounding boxes. + * + * - Landscape (ratio > 1): max 256×192 dp, fills width first. + * - Portrait (ratio < 1): max 192×256 dp, fills height first. + * - Square (ratio ≈ 1): 256×256 dp. * - * @param isVideo true if "video", false if "image". - * @param aspectRatio the desired aspect ratio. + * Media maintains its aspect ratio and scales to fit the bounding box. + * + * @param aspectRatio the width-to-height ratio of the media. */ -@Composable -private fun singleMediaAttachmentHeight(isVideo: Boolean, aspectRatio: Float): Dp { - val maxHeight = if (isVideo) { - 400.dp - } else { - 600.dp +private fun singleMediaAttachmentSize(aspectRatio: Float): DpSize = when { + aspectRatio > 1f -> { + // Landscape: bounding box 256×192 + val width = 256.dp + val height = (width / aspectRatio).coerceAtMost(192.dp) + DpSize(width, height) + } + + aspectRatio < 1f -> { + // Portrait: bounding box 192×256 + val height = 256.dp + val width = (height * aspectRatio).coerceAtMost(192.dp) + DpSize(width, height) + } + + else -> { + // Square + DpSize(256.dp, 256.dp) } - val heightAccordingAspectRatio = 250.dp / aspectRatio - return minOf(heightAccordingAspectRatio, maxHeight) } /** diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageContent.kt index 21be92c01d9..14761b448f3 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageContent.kt @@ -46,6 +46,7 @@ import io.getstream.chat.android.client.utils.message.isDeleted import io.getstream.chat.android.client.utils.message.isGiphyEphemeral import io.getstream.chat.android.compose.R import io.getstream.chat.android.compose.state.mediagallerypreview.MediaGalleryPreviewResult +import io.getstream.chat.android.compose.state.messages.MessageAlignment import io.getstream.chat.android.compose.state.messages.attachments.AttachmentState import io.getstream.chat.android.compose.ui.theme.AudioRecordAttachmentContentParams import io.getstream.chat.android.compose.ui.theme.ChatTheme @@ -92,6 +93,7 @@ public fun MessageContent( onGiphyActionClick: (GiphyAction) -> Unit = {}, onQuotedMessageClick: (Message) -> Unit = {}, onUserMentionClick: (User) -> Unit = {}, + messageAlignment: MessageAlignment = MessageAlignment.Start, onLinkClick: ((Message, String) -> Unit)? = null, onMediaGalleryPreviewResult: (MediaGalleryPreviewResult?) -> Unit = {}, giphyEphemeralContent: @Composable () -> Unit = { @@ -117,6 +119,7 @@ public fun MessageContent( params = MessageRegularContentParams( message = message, currentUser = currentUser, + messageAlignment = messageAlignment, onLongItemClick = onLongItemClick, onMediaGalleryPreviewResult = onMediaGalleryPreviewResult, onQuotedMessageClick = onQuotedMessageClick, @@ -180,6 +183,7 @@ internal fun DefaultMessageDeletedContent( internal fun DefaultMessageContent( message: Message, currentUser: User?, + messageAlignment: MessageAlignment = MessageAlignment.Start, onLongItemClick: (Message) -> Unit, onMediaGalleryPreviewResult: (MediaGalleryPreviewResult?) -> Unit = {}, onQuotedMessageClick: (Message) -> Unit, @@ -188,7 +192,7 @@ internal fun DefaultMessageContent( ) { val componentFactory = ChatTheme.componentFactory - Column { + Column(horizontalAlignment = messageAlignment.contentAlignment) { val quotedMessage = message.replyTo if (quotedMessage != null) { componentFactory.MessageQuotedContent( diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageContainer.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageContainer.kt index 8cdfc9151f1..0916d097265 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageContainer.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageContainer.kt @@ -761,6 +761,7 @@ public fun RegularMessageContent( MessageContent( message = message, currentUser = messageItem.currentUser, + messageAlignment = messageAlignment, onLongItemClick = onLongItemClick, onGiphyActionClick = onGiphyActionClick, onMediaGalleryPreviewResult = onMediaGalleryPreviewResult, diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt index 2617e162cdc..6f840eba765 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt @@ -1077,6 +1077,7 @@ public interface ChatComponentFactory { DefaultMessageContent( message = params.message, currentUser = params.currentUser, + messageAlignment = params.messageAlignment, onLongItemClick = params.onLongItemClick, onMediaGalleryPreviewResult = params.onMediaGalleryPreviewResult, onQuotedMessageClick = params.onQuotedMessageClick, diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.kt index b5731818573..d3a4d124e84 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.unit.dp import io.getstream.chat.android.compose.state.channels.list.ItemState import io.getstream.chat.android.compose.state.mediagallerypreview.MediaGalleryPreviewResult import io.getstream.chat.android.compose.state.messageoptions.MessageOptionItemState +import io.getstream.chat.android.compose.state.messages.MessageAlignment import io.getstream.chat.android.compose.state.messages.MessageReactionItemState import io.getstream.chat.android.compose.state.messages.attachments.AttachmentPickerItemState import io.getstream.chat.android.compose.state.messages.attachments.AttachmentPickerMode @@ -787,6 +788,7 @@ public data class MessageDeletedContentParams( * * @param message The message to display. * @param currentUser The currently logged in user. + * @param messageAlignment The horizontal alignment of the message in the message list. * @param onLongItemClick Action invoked when a message is long-clicked. * @param onMediaGalleryPreviewResult Action invoked with the media gallery preview result. * @param onQuotedMessageClick Action invoked when a quoted message is clicked. @@ -796,6 +798,7 @@ public data class MessageDeletedContentParams( public data class MessageRegularContentParams( val message: Message, val currentUser: User?, + val messageAlignment: MessageAlignment, val onLongItemClick: (Message) -> Unit, val onMediaGalleryPreviewResult: (MediaGalleryPreviewResult?) -> Unit, val onQuotedMessageClick: (Message) -> Unit, diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_single_media_attachment_content.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_single_media_attachment_content.png index fc29544a3b0565828f27af44791c0b1cf7fb2818..3422ba33310799fd47dccbc63ba872d0d9ffe0c9 100644 GIT binary patch literal 15787 zcmd^m2{@JM|97+aX_%N6X%W*xAtWXH8L3eyjWsEzQTClI9m@=r%m_vH97dARW+%)< zPPCj;_ATX*BU?B*cJF;Z%kdobn|{Cd`oHgWz1Qo~bsag*^E~(c{eHfm@8|QqLysKR zUdFeMZ^@D+%MKpcXRu_+SIi|#zIeOzU*LaUD_$>KvP9ke;66AtM|}7zuVWz zgRS`vX>EgEr5!^BF0oH~+>4J7S~ls~w>uZM4UPv5fuCh#pYWF;Z*i7mf*-(p9;B3Z zz&G%8z(0_U74`%_fX}d?pFrN=tLcrrfxiU#L|Hi|e79F=SxPDVxSr9VA@FYJLSawz zk`{F2}th&N*}?9NJAOG9&RTDe5C@yLtSt^^cCatEsAr>A#{}+L|#; zWk|C_ktLIEFYT?Zxf;k`c=O->vr`{Bs+hf$Kv9aC@6Nf=CvuF$JucOjfwO03dP(z> zZL@t85_{~O`UjfDY*{Ad*#{dz`5SWnE&=HdlmM=cXHT%oz-71l5L?JZMo%+Vfk&&d^eon%dd;`cB&4;Oj*= zqmD$4{V?J9yIlW>Sr5-_f5Yg6LBOiRUMn|8{UiXs!qWn7jZ%v!(4 zc_3zW^*2YdkHv+#!^_t*FS7p-mh+-zPYjF(%~6HN+|-nPNuvJa>FuYYgTYstsJ5S3 z1T8z^Ro$J2yV0|Av(u%*W0|@eGGud;O1^ck9i{H3M&JKB@2$MQ^OvRLaR%ipcSqPg z_m)4SH>wrzIO6`~h}}1Seis_vu0ZYvwAEOzW29#xN5cDMB|L(yWwR#)w?`* z-bno`ZtqsBD`Mc!qMt?-ip?u)ly)gNj>$0#T{>S44jR_Hc@4g7Tb5H!kHi-=2(tvm zG{W9Y?7DaM&nuO1QGSZuEJ7Jbwo4>yMqIrxTrnR|xQ~uK2d}o@jYl?Zskiz&~tnBAca_)yFy`GMp!>9G%l$w6kl=az9yYP<%lP(^Gd$* z*hhkO@=qVXF07jAUzpvoB^-W~m2~*Y!uGN(VOEit%cU}Zk5;Ml0v?sBxf&Cy(b)*6 zCsqk#C!*mN@PQwx{+ihC zILk9TFyrTK8!jA~6gB&r=lbu$k*|491Uwd;9Lb+}|e099h-opHsg*zZuA$Y@Tea)XiWI^*Ft@&0749CK@i zjf9&B@+B5nZSzZ%9JAAJ?AgQrR(pB7<+g2MmP~kao|?QUZ$}h;$C#>c#(T zBtCA;i0vrHj1VKu*+1Ik&2y@6t|mrWS(n}XJMzaDsU_okTwV<%U&<*<35ha{Qi2I6 z5H3w(#L998a&b&fB=odghfl<0;_oE#<0lMM~!bUys5< z*`~gtOv<$}8^f6U&Te{m%C(D9uI_Pezd6pMUJ$W%;nBxuM8fq=T#5_c@lSbo`tK?C z8QE)j(*OJ;Eww7}ZAGK_&&^B@#hdl>Pfb30(Ijl`P`@=pcx3SK!jV7oc}QnaE00*E zI$h7Z{PIT~xIgl5rdLto8_H8ieiN<+JgM~?Nskc`l$E~%>w}wuhL=XWIo6l->n9|67=Dz1tME>jOh6?FAEMJ>#C(zK12IZk%C97 z-(YVjUx|(YT&NJf621}?3&BfuCVH)F!ebsC^r7|o)7>kIi`#L!*o7S$P)`UAx|OCC(6iH7|{o@ ztK8+qkS;h4+*-l@*3}Sqqd`} z`g`5*gV=+l_N3K=oMP<38;#rX*9mGI{azTq0Ltg z9zVKcV`5j0`VD$b+}_{hmt6}xhyaU005kYDEmwm!rG{)JOS6gh1lYvn556(5mN0{z zLsijBiVtD1?W(lpIa-=4H@XRgDy;2+LE?HKNztLYN>;S%Z&}gS?076|Z>EU7{xwY` zQ2&zE3c-=@z<0!`7h&1SbeUeHM!?0ohG47~4;~2M6?!2po)d|?2?>|_ECKA$0c3|@ z1+$Wd8-#+K5#1Pl1w(5OdUGJQMDeXA(=$xyy~*K;V_$dq>;B-NBBY#g0*js6N5o5t zd2(<`OALf89z9RKM>pDte&|Omh|5wEQ3UbPWCuM*uoK>+0lY_XSU)4h76+7~?}6hw zyVL4Zb3?tR_R^4!TgK(E_Us{=Z&7fmE4tYQ+=2S}mST>9%2$zEX z^beX%2qYvUkkHm2?7Vgh8omH=uzQtaj^m-;9PBKJ#<%u7nLb~%iWM3eNqajdRSKCuwhvpehAcM=DAuAQL?5 zr{ZSLosTL?vRcO%W@oRP+DcsoUf6n~VHGn5!W}{Y0CWWH1xo;2sUdrdY`vJLZabO+ z+i`de23R1GhD9HUL~KE|y7J5vB)mZ4dyUYopP^JXSc}KMdLv0!P^q;RLL>Z6dq7wg zkgQl#4}2mzk1T|4ll39A5F@>ZZSD=OoNLxe+>KQn0PvjJ1C|d>+X2nOg+OOz3bBea z7yu1uotu%iVWa3_H@pPWc~1yh;%LAEH-!Pq5%>}AhY@5T(~YqTppt{bfvb<^o}UnW zdmnV0>*nazhZaHFF{hBKbR&iFLqB`?9NSQLg%+*mdaFSG*`#bOtg=(jBQ&I$8?|`0 zY|s9+TcqyD&w8nE6km*Kbq2Q7(1}`$*Atxncdtg$@F4sQFwb7Q{)w_B+#q4pq z={Dg*D41kTxn0G2Eg7~CrIC$%!_&cE61oXl6k!m$GH6$Fx2j`>H;U;_je2NXCbA-f z@OYouumqjML-Of!>cty8s;}>TypiO02NWMressbljovQh87wyy8Led3axj<-DiYiM zFIXO6Enh$#tgKUiWT#{d44O~pW(G#c-$VI9cR>~okT+AZQnI>{ZGZS( zKV=k7x3`m!Q;>68$36$Hd1%xpE0g6 zyrp#^G$2#H28Q0TS_tMSzk?_O7|UX)mvKY6B3M)dU35`Hs^U=!1NZ^~9O|r}AxH=6 zP~vX9IE7R_n;3ZcRd`q^R*Dxm0H|{=2dyq2wl!vHiUD7 z51O$k)BFc$z+#KjAwXJ#5_d9P@TjJ~*__fDJ1E!me`Pngs&A1L?7- zl_20aPMH$`Ok=G!cVz^X=m6;VE{4e4<-Uzn!ge>Ls=@0(dlD&e`mf9@yqZ0tupNZo z08&scfpi8O3QS7@&vu(#85@DoPp zwtufSMYmn+!n=J(kDjhkz5zwrt}TU)i1N2?1FHP>%q##lce5OVHE^7j4u)*_k35;3 z2%Q7{7EYs#=xy04u{RSO&maUBw15B_QQZ!bdjd2r6g+jNl!4Sa3wJ;5W`9sL4|Sb& zI~T6OKmo&OQRA?<*1!^r^m=Hu#a@1-BUFmAOr)8Ht#m!R(^37y^2mLI{I(pL5;&xtpJb^R5l0(y1~l*NK(o%iOsI~5C;D-MIV9sk2;W*VqITmidf#2D3trdVtTx)rj05=v?H5l9PMwq>dFW5) z0DHlJQ((B%j88|C5nU!2p|(ItBM=y3kBtOaxQq!sHt)4r*40Dy<|+AX{h?`g4sc|K zEq5+=@m&PXK-t-C{yw|GMDmQD)^GCXfdbsimW#hwsD?_1x|1&+tzaKtnJp<4^)hN! z(l`eA1XJ{MqC!;3KeaCEdOnHF6pl=NC>*(QjQ{bGH^{$>hzQa6KL`)?DmTQ*ju=`p zOhGxqgb5%4q00j$AdcVz_=WLKptFIHgA`&gr*V>C0YRDPr%qp}f%RTJ!iHywkEHZP|+*47Tj#CoQYIXfc?HNbd@hi}Uj1 zmgxnc)jI1FBy$`a)z(gU=>DL4$*#6j;Y|GL8t4+i+C-fpwj!V;;*8M;C~a7n-aF4% zg6R<=F-08Z$eYkMZ+F8BPu0iL*Mh7>x6T>S>N1e}UXmhXTyyXP3p{;AMRZw#e7fI{ zuy?&VK9-Q5$NHyt|16Bs_DlNVIc_nsa0|amyG_g%fK-nz%!03HGHV~ zJ~ZpSUI_Eqr=7XQE}%!)FGrZLR#&Ua7U$=bLoE&^k2qVV;-%Y;(XH)nP1er>&<+OH zIF8bXEdinG<(t)U0lnQKZjZU1An7?xE7C$s543NJVL5;%q zK|?%cY~jei`7a88hct_yZn;5sh%$`D-Ql6OO^8m6h8W)Ese63Fy1T<68X~RPX8+=} zU__q5!ExOBV3h(I{b$}=K%HFd*Z*0ZELt7?@`CNbcJdlRk7b)1RDS*Hp|}?pY~x1A zv3`ml*ubFyjwzzML~;6JPA>^P4T8jkt~|#Ic+I>Kq>;YtS+t3wL-NDvV(}E^`Q%pE zfgq*ZcaTui0o{?_j{Rba)8;#ZiiWmxydi*l;D*SC6*g#h^CnV>D~J~fV_aRQ!%SpR zG~a<*8(m{}1Av^M#K@h#Db#KGZpUGkcRU=vfe#5vTs3 zU*^b?1PLERA5zH(77KSK5(xT27=K1~2Hkjit7OCBOvI76nS%Y~^?NBMM*g!@xd3B$ z73Mfi0Hg?FAV_>%f(4=_s@lD7uHWcp$R0`;8CB?|-5A(C=!REu(;;%UkL z)f^1zZzj~)|E6LPv{5Nzx6zdK-s@!S0-8YgJm=|;zZvJ}i zyig?KGDxnvC40VB${{tStR5{M>TiMQ%wh1L=f}EuAWLB`5aFqjKEdfSqKQJf&yjR+ zN(CKhbY=l4t5~vuCKIRekk=i~1iEyr2vU=PQGR^=*1f9fC4(rGf1>B)QZovgcssCM zS>#^hJPlfmD2<>QJx+icf%^f4T2}_CCEGb(1W0R4jv^*Fmyo#F2?1nHJmY#=Ra}9i=|HTbv?kI+lun)S_;jQrPT z{b1z98`{@VFK^v!1O#MOSE5VilHH|GtJ84UEI<9oAkr-FQb9B4$z&>Ur+|IkKeipp zQoyKL>|KcuA=Dl)ZA=6G8`MgUz@$ttvs<$fGlaJk?isF)1_k{3Rk-_%IuKTIm-urA z9S2T`u)5eu;5SiyYgEa!iv#R&jp5@*-R2i<~~A?j-22!S8Lt5XA8;h%W4CJCckKG% zUoN2&sDNfH77V%P5T;V_5rC7Nu>cW*@|2|2rH2~CzzuwsfYhA3f50guY#31>G;yRQ zomT{PPC?g?k6rP?iiQsGK+~U7q&Q`q_(sC`%BSWGfIx~X5~wsG!tE4rYTRKrZ>gQg zQ1itQ!*+a`fi_=hic>x)Y;x=bVvGOLL;;t__8E8mcw)Xm$jo&kJ;brL7ptoewFZ9Z zgEb9mLM`2vwp z7O^U{wyy68PQG90G6?9cIx++X+S0qW19s&e`2?3`2+prMjHHeav-K z&&P(h4ChK46p2AaoME}ed7D<{e+&Wo;A{hO2BU1=nSmXrV3)UnISF$_c}G+U&g;X8 zyCJ~Af1FVP;Gf1$d=3U6@cw@V0|pYs`QcEqHjva+ZqMW%Ux_UW`L-eS?%moe9*AaL zbkv@U zC2doFceH(j5>a#h-KtNrA*T%;aB27#8y|3+O2Nk^xg5}c9M2#&CPF6kjJ(wNm$edH zBWr|yWxpl8Lb1loAt--*VW`c5eh8aj<_ul|e~O^{5G7<>yacN4KkJm5mIgHkH<-h= z70f9+tgma7zJ|!LK+#HyuCcY#=8}vNmcW_M`nYX?{Si(;KpU z*UA`0IKhY$@ZIED8(7#1jE~Z7koz-LNmJP=A>T$(?z*ms_0R)M0(+aS4>s90`q{}g zTrL6%7cMdXx7JmAhA``xz-0WGjfa?$@#yzA+i{^GVsgZX#k`EmLE4OPE2qZo=m4y3 z+ly*2xQv}YxHGF(urQpqu%cu-`<0-m+5!7}X#_G}kRUb^pSfsUNv4f|YId4U1;-n1 zqIMu>yd4bJ_f4}IZJRO0GTfik88G8}Y>a;@f;6WDXX~+Sv!c-^vcqUkk6F??Dc1u1 zG)anB^VvK$HuzJsxvrDL}8OteG9Y}#qS<=DTAq@@xDI>0t|B?7W zG0J5J64~QH%5Y4v7zH~hfKB#jjLk0tzgNEPpe*r?_wcKZYe!1Q->8ClER|HXsxP#2 z!ZTT^x}euR_~9^gLSDFlBLP-^KNf(qT$H+Yt-a|RPu;XVciuQg+=@>D4g&#yYt@=M zIunJ-oF3{BYkX|X%YyFGUUX0O89sFt-Z@_};TT4HY+^x|lcSEx3%~KJXP?KE$bx0}uDn;f4zeYx1Xw`!o6-Bt9tF+( zXS2Vr8sBLu-G>!0b(OAMS&cCrW14PxN}zq&c!eDPt6Ak&=NZ{D4FXd^bA95o)58^= z`3dnQey*0%iIOYR>q)9W{A;yJ7-j37ur_nYMd^`Vk}$sX56CBo<#2f#oZ)KioS$QNC=7i^Yt%X} zj2zYT^dq0bPTd7tHLHVTcQP6b0k;j^JhO%otY)vb4Oynx`G8&h7uo$ycf4tN?Pbp$ zz=6J|kcgFWQFUx^;OJ*mWO$LcS>s8U!eHfZ7S9Ezf<%YS3!N{qhdQQnmL;N%jkZJh z#J7LY?oa5cb}M{{G*wgU1&8*0c3t1@iwSNwsmt~4BWU-p@IEwVgO31bB*QubrU!k% z=X8V?8k)Z$p54)cXLlainEG9KWYdyL$p|U$@_XCWRp~SC=04VI_$nWs;6@6c_D!A0igeTslzJsB`#T^I6~)bz0V1w8a%z^nr7C zX|XdGz5RZCWMk}SeN|)W?YF`#k}R1-BF)#{XUmwn+qVfE;c`*7z46&|Ze)Ow`!4cH zOICnp#nhjovENj`x<{IuV7M56$STApx^nxpmi3ITwxG{X+!q}Nr=G$CXZtOw)CjTRvrnokeY&dpEa#_U z(RHRuQ6^ax3;9e)n@4&uDjhf4}$h`Az*Xji>S4_kCU8>-+s)*Asf=(B5x^ zRtqg#w(Ohz`*s;FTlP6~*|N{tzx)Dxa=hVj{jz167xwScHTG&7rVB?ksTOQGVku*= z^|MRMPX6|pz^Ps?H^-~#Z803H4> z!fxP);1h8se0j+xngw*Zi)rFtUS3EjhA&qVc7wis>E*)(I-c;iTL+FnH?;I}x;PX5 z_9q)@(9L{wdAk4|x*4TggktF1mtOwSYL;B?xMVedxA=!Q2%NgOpJzwQW;3}9v!k;i zqS900Qd$GjME|GUe(28G=Iox_4chh$6*+AmhDx=7U&0`RagRIA$|Ej($R=vf&_?}B z!{JpVu->h;n&~7bs7oI{%%=VzJn^8Wc*N$54Hro{C$ z&!E^eiW27@2OKj^6Whq=Buu+ znH8$EPVP7r9SmMcyY@b{D)W63Af~NdvXRDpx@B~>>u^*;5>=`8;2w1~@TzsLsN}Hx z?>Yu9gqXBSYkEDliV^i|a%S4!)Uyhf9OHDwll0FM^17A%90fGkF@|L;HiX-~@>F&= z7}N8w4u3l1JR{`md+El90iUm$YeK@ykG(A4rpsx#Aa4-!p>C%2P5GLw*V61y8CYCW zzPP7qJ+V7vt~yqFRC|7Odgu!EaouKGIcMPELTj##g8CYl?#EXJL*~8>?+BaTSmHN3 zb$S)EuWyxK%boSd4ja3u>7zgH45+ecotc@L5@%*Nd>Ah59I^Utn?%oh@UngO9odf6 zT98lR^tYM@tn`p=g17Xb8bf81cDrh>PWbHPNM|`WWJT+(+gp<3zwT%|<#x?NvqBI0 zn^M^YB67aehkdLyuB=MNNm@H0EZ#i!nu`;7MGszwE0x*&IC%ayXvwF&l#8{zbVkM zA0lVC8$4mRywyxm?%X{iL+su=Kl9z_^w(#t0~cl!#^2oBdGe17cKX5zPudic!_wl; z8|cX<<%h|i`rfFyItCnFg!G2qy_vf?4B-i{dk&i0Y#s_n=y6M@l&{as+HcZ%D-}g3 z1{by0g-?I|%DH%!k(y6V7#6L|US{f&BQ0e9+l7m(ez^MwVc%n&Thbtc=6Z5j5;0wF zZ2JS`7XJq~Zkud^%l#n z;SeYlcG!RcC6St1=G--ERk}FSbEKo+s?@t@`#$KrMZy$`UAe5O;r6DDG-`dl|Dy!- ze4OSAR?Ro&vWDjFB@j5dHdO43w8zK$8&kpMFGecXhe1gk+@(S+q3>TSJTcgu#io*p zZRX8VA$Jc4E>&nf!MBuz0@v1tedUDsA zdyW;IcgFrQ8z0CJi=E6V0`nvElG$J;+h6!iBf>1s90tSi>x_Qsv#+}dn4pqP>M z*y}PtwM#K~ThC1UdRl)!na#mM2IyB_~t1^^W%C^sR3E9Zk;m zneWK4)|+V*W0ji^ibF!hA~sOoSbuL+ZEPWQ@#x!~dwepLYeYdFpLi8+@r|%smM)wn zPLd0zV*-BcN}*zG>6r`Qyd0NP;G55G5-LW}gHj;bWK7H7)KIVwjqkF#@|RYLyfCFx zNn&m{B;jZla}#rm`UQ^b$+eWJ#5e>KTY@u#9Jkg&-+VVHGiW6GOscV2H(0F<(U^9S z5pOsgeFlP>khuqGL7CKCZ7N4|aC{^5!Sq-_Rxa2(V#lSI0vj4M9OTi}m432~tTzOgc&#BS#OwzbUKeVWKO9qD~KZ8v))St9_7Y zpPO<$TGg;^AH4Ha+!N1#s>)~@aDT*FLh!#_UsbHvZ0b0dLgUQyTG_*b&(q#ptP?R_EIOz7w`sJYO8(vx=c zqfMTF+h=B@ z;Ac!TQG0J28jxC{913Uo<-PtTe9N=!gY)vSpatO$432sPI-2+39m0C_2o`bMaY+ZF zLe;))pWV9T>(S7ClN24^VjyQsJBq-aF>P4)XM{jJ7Nkp|aW3LR3pqxkB!rc8e^n7; z`Prs^TYpQ9VWJMa<4h((SR>E!nGADM&@ZhnzA;J6$&z4+ltFhWh$?^u0)Et{`$7FTlcK zrXnKZ@HP*iLUl|_rJiPGY2tBveajb|;Lt3^8%#XgE3{^;TZ9$~A zi^b>%(JLkoZFv3aBb0d8)tl zpqU(Q$K2)GubjN`n!XhN(8k5sk9FLCG|0$70f9ggS|{6dpi*nHZ0 z35Kf|iX$Rujdj`=k8lOS5D?_zc4nsxhCp7mWuz^Z#AZ`n>Mz@Wi9{=_C`c0oM#S8F zu+ta<$blm45#2(;(r$&;<#IMz=YPx_6$_dBP?`fUoqozKjW0VKoJ5NezK|gb=?b{$ zB)~-n;#2Sj>x-ae5IX`;1yL1%<0xgn60~|IDR;BJI5ba}A>VN|aBc1TJ_@Ci(JP>P zhPjQ1*u+tBrt3l9OLL_SvbQ#c@7Jt&0bme-H4n8FM=bN3zNjiU-viDr^8 zJz2uM5K@u-T8k;~GuyjxcA%UD9~C&Q1MRr*I=Wc&{x|qw!H9XQnY^Qi>zI5UH~_yu z2k_d%KhIQGRM^1(dBy&Y-$@GnyO_%XlqCRQfH;IU6-G)wkW>h3 z0CmJZ0>(edqkuWCM4)RU_HH8$r6Vqp1066goO7xc?xJjg;OkOo>?MZg2At3(tmEW!ATtgtkP`6po0;)HY=WjyXo4KUk znCPOK6=E&Cw}l^(9Uq$gqeM|VJ(c@6>S6-qdcAvB-zuNT#+p68wg0{vc_`aXjw;eK!!TylKWF8aji#JJJrvXfFh!$XlG37k0<+ z|2!28iJx1jz3Oji8(c90FFG{_-?_CHO&>-HF!w1%%r^ z-0r*FvbQmp3^s;%yNvY5Z%uhpel?A>dK`a1Mkl_nYYno=EYMGbGd@Ez#W6+s;@6~s z)2oe+pGVBy+hZ>TN^=O+=23pa5yg~Kfv`Y-tW&tG=I7SJWobU}Wum}$DDts}9cREB zFb&A#4Iz3A98#-6M~CqQz>-mdYD~M8@tXmiXzIGbWWAQ76|o5! z2ir?|IkDp_-%(2iio45=I6NFN#VcBn073|ty$r-PR#(g(B%)Xw3^r8u3XCY?Oi;`a zg$lM7$2u~XQdmAW;@FIgv%FYDG_^Oq`#x|B8)M4JFD=_*47JxmjJIVESXZjCa*DCe z&cGY-M~?4Spv3IFr0l-9?iL&}59_jb$icv`#hm)cBmv$B|aJ5n55ui4!|4X6CBuK;8F*m7vj+l0n)jsQyt!3 z3q>vexa}ywgdJSF{AagO%UT&C$^U2TEW4LHtH^p7;5oASFy&jM%!}BGRX<#^euVHJ zX+*WLmdao~|I5I|5h}i7ER2ON9!byzs150oCspKAKNP4+tR*4BArwQ@#4@nV}MDPS6{J?L5oiL1kk0wiG3jzbqb^xf$gHCpOK@bkvAS)!Ox4%t+t^{@qWHwaJV zF|^Ao7qn>q5oS-8*hhTfe$J{#z1t`1PypDKgx8nqP!EPmgqU z0p5q!a!@oYF}i5!ylCwHpQwmj>N-kH!C4cT34|z{)Ywt*MES2sFNnqa#$R5C_3c3J zLo5p(K)%g?!$*$j9=3m`(rjbI9ckHbo-UVs^cRSJuu}~x>{mBR@_HMuPZP8@ZkhRN zg`D$&u(X$3QtocLs4u-lc6>@amNF2w1Q~WnkI+f&Z!rU|OwW{nd4cM4;P7WT{qcG^ z_fRMg>*qXh2=0SL!=mZ}P>mTPxC-}=nvrr0*d+*g0Kbt(Bk+sl=W!BHr;7iMp~eMYTcf=j5QnB~<{?VS#xDAjjnUL@O{erc z00)b@QIUBvP-tJw@y|?M;Qsy<%C;VrlfQ0PGSjB?ty zE{MktT=aNJlF&uvNqQ zz$o^_XcNf}Uy{Hn7{Z&i*m&Y?7U&A{+Jlw`Ksee}!ou;uj_rQ0q`5P+8hR8lX;H59!NDJ+4AnBWae`9fy1RPqg;>gLs zfn71jtx-S_s#>|nApO=Ap8?P83Z5azI}2DY{x??AJzkFlqzSTPe0&b4`yEBSYmVk@ zI8<*rzRQ+pssaRO8uDdF+j}X@rI2=mdpk>A3ckJ+5?wr%0Z2Npjp$d&-CK@k zW2VGUa`^V)!;Q4Q+tXjq_#_`xGLwRAX{gD8Lv4PW9t}6IO1cbcb@Mr$OB&Kk1CUTE zvbR?E$P-CY)%~5-SaEkujegoCxt%UBL|>1 z0Sx`1v>}ctkR!O-hwGqz%*DhYL+hWri9DYKl@j0#r7? z0gWGB;Z3nPenfVr5}QxxN;h0Oy-zVtnR}>KG4V;&w(d>|&TZ>@Z1{mP{b`dh`=Ss| zH#;KJNhbqACkibBArZc+`jKu~y z>#euj!CZ^Xi3gO4Fr@?D~qKX|J-%_>5tS&yn#p7`}Mb7gk=s+7s z1yX!fCr5WcVo#oQVyxsR$iuSLAhu;Bf>t!~BxAi}jj}rQV@L^xKl=2@$|ASJ+}1D! zP)iWcO;Q|tC(eW;?0PiRF#JAB)gEU?nA?R^T%VyU3rafJ@y6Q1@i+;*OJv3qqP0u8 zERG(1WaTZT3K%~Ca|Bq|6DJ%NPk^%{ikzJ~U$Eu>!ztRvUZ~OI`aM$qCib|c zmE9$(R<4%7=5;{d3K_cmL<1}Av;tGFjrO&jwMw7-Q?d`ittKFKf`=nHcts1?qL?@` z0S7v>SOd5K$VQ~4CkQqa?Y(ZKt;J1mWfk#Q$HxO!c!K+AOpjrx@Pj9Xj_u6k;NND@VruDlnynqkRe5(V)S3BMd;KDzs0=NNy3Ub zX#QX(E6>>i6bGkM(UBHtaz)i11DLoS>m8ZzN~87stL7YQEgNt0tfH4~rMO-po-!|D zCszIbR~pf&I~$&{2NMtAPnx(S8oK!DLCl(K3DzF8F+B9AgTvMxRfzDAk(y!PAxpHl z>^Yh{cX^#@0R0%R!gWn?BX-#Q9_cD*zHIX9J!2i!CT@FuJ_-z*vlHrX{}Xo9X{N|T zy*@!%i9A>UBR$rty34hkg{oJ+_z(7Y^w?Uq%2gOLp_)(p_AlXYVVhb5G_Fv~2oPzs zack*}Z`b}s;|iMZ=LtlptLis3AGO`YB}FO-o7Wt1-SBk&KtI?FFwZ)Y@(9rxeXc*Hz ziFQD}TkaFCv!?%C(K8|M;Q(=i=bPCN@ndm`j%Q$~0 z3L4!?Ie-nivGM+4a4?r_(WEZ<|}tA~63f zSa80*@Ek_L7w5_S$Ntpgj#Q((w2MadB>N{sKh-G@;QDhpllCYCg?$d52iU5ua=;=n zVA_jg7g&ayL8t%Hf=sPHaZTk?0~T^?_QxBg7oOM8-yH^#Fw!d<%cowixn0uSpF9Hn zy*=JZUnE5pojHtiy5IaR%`%mMN6fqC!81nB=1=1cE{ZsKGC;A zQUa+}Ha#89L*Tg%=D`lLKI{pG@&(`V-hc9pbxDJrfzE%P`$AI82d~2E*c% z<__?bksUe*!irU{u<35L3b2?Hm>(L-APSWPM#&%>5aysBR@`-%T01_rirJiHHA>d< zfm?=H@r~uKB}cjveQq639JO-oT#G3(-(DMbzr>RYcZbmZ&*>$eY`HfnLrrUnJvaBz z?kSgRoXYC&fJZjyg0Zd`2a7x%POX~0mI0n^d1*KD3_F}NKKWkWv;B5j!V_zLb{snF ziWNS}C^vxB`aL-^e+~J3Tlhf|J5no=kgL?H z4DHp#9p<_l_hplZjK>8Eg*P5E$;Nb zXAey$1DhxV43%dO&t0ZgY{&u+qOi)qGf>a21nD25liG4DF48h2ml^~fy5<20)S~M~4(QofLhHeJw zMc7DV?uS#XzG3Kro^mhN&fWSKkHAWn6vgsdm^)AIGL7rv^c?j>PjFa0%!6W@kZ&r^ zbf(f@-r77V_7?S?NJcEL$$_SOv-PfD-;tclIW@C+K0X`CXN0 ztu%A0u(9-=>hZ4cEml%e;;`xU$T5*tgiXKFC@-p>imxls2^YK2V%d9U1B#VNyZ8FO zt+cVs95`Q3;8+jl6O|g!9$%a5Q_+&$X6cWLb;`}}2h0u^&dz%o$r9zEAy<-|b6P_5 zvE9jGcCR|gTffV)GjK%*s3&_U)*AU8SI0YqC+2^gpBSo5n7F&izRDm58EgYnQFgWm z?l!Hkie?91$wCZL7Pc=$3kXWBr+%FekAJW=zm(2+hcN-trXtx-jY zQCOQKOG2D%&5AAPRdKIVlu8)?v&$~v&00BwO9s400;AfxayRw`I#q6>b@_EwU0)c? zDjykrS6V`3dD!}_ak!cnxfSwC!}AyJyfgwAD{8-Bu)oOr^es8!UAy(8`Aj9?^hXFtta~5z+@*QmioQ zgXxSb)Qk?nXE&beaL4UOQ&Up|#@o>Eq Date: Tue, 31 Mar 2026 10:15:12 +0200 Subject: [PATCH 2/2] Set original dimensions for media attachments on resolution --- .../internal/AttachmentStorageHelper.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/AttachmentStorageHelper.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/AttachmentStorageHelper.kt index a7aac6652dd..afbfba5b66b 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/AttachmentStorageHelper.kt +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/AttachmentStorageHelper.kt @@ -17,13 +17,18 @@ package io.getstream.chat.android.ui.common.helper.internal import android.content.Context +import android.graphics.BitmapFactory +import android.media.MediaMetadataRetriever import android.net.Uri import androidx.annotation.WorkerThread +import io.getstream.chat.android.client.utils.attachment.isImage +import io.getstream.chat.android.client.utils.attachment.isVideo import io.getstream.chat.android.core.internal.InternalStreamChatApi import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.ui.common.helper.internal.AttachmentStorageHelper.Companion.EXTRA_SOURCE_URI import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData import io.getstream.log.taggedLogger +import java.io.File /** * Handles querying device storage for attachment metadata and converting between @@ -111,9 +116,17 @@ public class AttachmentStorageHelper( logger.w { "[resolveAttachmentFiles] Failed to resolve file for URI: $sourceUri" } return@mapNotNull null } + + val (width, height) = if (attachment.originalWidth == null && attachment.originalHeight == null) { + resolveLocalDimensions(file, attachment) + } else { + attachment.originalWidth to attachment.originalHeight + } attachment.copy( upload = file, extraData = attachment.extraData - EXTRA_SOURCE_URI, + originalWidth = width, + originalHeight = height, ) } @@ -127,6 +140,37 @@ public class AttachmentStorageHelper( public fun resolveMetadata(uris: List): List = storageHelper.getAttachmentsFromUriList(context, uris).let(attachmentFilter::filterAttachments) + @Suppress("MagicNumber") + private fun resolveLocalDimensions(file: File, attachment: Attachment): Pair = when { + attachment.isImage() -> { + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } + BitmapFactory.decodeFile(file.absolutePath, options) + val w = options.outWidth.takeIf { it > 0 } + val h = options.outHeight.takeIf { it > 0 } + w to h + } + + attachment.isVideo() -> { + val retriever = MediaMetadataRetriever() + try { + retriever.setDataSource(file.absolutePath) + val w = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) + ?.toIntOrNull()?.takeIf { it > 0 } + val h = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) + ?.toIntOrNull()?.takeIf { it > 0 } + val rotation = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) + ?.toIntOrNull() ?: 0 + if (rotation == 90 || rotation == 270) h to w else w to h + } catch (_: Exception) { + null to null + } finally { + retriever.release() + } + } + + else -> null to null + } + public companion object { /** * Key in [Attachment.extraData] holding the original content URI string