From d11290e61e007697adafcdf25d92990fa17df872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Mion?= Date: Tue, 7 Jul 2026 08:49:48 +0100 Subject: [PATCH 1/4] compose: Render no-caption voice message replies on the bubble background A voice message reply with no caption showed its player in the darker attachment card, because shouldBeDisplayedAsFullSizeAttachment() returns false for replies. Gate the darker card on caption presence instead, so a no-caption reply renders the player on the message bubble background and a captioned one keeps the card. Extract the stateless AudioRecordAttachmentContentItems so the reply states can be previewed and snapshot-tested. --- .../api/stream-chat-android-compose.api | 2 + .../content/AudioRecordAttachmentContent.kt | 102 ++++++++++++++---- .../content/AttachmentsContentTest.kt | 14 +++ ...ntTest_audio_record_reply_with_caption.png | Bin 0 -> 17635 bytes ...est_audio_record_reply_without_caption.png | Bin 0 -> 17373 bytes 5 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_audio_record_reply_with_caption.png create mode 100644 stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_audio_record_reply_without_caption.png 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 6d6a9f0ea6d..f8c14832adb 100644 --- a/stream-chat-android-compose/api/stream-chat-android-compose.api +++ b/stream-chat-android-compose/api/stream-chat-android-compose.api @@ -467,6 +467,8 @@ public final class io/getstream/chat/android/compose/ui/attachments/content/Comp public final fun getLambda$-1718290425$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda$1115484442$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda$1922797615$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda$50150675$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda$928954932$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; } public final class io/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$FileAttachmentContentKt { diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt index 49f9f33bdfb..906ef029669 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt @@ -76,6 +76,7 @@ import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModel import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModelFactory import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.Attachment.UploadState +import io.getstream.chat.android.models.Message import io.getstream.chat.android.previewdata.PreviewAttachmentData import io.getstream.chat.android.ui.common.state.messages.list.AudioPlayerState import io.getstream.chat.android.ui.common.utils.MediaStringUtil @@ -94,16 +95,45 @@ public fun AudioRecordAttachmentContent( viewModelFactory: AudioPlayerViewModelFactory, ) { val viewModel = viewModel(AudioPlayerViewModel::class.java, factory = viewModelFactory) + val playerState by viewModel.state.collectAsStateWithLifecycle() + + AudioRecordAttachmentContentItems( + modifier = modifier, + attachmentState = attachmentState, + playerState = playerState, + onPlayToggleClick = viewModel::playOrPause, + onPlaySpeedClick = viewModel::changeSpeed, + onThumbDragStart = viewModel::startSeek, + onThumbDragStop = viewModel::seekTo, + ) + // Cleanup: Pause any playing tracks in onPause. + LifecycleEventEffect(event = Lifecycle.Event.ON_PAUSE) { + // Important: This effect is disposed when the parent composable is disposed. A side effect of this is that if + // the AudioRecordAttachmentContent is shown in LazyList, and is scrolled away, the effect is disposed and the + // lifecycle event is not received. Therefore, the audio needs to be paused higher in the hierarchy. + viewModel.pause() + } +} + +@Composable +private fun AudioRecordAttachmentContentItems( + attachmentState: AttachmentState, + playerState: AudioPlayerState, + modifier: Modifier = Modifier, + onPlayToggleClick: (Attachment) -> Unit = {}, + onPlaySpeedClick: (Attachment) -> Unit = {}, + onThumbDragStart: (Attachment) -> Unit = {}, + onThumbDragStop: (Attachment, Float) -> Unit = { _, _ -> }, +) { val audioRecordings = attachmentState.message.attachments .filter { attachment -> val attachmentUrl = attachment.assetUrl ?: attachment.upload?.toUri()?.toString() attachment.isAudioRecording() && attachmentUrl != null } - val playerState by viewModel.state.collectAsStateWithLifecycle() - val shouldBeFullSize = attachmentState.message.shouldBeDisplayedAsFullSizeAttachment() + val hasCaption = attachmentState.message.text.isNotEmpty() // Mirror the multi-attachment grid: a non-clickable wrapper carries the "Voice message" label // (and the sender when this is the sender-bearing attachment) so the message row announces it, // while the playback controls stay individually focusable. @@ -121,7 +151,7 @@ public fun AudioRecordAttachmentContent( ) { audioRecordings.forEach { audioRecording -> AudioRecordAttachmentContentItem( - modifier = Modifier.applyIf(!shouldBeFullSize) { + modifier = Modifier.applyIf(hasCaption) { background( MessageStyling.attachmentBackgroundColor(attachmentState.isMine), RoundedCornerShape(StreamTokens.radiusLg), @@ -130,22 +160,14 @@ public fun AudioRecordAttachmentContent( attachment = audioRecording, playerState = playerState, isMine = attachmentState.isMine, - onPlayToggleClick = viewModel::playOrPause, - onPlaySpeedClick = viewModel::changeSpeed, - onThumbDragStart = viewModel::startSeek, - onThumbDragStop = viewModel::seekTo, + onPlayToggleClick = onPlayToggleClick, + onPlaySpeedClick = onPlaySpeedClick, + onThumbDragStart = onThumbDragStart, + onThumbDragStop = onThumbDragStop, ) } } } - - // Cleanup: Pause any playing tracks in onPause. - LifecycleEventEffect(event = Lifecycle.Event.ON_PAUSE) { - // Important: This effect is disposed when the parent composable is disposed. A side effect of this is that if - // the AudioRecordAttachmentContent is shown in LazyList, and is scrolled away, the effect is disposed and the - // lifecycle event is not received. Therefore, the audio needs to be paused higher in the hierarchy. - viewModel.pause() - } } /** @@ -349,9 +371,11 @@ private fun UploadProgressIndicator( private fun progressFraction(state: UploadState.InProgress): Float = if (state.totalBytes > 0) (state.bytesUploaded / state.totalBytes.toFloat()) else 0f +private const val PreviewAudioAssetUrl = "preview://audio" + @Composable internal fun AudioRecordAttachmentContentItemPlayback() { - val previewUri = "preview://audio" + val previewUri = PreviewAudioAssetUrl AudioRecordAttachmentContentItem( attachment = PreviewAttachmentData.attachmentAudioRecording1.copy(assetUrl = previewUri), playerState = AudioPlayerState( @@ -369,7 +393,7 @@ internal fun AudioRecordAttachmentContentItemPlayback() { internal fun AudioRecordAttachmentContentItemUploading() { AudioRecordAttachmentContentItem( attachment = PreviewAttachmentData.attachmentAudioRecording1.copy( - assetUrl = "preview://audio", + assetUrl = PreviewAudioAssetUrl, uploadState = UploadState.InProgress( bytesUploaded = 2_400_000, totalBytes = 4_000_000, @@ -381,7 +405,7 @@ internal fun AudioRecordAttachmentContentItemUploading() { @Preview(showBackground = true) @Composable -internal fun AudioRecordAttachmentContentItemPreview() { +private fun AudioRecordAttachmentContentItemPreview() { ChatPreviewTheme { AudioRecordAttachmentContentItemPlayback() } @@ -389,8 +413,48 @@ internal fun AudioRecordAttachmentContentItemPreview() { @Preview(showBackground = true) @Composable -internal fun AudioRecordAttachmentContentItemUploadingPreview() { +private fun AudioRecordAttachmentContentItemUploadingPreview() { ChatPreviewTheme { AudioRecordAttachmentContentItemUploading() } } + +@Composable +internal fun AudioRecordAttachmentReplyNoCaptionContent() { + AudioRecordAttachmentReplyContent(hasCaption = false) +} + +@Composable +internal fun AudioRecordAttachmentReplyCaptionContent() { + AudioRecordAttachmentReplyContent(hasCaption = true) +} + +@Composable +private fun AudioRecordAttachmentReplyContent(hasCaption: Boolean) { + val attachment = PreviewAttachmentData.attachmentAudioRecording1.copy(assetUrl = PreviewAudioAssetUrl) + val message = Message( + text = if (hasCaption) "Caption" else "", + attachments = listOf(attachment), + replyTo = Message(), + ) + AudioRecordAttachmentContentItems( + attachmentState = AttachmentState(message = message), + playerState = AudioPlayerState(getRecordingUri = Attachment::assetUrl), + ) +} + +@Preview(showBackground = true) +@Composable +private fun AudioRecordAttachmentReplyNoCaptionPreview() { + ChatPreviewTheme { + AudioRecordAttachmentReplyNoCaptionContent() + } +} + +@Preview(showBackground = true) +@Composable +private fun AudioRecordAttachmentReplyCaptionPreview() { + ChatPreviewTheme { + AudioRecordAttachmentReplyCaptionContent() + } +} diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/attachments/content/AttachmentsContentTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/attachments/content/AttachmentsContentTest.kt index fc229d003ba..1c63e3d5002 100644 --- a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/attachments/content/AttachmentsContentTest.kt +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/attachments/content/AttachmentsContentTest.kt @@ -120,6 +120,20 @@ internal class AttachmentsContentTest : PaparazziComposeTest { } } + @Test + fun `audio record reply without caption`() { + snapshotWithDarkMode { + AudioRecordAttachmentReplyNoCaptionContent() + } + } + + @Test + fun `audio record reply with caption`() { + snapshotWithDarkMode { + AudioRecordAttachmentReplyCaptionContent() + } + } + @Test fun `unsupported attachment content`() { snapshotWithDarkMode { diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_audio_record_reply_with_caption.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_audio_record_reply_with_caption.png new file mode 100644 index 0000000000000000000000000000000000000000..d321d801108aaf8321781af1a5e71d9b0fe5a662 GIT binary patch literal 17635 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

|YuPkETnNEZ59D9u zJ(?n_fL-rNd7jZa!P8!1$Eln6));r3MsL^}p&dVx76-x2s3Q$XjZd?1T z)y72jI+@w1n@Xf`Pl-0ava&rffBDPU@brh=IM<`7kCD*zJk1<6O~CN3J;7ywnfKr2bS7w2!-0pd)Y|Me>_sKEFydiE6ND7V@KDs{2n(<%!?ZZEm((7E49aH zttCvH_TE~S2%q4V79iO~v;l0z)N}Xfb1;S`m{)sFI6I20Kk18{T~|N6H@rPe%1-Lx zsNucA;cmS>nuZ(@JKj{n!F;Y5|FRn}yQU=7`La2&)Lc6gLKl#;x!1hgLzflXPD+wZ zTI}w%qI?m4f53YIVxm|8q&c5`!wB;|#8cpvR(&y>MCU6?wQ0#(Yo*S5A&g&dn!^(M z3bi@4zBtBY{+7dL2&YGrWB-Y6-QKIx8s_WsDYh_CJ@6X)>2QCiM)P(Sw0`<|r_dTc zf-|Wrrn#${zpkF8g4LWCY z=*>x!&H4K9(uS%F;Z@FPt6z@+DB_cWIa;6}V!yzi7}#r3SvSd=71-KCESz^{a;Fs~ z-Q9i1tXp2~)&S_h7vr&a)%E@<)N;-Dpy71#4i?%8w_9fD+U@}t40EM*%b{;|lDZ;b z)f==A8Y4@t+^DWj?G>Tgy49to(b;0Tgxo*Y8bP^_*zdZyr4}sU73?wW_QrFiz%!EI z-NBc5Q?<0wE;KzF(M2;B>08UG z=J1;V*j?gmj%B1PrXzX@q&lr2 zaA;;9epl^CDse_mp2IdUMhr&gZ{|?NFRp^bIExInnM+u0Ip^xi8`bH~{PlS1z?ORW z8jTXK?QEpxXaK+()&b+2lXf9HN#4J0AzgX207M{?up9$bo~HQ~#dxtC>ybrpb)z8b z#Fmr2Lbpu9c7l3n6y@Ef+gjrtw%;D<-|nzCx`< z)%II)8?GnR%+_(h=i|HFeHj&#A z#-YxLv0l7rO4!~2h=~VWK-0D19npj)xsWdHD*U}^!$@f8GAHC)s*yBZ|1&HTf*sY| zouPLfxVg4M&sD1k`rglb33$D`q;UrsZ|~+zg!5jl+aBWtZ_=c8Z0$IMcv0P%g8c1J z5)!d6nLo%p@IK>NYz1PpvlF|XZsm1)50yBK($hr~YrE&I=|yu$JWb!EP%dI+!#g4W z^PTdt?NO|ENPv^Jj+Bc{TF4KNVMvOuv-hUHGQ#E!T4!b`W|wI%ILqoa9|`(A#lOIg z-|Vdj1&GErf=~UZlOZQneLHk+2r?5M=C?< z>X<*6##X3Jb$!KkX6HR4kd5oqHUot!16v7B9;?YNTl0#5VXNK@0(41x_iaVqP(T8q z-dL?*dxMz2xt&*-KF*1)0Cv;)+n-|5>3>=WG!ZYsvx{|)u|O#9y~(H;9UOXl+p?a% zv4n?n34;eZf*`?eGTe`k=Qszf0IzOTujL#}~c}J{;?Co360FC^LRmEBz1YYc%dvtMT<# zhGRv@qLIf~0ij1Y*tge22a4A%@hrOq%xm+i*QgR}7fop5 z0B_KDUEXGy9oqR3t@^F1p+MqdTo|z5hU+?SuhI;O5nF3a+l?-Vwsael-A!ug$k@;v z3Gs{O`VnJsaaDX`t4oEs`cf5BIW?d4BPneCA2oY1{a)|15Sty!@KkHNSRjNi}YkHA&Sbj^He@8SB#e4+tJ{uav_dxN)tG(I)UrVkY_)A>wusB= z+gbKSyNL$(pl%B_A~-SH_iR_+lMarsK`m~d9Hn0{h9`})t~j)AEkd5>bG8Nfv?3@M z%y@b(9G$5{oXo?%VPh`-$w1b_F*31UChlva4#?n^wWwn~lo-teMNXUuA|6 z9_bZ)cIrN#R?fP^7%TWXLHwcM%?K;urk1*~R^zdF?DcB^?t(@;xJ41{$R%UOZ~e{2 zIs~TI=U%^nw0D;`JAay#7K~|L*VmS>OKjm&^IlAMU@z?ew_(@SXSuzy!b5K{85ZSShZI>zUU==C{+PV?l-s^XjV-)hp{f@T{gTmqO%b3|srn z)!j(?`eI^tBWGts*I6nE*mOUa8)fm=j|aP#b(*awG%kNFk+KeP`%MSEWs9ID_tXT3 zb&~c7nsSXxd$ylC`srL>eUzIUZ{#)7T0i!uo|?NW6mO?)P?{vaflUVN2mj1zn3-7|}C@ty<3hh}>iS zU$tOe{c-U70%ms1wuyA*Yw&KldVXNqCbW+~9XD80hcTAQ@R|uD#~YW%RDRfmPCu!p zm6e^^W2fbpRfl)p&*ps8h=I?&Fp6l)k|&zz%p8X#s}4`T_YV&HMnK&{cxrgJ^}g6@ zDM7^&J8ZeL&8O5)NtkFI?p%6L&mShNem$u#3XyQw-K;d0OIL0PDq75$8o%Wg@n!J@ zuWL?_msnHZ*ncE+U^>=UUI;O?_kUC&Xg zZ=MAa*f>9zp*bmgU>-<@Q=m~!k}@GbV93{ay5_1xihYf5TEGEn7WR94g>2u4 zO*VT6%b4$f+H|1$>-L*NtRxhV1pK|~`FG56JRnt*9gWNDh-y}WNuL)l!m_wgb9FZ6 zB%xeBFoY6+!X*_uwirl!B%bfPLfy`@5Uh5zW4eb#1IP=pCQCR?s0Lw{q5i0!!93WF zjDfC&SqcW{skzvb)(*G9dK0s{T(J-dSW|*?jjdw%klj15@VDS~6FvTUfXqAsR3NZK z60cj9cZ2wOGk$nRsp|WCctp0a_}oK>SIm*oitJ3%O88Fr`IBHP)_8bFUwMBvQ6sQt z5DYt1sb-6VYQijeU#qhc#x3>_j}?)-YUEt`$!0qh`WS`Dje8dZ z#jME%mhz1(4Oxh4n|T4H4hI3EQQ()U4OEK1@1Tz@rq8^l7^vh)>Y&f0HSQd8_5I^E zMNpZnC6|789Y*s8(jkw|K<`^FeVF=%X~?j{w3@y0UU6Y2k{n=`CnE(@^DN7Mkq6X# z$eyojdg{?mAaG)7U~ENAU!<<5cog0<#2p~Oo$oW4%J1fu5etb;Kr%pM7H zMXRUiIM-n+dyM)~wxbq~kXrjALPnm?rP=R2DIo@xp2i#ktMA-6)OE4(;c^6Zj|aNI z4d%^c_QxRF^0yPa?|ZMnid_Z18BMF*7*Mazc>!J_L|;g-b<@ECp8@*LBltx47+ns) zrkqdD*(ksMO|#eS+=^T`oF8W0e6Y4L>FOn2DbRh?Q}s>4w5|{Yl|+zn=I$jc$T_JM z&m4SD{H4b>@CyNT>Bw)oUkU`lh!s!P+Hha{&bP_d>yGqPW+?8^qZW_(tH1|QeTzV@ ze#aVWEJ6Iv$E;#l@+fk<#awdemFu-^@;Zz8(0c{)O=Gy`3wkkxtaX6VEPvjS9FXdH z4t6SEwRRoZ`6=h1?e?LE<%}`cLYf-IF)X`!cF?Oz^`Z*`_jw^bWaLTObn&dAI?I^N z(-=%#@>O%){ko>)bT?!~v)iPFv_v%eAmWt)0Qou3kbY9^6}QO627gu+rq^|5vqAa4A? z*UgSGyOHKKp^lDbUF9sJ$q5EOjS(6K;6_jXh8rnHRPJc&;Y&+UW=rzcQL_^-M&`0~ zoF#RfLn-lwm~K}zy-%~=9D@k7)7J}F!j_u1dRYl6OsnN;W&|ntbjW{NLeTzDb`iZ0 zKcA=A_Cq}(u=8Ld9DtAzRXui1<;UVn&F~YWQu+_8LhR-DS$J(h0#TwNc;&t}RhQhb zo1Y~gjqOPzJ=Iy3^=`c*X*tEYFH&vw+!&3UwiZy0z?J!x#}@DXS^yd;0rGMNWNT|= z;^{XdWFP?#lDBDTv_3Z06vfD?yompWyl|o!LAo)W7&!`Rs2QrEeR9|tY?;1!%l+{5 z*TaFwH+`{W!0Zu`-J_I_Z$sbe z=}1`zPuDK(1DK>C^ab>%RGpbYEALD4Cm{e@qyW`@a{(;}D{jYXZ?71WB>?5HPFvVL zrp;n(Gze!p=Etmf9+qJ{-3y3>o`#pkR=e$#(!_hBph{|RA}HLoDOS=z44jav_t1eK z?JYsM)0*1l000%!(&ijeEHJNyQ7o|{zgX+S8B9o zay>*c+ds$Xa8TuLtB4_=_@gDat4D`NtOF~oD~7gPhcw5Bf76{Vw>f=+zMuc#iHj#> z4+4Y^9Xgbn0?id2) z`hC=$UcZm}W5B(LNv5UVnq`N|_O!}T)XH&`#nv))J7DFpk@oAm8|_j~TYc0mvz0~E z_F@N)vxO7UW2WiN?~>=PXO)o09?4UJvl^n2cQ*&}oLbf@0+lA&%i-a~6PT@ zC{b-a_JAIR{~~wd`9!@HVLc1&)!zh_wpYzL?-EkOTNfQW#t7T%(@KF*lc1&QuA}C# z1o!{k;g<#+rUXSy%q>Bokd#`WqYA&yL2N9#mu7BGsRS*ruLyx z)F(T0h&J3~VhLN=hbt=$(X57!ej_Qz&fY0F)_Eh=`PU@i?8|BD_A<4%idm%5TC$~P z&R0_nRA=!=W(`Z4=)*Bo=}mE1szwTp2}B#l9;XE>yS|@GtY?TFa-Bt`Q@{} z8DB~Q$YiO&SK{L{+Exo?v=`pwkLz*!s5#S8GY(Uu$XfASgQ1k+Yc|n!2}vtk@0i_2 zx-|)i@Rk{gjCz8(qoN~;vRl7+GZwrY3Jt;)HeB3 zAc+`nC&tR1#7fet3uOg;bxdk|N~$^VS8ypnMn&{~Pt16n&XOxEav=JnaPIZKo}QAT zm>2SU<3`BZWZ(fA*|KfTCoE4PVKvuDn6|j*uFH~DhRHMeQtaCdp;*i8&RCF$Z)k;tx_knxrrN36|R5DldgzlFs)?O=T6d$yUy{cb0M4^vYodnZ%O{peB zK`R(9-P&x$Euy?06ePSp(%Qty(n%66ocFvbFO&_u!Sq^R2<8NTi39bGdCAK4pS{Z4 zT#o4m&!9HqQNQY8?|uyR(j_Ir+qd%dHuC`i<9e;C#|`TDK7+{QS;c@(%IzAMt2fk+ z^eSsIgE~S0f~sS-=QM|20v|2An~kRQXg@E%+AFDoS~WqPyAyr`vbQglipdeh@plPh z6}aSj48oadVw@&tuFD??Ra4I`}4d`tkmoLE|yWd8D)~#=SJxpy&a|G*g(yrAT5^N_O zO%6(q9GB+p`#F(y_pbizX>;f`bd_rcW!X${DEeYIn=sEuzGWa+oMJbj>q=6cF4%|I zALbc*eLYxoRco;c2Mb`o5yogpp*1mJk9rOLJb`n zi8b37R#OfymmXMdH$I7;IoFm}CtLw^nr&4r!*-&(bl0m%-t(ZUV-N6NoHpp>iCVy~ zCgpLYC#%i)*2)X>1!++amfkOQ3@n(c;E_Iz*ZyIwp2N?9Iq4deieY_=rx^al(i(iy zX@a{d+6;@+=G6{m2Pb9Zyzw%U3UOGaQR7EgK(0-zP@NPmIl z_E4WQQqwdRk(n0_X}qfz6DkuYQ7!b1r$gNCk}P|)Vu)S0gL ziuZcDnFae1qqjjv!rym@I6Ev&wj#gQ``fR45QM+=LK*1t%XcbvNiL;$*D3g;AW6%^ z*7TE_PwqWA2%xNg6n(_JQDG=kBUUpJ*>jl>0YP3SuYLhbJ?Y^Cosy_fktha^nZ~9>#miU9|$|rrFr(+Rb<81Q7~VEj8Zn&+X0JafKJLpvevUiMFi7ue3Va z>C^4469JtWq(W?4v_OD(Jw0=$E_d6lXN>3KCXdzz?PlMQ8YjET-Z0jWduq`XXG#|q>-Hit)jToBGAu6w zKNr2y8u?mrJjAubJbH7WgVDxlo!;lXq2FFOi4a^>;m}m#UY-e9tION?Brmx&n#aU= zi7C69S)n*=s!AI-E;!=M8pZdXijE7hy;ovcEP3e6BECxHsN${bm~3EeBc*=qscF`E zi9_cI-1vd_47g@yb#8Z%0AEf^P@SFs!2D2gEJ$5?z{i(uCXbLa42C9dTC^_3odidS z01^$|7o?J%96rj*lt5J^*sX%$PUxN3N)fAY2-72bO-Zbz`bewLE+X6)c z^ogkLqGC`q8_mW?TSbV*w738Y($&|kU|2>AS*K&T!ro|W90{SEY1<;HByU9e;w$|( zw9F8ILQ=_FY?emy2Ft$Q@3cYTw)XZGlP<_?F3t|VX4C%2dGkw#;isO?#N+X3Wn}rr zP89Um#v$lRHH@_`6s;%Y{qP`wDF!?c(HP=4>OVi(^TD9|rZAu)fMEPHu{GcJYG}<~ zvz@R&PEqn0;I?6F;CYExAqtk zbSG0uxR7X1(Mas4YqeDP|cYVG{(vx9^k_^mtjN)-JGV;k;j?>ldp~@K5{n@{cg-8K^B1@58gd zrChunw!GdhwS5OTXrpydHicH_lYay1UfzMN?vk_@P#=e15wpLT(v|WBaBC7w2$PogwFTD5b z*SD?)B<^GR1=z`$TD?JSvz<*PW-P5g@6KF(Ph3@HTXyqT<`N4e&cTPV1eik3s~sv>-ZJiuJXt zm*2O}(sqrmH_4i0q({RCOf|w*Bf;G@o#nQ*n%o`Qw%&A~Y=Q2U0#KA8XTUKDDWc^m zJ^E?Z40mK#HTUI0K5<-6?0H&PEvaXwvo=VBX@&^X38H7;NRY*HmPM0OdcIvuS`npW zzLicBxdiVylW`?k9Xw4Vp#>uWcPiQ@X4VpxBi&0gU{ zBwQzN3@$fm4@##A#criqRjPAC(dmkYNzEb>aV995H0}?t&t5Jegv&ErXX|9InQ5~jY|g-+1Mhn{ET^Wi$pOX=mkA*v`Yy~QRO-(z)#8r+ZPP*f5RiDj2nm2~ z_06G{{uYrz-N8=9;oIxsT{2)AJ0yYHeq=r}+NUJTv>-wVV3Mu~T!I2-s!uyD7cVuW z=27CEx7>^xdf;d4ehOT7>*3%emDi$m{YT$D42jLeK7$filQ!GXb@=@H;0 zCaK9DS(~7MQlFYY|6uBuLKAeJQr57#-4{e*f61~<>w=YxXFMLF7O*at*`IUy8tmQB z`k{?9Zh9$HmcBqZT&wD&QI-xMzcH*V8#L{fJ+m07u;Zsf0INr!KE892reQU|ISomt zSyfp%k~F2F=uSB>q>t3KtO=Dh1Fo)^R1ww#?G%>ACTER!~1tiIet&JDH=lNSp4@QcV{#$+V*!`N)_ zFX_(vM5SfGrB3jPB>-=Z1NqGY32%EoXMo|Z11GZ24*Ud2&Cy9NGS72r3-tPMrAl9} z>#_JuWY7U*Ti|0nWw)b8HyF!E^M`SuXzgZ=?QzpVC}P3DI)SUaO2gt5Pjk@T~73bucS&dDQ=LhH=|kLr+N^ zL}X;~O1CV88&6C`sYt5@=@&T;jvfqbJpTy|w*#KaGchB!%4!WjYNKz}QuCOJ6ML>6 z@jBx(@Uzc8U0PIGYqW;l5p_xFH1-`pv)+`7#rO1vmO{nP@EB08B4u(Fsml8Sv|6!n z=oeAj9@6nlTfHC!f|mLFgjd91o~FJlY(7hY{Xo*%CLXn&GGScXdm4YecR#}r|D?xX zSi_mwYtuUf66If5e{frhn?M_aHcbm0!iKC4$jmk`Rw}fY=6;$w;qdxIzAaYZV~*cx zWw4xCx9sPv*vm-E1bt68$JZwYWJ@=nvA$ZW1Pzptl-%YzH%f9gu_|`cs8*i!)UAC^t|8BJ_BsNekLl! zogLZRbQY1Pwkc*Jt?H2!By&OVe)R?#pHIhDjcA<7QY!H&t`<`IjlDix(`OnVOW1yI0^n+q~g% z#zFnvqiqUSi3#tTZ-dRA4d$IM87*f^;jrHyO)TAv+MF-W*fqd!8<2xOGiV+q8Iw}d z8l*#%7CcRO)6D)fwBXa4vtNE6H#jXCy3~?obrhJh@i|WSU6gVty+{dM%8#%yDRxzZ*Z zG5e0FDf+q)+D*isDz2%Sa;be~{2r`7I!ABhM#-Oy%ErQL^ncqcd@)fOkYQpGN}IRX z32T6J=Q77qpod!R>P8mAug>uy$YG(*o|oVC)9<`AIv0$1Abvb@Evmft@~u7coy|Yp zeWNcY=Qg>$-eALh%F%JYo>Hl$5M#dvP{gskP&OYTv+2f01By)v9zZ(8s*B`flG?HB zN4Od1pqa;cB<#r8gK**Rh~(!~#Jzdqdj)Xm0mSHd4MrF}lVW)okuh9GzGz6ZV++*9 zH|<6AF~)j*Popd+eVcQ-G#=0H*MI#g1Bb4zyxeE|1k<%NaJq0i7UUmbd<2~r;(|%7 z^+2b;5O#`O(;}Y8^aZa@M=f3ENA%Z6Zk6#N_A?}x8B&L1L1EGZfmyL(>{z#03=xLI zWH*a<9a6JBrO~bDYqoOA4WLrgYZ?$I2X?bA1mE;LW^h8KGaFYPJd&9rnwL@n%h|Nl zLVO^^#b-*?Q{*)2wRFx4ar2D1*i3#CoO68oX{`E4{7u@@^QQwcGZ>ly=vrYzfs1i(Rk(&ea|In%V;~- zV{v)*{w2A_5K4fakiJ~9W9`GcAJ651<0wHC$d?W71TSAi#34Tr!2qE&K@4!17?#VZ zgu}n%d=PjBPvu$@SDX!{oM1(^-I( z@!RK$(su8RXno0MNcY&o0A$L%>=LgunD(TtpK39*_DC*Ol#MGX5)bj2UA3=N&Jm1& znk8fa9iKO8iVF3KJ|AV^f+Bf%PjIL5J>F|o_n<_?bN6riixJ1wwx!d^TpwgieFVeQ za1Bv**y81uXQ#|GC4{nnmA1?}=Qx(AE^SZRlhOWN{ICPC_(hW_hs{6A70aoeQFHRVtep>F>lUgXmKHX@Fcnj?Q6mYv zPJN@8gPrRK=UEXFDb|^xHTQ4oHk}e*49xKLFfB5l=BE0z%@Q|veMyTX%urrt&!RMM zj#+@#FgMw!REzWI7-m10PTOtQ_kpzc)x%?IO_0m!FC`+Esvqeg&+@*v7Cwj6v2kF< zd{aHvmeTW~VT7V(8-X#rr?pa>wMhKO-_9rdR(^QagFyHP z>Rt}S785;X+?jMpve6M~%XmafrP^tAlRIjM!22FfX5yV|yHtzcXtFg$`FpQAPoGPJ zLv_Xvh?KHgIHlW=a*`)#HE|4Uv}C1Zg>@83+N?Ln@wtc=|?}*_p@C9$&PWgNOr& zLuV1i?gG5>G})zg+dBNx;LTzM&kJ{@j+yoqrp(_*xUd#%P|e!dH0GIP_-ejFj{CC^ zq$tGvJu%d}Sa`C#2dsM;91|S+N%qc!K{6BGjM{-y8f{ zqh;y+CB0ANB?_RFW{h3Q25V__~n7M1|cZQumm*zBSM@)BKljRZx%==hV zxhhhqr6JgDTPHf5Lmm|RKd8i?(Zde4_#0pN2hjKfdiVh}ey0*YsKo^Tf3wH$9OJK~ zBmY?apK;6|SN~@m^ACvguW(E_&p!j4f79cSZ0Emi;Qz3>zDQjBKgiUdSN}h5;Qz3> z{=CC~a{&Is-{;@D^NiVv|Jf7&|M3ks3;#2K{kJCiJGT87C;mU|kpBh-r!MpSU*Y4w z?1KD@%~hav?>|A?|2yW&zI&7_N-%huvWtgDpQ}%}*vQ1lUN8K5aN580eoC`lXDkmbJV(sfi7SH=B zTUU)-FLy##-$DhMRC_G0yWu8b9WVxgD#XiO!D?PBYx(qfztJ0t4lOMkE58gp<%D;7 zNIM+7N3^pU1ZTgA`r5{=Ok0oVTi-FKJG{w`w%K9*LmgIl z!VSe{_m1tx5xb?YO~}UZ=z=9AIoFMql+)3im=)i&FXN~sgRqR*SEX#GLw-6PCm4gp z*as*;mPaXwogIpz?v1>I+KVbbNw&l_wY*e|HV&e~7!vFF@mjCIAPNTB!O4a}nd-RK z(1_|toFtC#6+aLbNzOsXX9Z+Bjz=uExUn}6EFb1tLITO)?eZo&S#)-aia6BiU0k=4 zH1-`1JYxnuak~$>q>0<>a(JvEP{k9|SlzhXEGm<&Mz#(Y;E^6k5Umw?o`1GDPp^VY z+PqYAEyJit>bBcZ=9&j4J9v1|t*6E+SK?*L(r8`T;ut1Ji<HrlP zemwS3il_*nfs`FzbvoZ|IQt6A`3_@kDUv#Yta=%-W=dkT5qpnUgno=UA(I?3i&3gX zZ9f9&(r`?=NP2O+<_|$Y3n?AA{7w2~zAsScR^%4oOTFJn&hjxnMCsRhnAge-z4%CR zZse69#^`p`7nA1k>ZmZFSm>k&KHc?~{c0Kn?PAv$MTfigV`IxBwZ-0_K5^^NJrdfo z%7Do-*6d{QQ}LLY3NWxQ+^3o?WRzs=SNgad8w_P-v}5);x7BW?$-U+{ch zWC;J~NF`L<1;2g5pDP%6 zblelNRtH%gw@O;_n`j;?-casIowt?_#yggE>VUZ@3BS6|FCNPJ5E#yMbwQ^qIl|ue z@?9(Is*zx=Qxu$$*Z`Gls@?{(A4}Apaz>=~j)gCYebrj199n!Tm7`wwGHj8Ot1gjh ze!F7w9F^kYMcT?KG|5P8oWLMzdtZI{9s@FyNnC5Xx0|?noy{9F&dEu-lcWwlA2>Nq zTeq*j>*#jq5oYTOy7!3w2-@Rw6LfN#=H*z|Y3P!3hee6EZ<`OR@oN|XvRj^U{R*F> zEi0|&JB(FA85$n@oLlFMR|hJE8W^D$y2Jn!-RzfVwcEg}N+HJh^0Axn#x{DT`poQ8 zD_`_48Y4=2tR{X$QD6$I_r<05bG@phQUuBSt&=jpl=V>+r!O?qz=0yHQXndjpraJ1 zhv@4ehuR4o(jY{c08gtu`#OJi`$P<8X`~^+`(sdI)HDt}l|A`VBk&T`$D`(Rfan&b zK}D32(i2r`-Qe65>|um5F|!~6v-^j%HE80ixuZ*U(b3fq_H3~H=IHV|`%bTw#)}}e z$HjulbL4n{*81%8FK2b`*J^7QzbqnLz$^G=8+fwTC6odQJzu7ttejrT6=J~$ldpx< z{N(pK2J@v5Ej9_^7XMZEepUw89P&G+FBd`e2jVSazmd~f7!lf%zxDHkZ?3h0ir z4NA5aN!g9W2BaKx|ctTKR1|ILs&3UbwS&{y_;oqYE*BL)P4?pI*Md z(G)zc%es&CQ9H=J_goVCR+YeJz(^lkh*isJ;`>2#&jZF1#|e6(>ZR_OLI9t>9KmbV zKjeqV|t;R6fwH2WSP|Wwu*C;?o%lpY0bQ2kYVJtJl7joR>yl2iRNfnP1 z!yJK?Eju}LFL(5g7Z-l5uI0psZXN<;HTbDW6`WbVH9kzOC^j)L zd5n5vOjZzU;5m{L9;S*|*>+=hVqZK0aqGXeLiX@_2Xi@IOn}CWFXTh#%ZFJh^QPVJ zBE*{!;7p60wMC8RgxV}Y6aE+0C##ceq@MdwJ>=!7Azl#w;XF zN;mHjxDW5w8(VjxV>~zrVeywWeqQ)U1H5F%(JB@57c$7%GQ720V`z=Z0J{S24>b4;)}QBdr|2 z)5lG!l%nI{OKJo36yr}0q0EHdHYa)R?zs{@;`YOt^=o;2C36lBOV0G^ab5?@mTZ&Z zgZX(5QEMoa>NHhn27wFQ&}yUjQ+=3n>!0Lxd_NJQX_iT6ip;q*{nBDsG~}bSGs7}( z6J#Tt)sWPjm!afr6}1^pI5Wvaad6V`Wd(xDaOK$DtFi+6-9*!%;qs9Kfmy6tiIKMz z6F6>wwc1aKCosOG!!=kf8+EcEPH>EZT(h1 z7NDZbp2rl*?AR7tJv2b94(kR zMfcREGF4Op>1$Qd$S7$z_oFLuSjkSoOCL~l4*aeU)2k|CZ84stuzZFfo(CoF>($HZ zpNf^Fe;l`IutB%Ph99Ofi*Y-O2P*r!S3D_PXTz{9l5^jGrV|SjL^!m2D)PGQz%2ICTod34}-Z-375fX3=5f?xQNjR-@P->SktJXk)z&=U4Rf)o@)AEwN} z_rr0Kx_7%(t4mXWXRilY1(E7n3F!sJ(rKscpzlk^$BV~ON|>7(ElrKEl~mPe&ShIG zg|}Q*oK5p$EjC-cXn_tHS}fpZyq?b%s<50*QPyUWTXROmM?@S$=kK%N)C*-iy##a$LLl8E4 z>r-^P$)mkb$y>31{@n6iip6WLAR}uqCM|q`UW=N^ zp{lqml7Ye}*8&Sfbuk3!s(bE@wq1SMK;#xxBlpgi$5iHWc=W3+dPpA-)gpV*f5?Vi z*aXG z9AbZ64)PNLW^zRdr@|{E zTqbV^R^1;SNU^OAaA%rWH$nTjicinBo~`!rNI8-IUgeINj#v>VyX^!O=sc%X>n+xA zBeG>BQT~ODigej&vncvlykIoZSs?qTG3VKQqsWiE>mm``Tag5g1@v=8EQo!68(8>U zPj7im9s=P1>DRPRO9?M;Pu)fp)IxwIy-Y zw^1wc&d|Gp-m=rVlVFR|~^6$&FZ3+&{!ngP4 zJ_034gn_}HdSM>e+#IZ}?}^K86pG&`vY_6f<;HW)Jn`lx`W*_#}ImC%NyeqN5}{%(O`Brul_8n!Vv5ks&Ud1 zhumHva7~Beka73}y9j@mB5)r$B>6^_ft*5m`h*phE6K?7STUqLzoENd!+i&pKEtT$ zc5Ob1_VH%8hM z6C*sS2{`8y0`6;l+aZo96Tm!!E^0OF)~%M{a>lK?qqY?Y;XcOQe4^570n)YJmWwYQ zFAi5MdUu19NG-7@3P1=kY_&jMuMyT|Wn)V9=67 zGq0x``DxCe0UT^;l?L-wNwsOA)sb-PBRF{V)TELvhx4vjFZ|Ok*}$46EIpS|(^=RO z84h&axLBZlOiU$mq^x(0G^z*9FK^`=E{A^a*lNPDy_jB{bxv5lPs#SSHYFR~ycgk( z*WJ+aAMvTF`r&n4Ew-<&LVTu5J_^OX+^nk9Jh4^84U4fG`RkW1P#IV!`@icrc8dTW zzT4VtrLqVl;+yK=f$W8JXY%{V@t{D>#^&4ouRAW9Yt>o;*-nhSUsekfv{mh+8@-t!=Z)5O<B3s#pT{ zorhiDfLi>P=Ot;n4aG>IidrtZHv0E>lkn1H6R!GlcjOmvs2m5=2B`I%08C%m9A)pi zsnsP$r{;MBGzafo_T;2B!pkV6@CxNg))eQqS`QQQMng}-{TCy z3TV~#Mi-}IiyC#XLPDmd$y-HVjx_~BRyN>~3()SETIJ^EMwjUO>oVXrHbv@r@vSra zYT3Nif0Ky-=W;@pe=%*{m_{3l7tkN{U7xn$u!wF8g9?Jmt(7^GKhySsig-{0a$&(2trz&N_3F`kM2=NhG%55;LF461x9+lNy=EPuhXA4$ zcj;LKr`(eznGW{T?`QSw_lfs5uVo25c`)QVEK)nGs90o*Drtl+`;!Qw>`Rc(%3_G? z$6!2MCF~W6iSw667FgjlZlIjNxgs;Bli}kx+0rE5UxB*rQcP9ai6VP=A6ttgmixSG zorpxwP28tzVkOn%+6xCf(^)+?o4V|(fUIfXUVZ*|a2r%(p#AfWYkNr^E$?~{M7Ae1A9j++)Y zsmuWe{z_QPP_(yV{*aU?E@D-p9Ipu8%1?n}-1F?BXlWd$fIhWcZbW;4REX}^<{o4K zy(lX_jGH8c6l6*qgHR{!m&C#@N>WpLF8wxU=^1D6)9qP|feJt)diz0AC0E2NPNI;F=1}nmGbar?HD0qaa?lGR@?OYm`Daeja*iXyj{$QWDYIq;I%!u(6bB>c2qD<{!e&q`5^RZ7~ zrB~m&)J=RJ)<}rHeh@Ge3qskxeHC=eE3{HV-*Y;x^>H7!`YEbrk|DSrqB+202K4m- zs(3PTw+)Fx?9EOur@L0RLG536HC=|D3EfO=IC^X;<#e2GLx6%{echPH7cV4}@8PEw zD7`B*!tW%v28F9@hUJ&B1WxiVVi2Xj7;htlVj)@crq5Hn*FLRONGL0vUk;i&FYmzs6%?T_q^DIfHOu>1t? z?;gKP|47>%lBA9RWx33eh2*PSA?%{y9hnL@|7O1*w=r6!XK^+jc*911xufVe67lWb z*MyOMEnhl;;8ECK+@ZN1{dJo6Ik5rbm!{m`R~!kYKx}eVk69Tla^|#lDQr+ z($i*m0?7bncIi=p6|*L!we$O>Em@8<@xBlsZa!O#TM5g?Ttd{k7zZsh*(5y9bV4+p zwY7S&C@U~M6M7jHeJ{3wU_1Fk#gc!PglmT6+gJuVVHKqcHR3wW&}Fx(0)d{G(dq!h43W=sM-((B&LPK@CEJAowh@z?z5R^4ubPh`%JkIJ z@CLi?emYnA=a$l~MkTr%J{(H8P;xxhhd`gv{fQ5#3SuJ2wVM4qoc z4?H!9&-G_9vM$1s4X)x#-XvLd!IIeqBek95O86IiEW4^7fgaQj6-qt|0_enm zVtHF(q+d?C%MA+bRj{s5V8k~?Z7$JD`{t7R@iHYjM|)FmuRelAzI``lQU5f>IO_ap z_Sq0!@vR6Wxxsq7UIKp_?2CE~$RyZod8aU+5ne%PETrn;B)Fi%gizTCfjBG2s9SU= zam%#I3O1rj&TVctOh1c1H>Y9^J85LfZ1GJY0I%EZ`)&|q*zw)%AYk5Wk&MUh9}?H_ zOF@{^rDi?dw+%NB6(in_QP#Td@gE0B3lA&542Ct?e@oS}StEIM7kAmuDk+mcIq0}2 zY1r@A!B!s2eskN%O8Vh;8l&E})>8u!h3b*`S=y2mPsuzUpk%{GY6bx*g9-p6`Qz;8 zi)*G!9Y1T{^Eg~^9*}0K$<+%}E_|`a#=q1GZ_~Q_xVt5Db0Rk6(tzveyx6g)DJ9Q* zO|#+KFFH!d4i-+5Ta!WJq9e88&fIjN(BnOq-Kg&d&L4Y2Y=R#%$;qnF?EUa|Ou|R? zn5>)p$JnFyW%jUnF^47;ig3#CjrT>`;T3i^hDKIP<8{dKrnlp( Date: Tue, 7 Jul 2026 10:27:47 +0100 Subject: [PATCH 2/4] compose: Drop section padding for no-caption voice message replies The no-caption reply player already renders on the bubble background, but it still carried the attachment-card section padding, which belongs only when the player is a card sharing the bubble with a caption. Gate the section padding on caption presence so the no-caption player uses only its own content padding, matching the design. --- .../content/AudioRecordAttachmentContent.kt | 4 +--- ...est_audio_record_reply_without_caption.png | Bin 17373 -> 17271 bytes 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt index 906ef029669..2073d2ed21f 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt @@ -71,7 +71,6 @@ import io.getstream.chat.android.compose.ui.theme.MessageStyling import io.getstream.chat.android.compose.ui.theme.StreamTokens import io.getstream.chat.android.compose.ui.util.applyIf import io.getstream.chat.android.compose.ui.util.senderAwareDescription -import io.getstream.chat.android.compose.ui.util.shouldBeDisplayedAsFullSizeAttachment import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModel import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModelFactory import io.getstream.chat.android.models.Attachment @@ -132,7 +131,6 @@ private fun AudioRecordAttachmentContentItems( attachment.isAudioRecording() && attachmentUrl != null } - val shouldBeFullSize = attachmentState.message.shouldBeDisplayedAsFullSizeAttachment() val hasCaption = attachmentState.message.text.isNotEmpty() // Mirror the multi-attachment grid: a non-clickable wrapper carries the "Voice message" label // (and the sender when this is the sender-bearing attachment) so the message row announces it, @@ -146,7 +144,7 @@ private fun AudioRecordAttachmentContentItems( }, ) { Column( - modifier = modifier.applyIf(!shouldBeFullSize) { padding(MessageStyling.messageSectionPadding) }, + modifier = modifier.applyIf(hasCaption) { padding(MessageStyling.messageSectionPadding) }, verticalArrangement = Arrangement.spacedBy(MessageStyling.sectionsDistance), ) { audioRecordings.forEach { audioRecording -> diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_audio_record_reply_without_caption.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_audio_record_reply_without_caption.png index 17b4ec5f5155e4d4d32d531d6ac5adea8f7b6700..3a2ab16778b83f0de2b3a015f644fa4f598fcf3f 100644 GIT binary patch delta 13564 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 Date: Tue, 7 Jul 2026 11:00:28 +0100 Subject: [PATCH 3/4] compose: Drop the bubble bottom spacer for no-caption voice message replies A voice recording supplies its own bottom padding, unlike media and file sections which rely on the bubble's shared bottom spacer. When a no-caption voice recording was the trailing content, the player's bottom padding and the spacer stacked and doubled the bottom inset. Skip the spacer in that case so the bubble bottom matches the design. --- .../android/compose/ui/components/messages/MessageContent.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 752cf4e0fcb..d75e59b2de6 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 @@ -303,7 +303,9 @@ internal fun DefaultMessageRegularContent( ) } - if (!info.displaysFullSizeAttachment) { + val recordingSuppliesBottomPadding = + info.hasRecordings && !info.hasUnknown && message.text.isEmpty() + if (!info.displaysFullSizeAttachment && !recordingSuppliesBottomPadding) { Spacer(Modifier.height(MessageStyling.contentPadding)) } } From b4c683c0d9a3a8323d7b83c1695e74cb28ac63a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Mion?= Date: Tue, 7 Jul 2026 11:39:18 +0100 Subject: [PATCH 4/4] compose: Add voice message reply rendering tests Render the no-caption, caption, and recording-plus-custom-attachment reply cases through MessageContainer, exercising the bubble bottom-spacer logic and the viewModel-backed audio recording content that the Paparazzi snapshots cannot reach. --- .../list/VoiceMessageReplyContentTest.kt | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/list/VoiceMessageReplyContentTest.kt diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/list/VoiceMessageReplyContentTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/list/VoiceMessageReplyContentTest.kt new file mode 100644 index 00000000000..f55832b3ab1 --- /dev/null +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/list/VoiceMessageReplyContentTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.ui.messages.list + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.getstream.chat.android.client.test.MockedChatClientTest +import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.util.extensions.toSet +import io.getstream.chat.android.models.Attachment +import io.getstream.chat.android.models.AttachmentType +import io.getstream.chat.android.models.ChannelCapabilities +import io.getstream.chat.android.models.ConnectionState +import io.getstream.chat.android.models.Message +import io.getstream.chat.android.models.User +import io.getstream.chat.android.randomUser +import io.getstream.chat.android.ui.common.state.messages.list.MessageItemState +import kotlinx.coroutines.flow.MutableStateFlow +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +@Config(sdk = [33]) +internal class VoiceMessageReplyContentTest : MockedChatClientTest { + + @get:Rule + val composeTestRule = createComposeRule() + + @Before + fun prepare() { + whenever(mockClientState.user) doReturn MutableStateFlow(randomUser()) + whenever(mockClientState.connectionState) doReturn MutableStateFlow(ConnectionState.Connected) + } + + @Test + fun `voice message reply without a caption renders`() { + setMessageContent(voiceReply(text = "")) + + composeTestRule.onNodeWithTag("Stream_MessageCell").assertIsDisplayed() + } + + @Test + fun `voice message reply with a caption renders`() { + setMessageContent(voiceReply(text = "Nice")) + + composeTestRule.onNodeWithTag("Stream_MessageCell").assertIsDisplayed() + } + + @Test + fun `voice message reply with a custom attachment renders`() { + setMessageContent(voiceReply(text = "", extra = Attachment(type = "custom"))) + + composeTestRule.onNodeWithTag("Stream_MessageCell").assertIsDisplayed() + } + + private fun voiceReply(text: String, extra: Attachment? = null) = MessageItemState( + message = Message( + id = "m", + text = text, + attachments = listOfNotNull( + Attachment(type = AttachmentType.AUDIO_RECORDING, assetUrl = "https://example.com/audio.aac"), + extra, + ), + user = User(id = "me"), + replyTo = Message(id = "q", text = "Original", user = User(id = "u2")), + ), + isMine = true, + currentUser = User(id = "me"), + ownCapabilities = ChannelCapabilities.toSet(), + ) + + private fun setMessageContent(messageItem: MessageItemState) { + composeTestRule.setContent { + ChatTheme { + MessageContainer(messageItem = messageItem, onLongItemClick = {}) + } + } + } +}