From 2c14749a18337c4fa297d870f9e160afb9b85c7d Mon Sep 17 00:00:00 2001 From: VelikovPetar Date: Mon, 23 Mar 2026 09:35:48 +0100 Subject: [PATCH 1/2] Deprecate imagePreviewUrl and use type-specific attachment URL fields Co-Authored-By: Claude --- .../ChannelMediaAttachmentsActivity.kt | 3 +- .../compose/sample/ui/chats/ChatsActivity.kt | 3 +- .../content/ImageAttachmentPreviewContent.kt | 3 +- .../content/LinkAttachmentContent.kt | 7 +- .../content/MediaAttachmentPreviewContent.kt | 5 +- .../content/MediaAttachmentQuotedContent.kt | 8 +-- .../preview/internal/MediaGalleryPage.kt | 3 +- .../ChannelMediaAttachmentsScreen.kt | 5 +- .../composer/ComposerLinkPreview.kt | 5 +- .../internal/AttachmentExtensions.kt | 22 ++++--- ...ChannelMediaAttachmentsPreviewViewModel.kt | 11 +++- .../ui/common/utils/extensions/Attachment.kt | 6 ++ .../extensions/AttachmentExtensionsTest.kt | 66 +++++++++++++++++++ .../media/ChatInfoSharedMediaFragment.kt | 3 +- .../AttachmentGalleryImagePageFragment.kt | 6 +- .../internal/AttachmentGalleryPagerAdapter.kt | 11 +++- .../AttachmentGalleryVideoPageFragment.kt | 7 +- .../internal/MediaAttachmentAdapter.kt | 21 +++--- .../feature/messages/list/MessageListView.kt | 51 +------------- .../internal/DefaultQuotedAttachmentView.kt | 3 +- .../view/internal/FileAttachmentsView.kt | 1 + .../view/internal/GiphyMediaAttachmentView.kt | 3 +- .../view/internal/LinkAttachmentView.kt | 6 +- .../view/internal/MediaAttachmentView.kt | 12 ++-- .../viewholder/impl/GiphyViewHolder.kt | 3 +- 25 files changed, 153 insertions(+), 121 deletions(-) create mode 100644 stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/extensions/AttachmentExtensionsTest.kt diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/channel/attachments/ChannelMediaAttachmentsActivity.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/channel/attachments/ChannelMediaAttachmentsActivity.kt index 8b69c6de05d..53efdb3ed38 100644 --- a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/channel/attachments/ChannelMediaAttachmentsActivity.kt +++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/channel/attachments/ChannelMediaAttachmentsActivity.kt @@ -34,7 +34,6 @@ import io.getstream.chat.android.compose.viewmodel.channel.ChannelAttachmentsVie import io.getstream.chat.android.compose.viewmodel.channel.ChannelAttachmentsViewModelFactory import io.getstream.chat.android.models.AttachmentType import io.getstream.chat.android.ui.common.feature.channel.attachments.ChannelAttachmentsViewEvent -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import kotlinx.coroutines.flow.collectLatest class ChannelMediaAttachmentsActivity : ComponentActivity() { @@ -50,7 +49,7 @@ class ChannelMediaAttachmentsActivity : ComponentActivity() { ChannelAttachmentsViewModelFactory( cid = requireNotNull(intent.getStringExtra(KEY_CID)), attachmentTypes = listOf(AttachmentType.IMAGE, AttachmentType.VIDEO), - localFilter = { !it.imagePreviewUrl.isNullOrEmpty() && it.titleLink.isNullOrEmpty() }, + localFilter = { !(it.imageUrl ?: it.thumbUrl).isNullOrEmpty() && it.titleLink.isNullOrEmpty() }, ) } diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt index 89ec2360136..6fdbb4f9c89 100644 --- a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt +++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt @@ -97,7 +97,6 @@ import io.getstream.chat.android.ui.common.feature.channel.info.ChannelInfoViewE import io.getstream.chat.android.ui.common.state.channel.info.ChannelInfoViewState import io.getstream.chat.android.ui.common.state.messages.list.ChannelHeaderViewState import io.getstream.chat.android.ui.common.state.messages.list.DeletedMessageVisibility -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest @@ -577,7 +576,7 @@ class ChatsActivity : ComponentActivity() { val viewModelFactory = ChannelAttachmentsViewModelFactory( cid = cid, attachmentTypes = listOf(AttachmentType.IMAGE, AttachmentType.VIDEO), - localFilter = { !it.imagePreviewUrl.isNullOrEmpty() && it.titleLink.isNullOrEmpty() }, + localFilter = { !(it.imageUrl ?: it.thumbUrl).isNullOrEmpty() && it.titleLink.isNullOrEmpty() }, ) val viewModel = viewModel( factory = viewModelFactory, diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/ImageAttachmentPreviewContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/ImageAttachmentPreviewContent.kt index 5f9e12cc706..92773d402a6 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/ImageAttachmentPreviewContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/ImageAttachmentPreviewContent.kt @@ -42,7 +42,6 @@ import io.getstream.chat.android.compose.ui.theme.ChatTheme import io.getstream.chat.android.compose.ui.util.AsyncImagePreviewHandler import io.getstream.chat.android.compose.ui.util.StreamAsyncImage import io.getstream.chat.android.models.Attachment -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl /** * UI for currently selected image attachments, within the [MessageInput]. @@ -76,7 +75,7 @@ private fun ImageAttachmentPreviewContentItem( attachment: Attachment, onAttachmentRemoved: (Attachment) -> Unit, ) { - val data = attachment.upload ?: attachment.imagePreviewUrl + val data = attachment.upload ?: attachment.imageUrl Box( modifier = Modifier diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.kt index bfa3a3aa2bd..d61f8e6159a 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.kt @@ -67,7 +67,6 @@ import io.getstream.chat.android.compose.ui.util.AsyncImagePreviewHandler import io.getstream.chat.android.compose.ui.util.StreamAsyncImage import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.Message -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.chat.android.uiutils.extension.addSchemeToUrlIfNeeded import io.getstream.chat.android.uiutils.extension.hasLink @@ -179,8 +178,8 @@ public fun LinkAttachmentContent( onLongClick = { onLongItemClick(message) }, ), ) { - val imagePreviewUrl = attachment.imagePreviewUrl - if (imagePreviewUrl != null) { + val linkPreviewUrl = attachment.thumbUrl ?: attachment.imageUrl + if (linkPreviewUrl != null) { LinkAttachmentImagePreview(attachment, isMine) } @@ -198,7 +197,7 @@ public fun LinkAttachmentContent( @Composable private fun LinkAttachmentImagePreview(attachment: Attachment, isMine: Boolean) { - val data = attachment.imagePreviewUrl + val data = attachment.thumbUrl ?: attachment.imageUrl var maxWidth by remember { mutableStateOf(0.dp) } Box( diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentPreviewContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentPreviewContent.kt index 041e0354754..0ec4a3cd379 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentPreviewContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentPreviewContent.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil3.ColorImage import coil3.compose.LocalAsyncImagePreviewHandler +import io.getstream.chat.android.client.utils.attachment.isImage import io.getstream.chat.android.compose.ui.attachments.factory.DefaultPreviewItemOverlayContent import io.getstream.chat.android.compose.ui.components.CancelIcon import io.getstream.chat.android.compose.ui.components.composer.MessageInput @@ -45,7 +46,6 @@ import io.getstream.chat.android.compose.ui.util.AsyncImagePreviewHandler import io.getstream.chat.android.compose.ui.util.StreamAsyncImage import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.AttachmentType -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl /** * UI for currently selected image and video attachments, within the [MessageInput]. @@ -98,7 +98,8 @@ private fun MediaAttachmentPreviewItem( onAttachmentRemoved: (Attachment) -> Unit, overlayContent: @Composable (attachmentType: String?) -> Unit, ) { - val data = mediaAttachment.upload ?: mediaAttachment.imagePreviewUrl + val data = mediaAttachment.upload + ?: if (mediaAttachment.isImage()) mediaAttachment.imageUrl else mediaAttachment.thumbUrl Box( modifier = Modifier diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentQuotedContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentQuotedContent.kt index 193c4f6c1b6..c85ea30713d 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentQuotedContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentQuotedContent.kt @@ -49,7 +49,6 @@ import io.getstream.chat.android.compose.ui.util.extensions.internal.imagePrevie import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.AttachmentType import io.getstream.chat.android.ui.common.images.resizing.applyStreamCdnImageResizingIfEnabled -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import java.io.File /** @@ -77,12 +76,9 @@ public fun MediaAttachmentQuotedContent( val data = when { - isGiphy -> - attachment.imagePreviewUrl + isGiphy -> attachment.thumbUrl ?: attachment.titleLink ?: attachment.ogUrl isImageContent -> - attachment.imagePreviewUrl - ?.applyStreamCdnImageResizingIfEnabled(ChatTheme.streamCdnImageResizing) - + attachment.imageUrl?.applyStreamCdnImageResizingIfEnabled(ChatTheme.streamCdnImageResizing) else -> attachment.imagePreviewData } diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/internal/MediaGalleryPage.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/internal/MediaGalleryPage.kt index 6eb3578ca2e..386e31ddb28 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/internal/MediaGalleryPage.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/internal/MediaGalleryPage.kt @@ -65,7 +65,6 @@ import io.getstream.chat.android.compose.ui.theme.ChatTheme import io.getstream.chat.android.compose.ui.util.StreamAsyncImage import io.getstream.chat.android.compose.ui.util.clickable import io.getstream.chat.android.models.Attachment -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import kotlinx.coroutines.coroutineScope import kotlin.math.abs @@ -103,7 +102,7 @@ internal fun MediaGalleryImagePage( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center, ) { - val data = attachment.imagePreviewUrl + val data = attachment.imageUrl val context = LocalContext.current // Ensure we have a new imageRequest in case the data changes diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channel/attachments/ChannelMediaAttachmentsScreen.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channel/attachments/ChannelMediaAttachmentsScreen.kt index 8b8c92861e2..4cbb9206e62 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channel/attachments/ChannelMediaAttachmentsScreen.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channel/attachments/ChannelMediaAttachmentsScreen.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import io.getstream.chat.android.client.utils.attachment.isImage import io.getstream.chat.android.client.utils.attachment.isVideo import io.getstream.chat.android.compose.R import io.getstream.chat.android.compose.ui.components.avatar.UserAvatar @@ -50,7 +51,6 @@ import io.getstream.chat.android.compose.viewmodel.channel.ChannelAttachmentsVie import io.getstream.chat.android.previewdata.PreviewMessageData import io.getstream.chat.android.ui.common.feature.channel.attachments.ChannelAttachmentsViewAction import io.getstream.chat.android.ui.common.state.channel.attachments.ChannelAttachmentsViewState -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.result.Error /** @@ -141,7 +141,8 @@ internal fun LazyGridItemScope.ChannelMediaAttachmentsItem( item: ChannelAttachmentsViewState.Content.Item, onClick: () -> Unit, ) { - val data = item.attachment.upload ?: item.attachment.imagePreviewUrl + val data = item.attachment.upload + ?: if (item.attachment.isImage()) item.attachment.imageUrl else item.attachment.thumbUrl Box( modifier = Modifier .clickable(onClick = onClick), diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/ComposerLinkPreview.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/ComposerLinkPreview.kt index 22d66da558a..a24989db686 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/ComposerLinkPreview.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/ComposerLinkPreview.kt @@ -60,7 +60,6 @@ import io.getstream.chat.android.compose.ui.util.AsyncImagePreviewHandler import io.getstream.chat.android.compose.ui.util.StreamAsyncImage import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.LinkPreview -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.chat.android.uiutils.extension.addSchemeToUrlIfNeeded import io.getstream.log.StreamLog @@ -133,14 +132,14 @@ public fun ComposerLinkPreview( @Composable private fun ComposerLinkImagePreview(attachment: Attachment) { - val imagePreviewUrl = attachment.imagePreviewUrl ?: return + val linkPreviewUrl = attachment.thumbUrl ?: attachment.imageUrl ?: return val theme = ChatTheme.messageComposerTheme.linkPreview Box( modifier = Modifier.padding(theme.imagePadding), contentAlignment = Alignment.Center, ) { StreamAsyncImage( - data = imagePreviewUrl, + data = linkPreviewUrl, modifier = Modifier .height(theme.imageSize.height) .width(theme.imageSize.width) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/extensions/internal/AttachmentExtensions.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/extensions/internal/AttachmentExtensions.kt index c648fd921ca..8e415297201 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/extensions/internal/AttachmentExtensions.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/extensions/internal/AttachmentExtensions.kt @@ -22,20 +22,26 @@ import io.getstream.chat.android.client.utils.attachment.isVideo import io.getstream.chat.android.compose.ui.theme.ChatTheme import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.ui.common.images.resizing.applyStreamCdnImageResizingIfEnabled -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl /** * This property checks if the attachment is an image or a video with enabled thumbnails. - * If so, it returns the image preview URL (applied with Stream CDN image resizing if enabled) + * If so, it returns the appropriate URL (applied with Stream CDN image resizing if enabled) * or the upload [java.io.File] object. * Otherwise, it returns null. + * + * For image attachments, [Attachment.imageUrl] is used. + * For video attachments when thumbnails are enabled, [Attachment.thumbUrl] is used. */ @get:Composable internal val Attachment.imagePreviewData: Any? - get() = if (isImage() || (isVideo() && ChatTheme.videoThumbnailsEnabled)) { - imagePreviewUrl - ?.applyStreamCdnImageResizingIfEnabled(ChatTheme.streamCdnImageResizing) - ?: upload - } else { - null + get() = when { + isImage() -> + imageUrl + ?.applyStreamCdnImageResizingIfEnabled(ChatTheme.streamCdnImageResizing) + ?: upload + isVideo() && ChatTheme.videoThumbnailsEnabled -> + thumbUrl + ?.applyStreamCdnImageResizingIfEnabled(ChatTheme.streamCdnImageResizing) + ?: upload + else -> null } diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channel/ChannelMediaAttachmentsPreviewViewModel.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channel/ChannelMediaAttachmentsPreviewViewModel.kt index 0e99ca82159..dddf51db36c 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channel/ChannelMediaAttachmentsPreviewViewModel.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channel/ChannelMediaAttachmentsPreviewViewModel.kt @@ -25,7 +25,6 @@ import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.ui.common.feature.channel.attachments.ChannelAttachmentsViewController import io.getstream.chat.android.ui.common.utils.AttachmentConstants import io.getstream.chat.android.ui.common.utils.extensions.getDisplayableName -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.result.onErrorSuspend @@ -81,7 +80,10 @@ internal class ChannelMediaAttachmentsPreviewViewModel( } private fun startSharing(attachment: Attachment) { - logger.d { "[startSharing] mimeType: ${attachment.mimeType}, attachment: ${attachment.imagePreviewUrl}" } + logger.d { + "[startSharing] mimeType: ${attachment.mimeType}, imageUrl: ${attachment.imageUrl}, " + + "thumbUrl: ${attachment.thumbUrl}" + } if (attachment.fileSize >= AttachmentConstants.MAX_SIZE_BEFORE_DOWNLOAD_WARNING_IN_BYTES) { logger.d { "[startSharing] Attachment larger than " + @@ -111,7 +113,10 @@ internal class ChannelMediaAttachmentsPreviewViewModel( } private fun share(attachment: Attachment) { - logger.d { "[share] mimeType: ${attachment.mimeType}, attachment: ${attachment.imagePreviewUrl}" } + logger.d { + "[share] mimeType: ${attachment.mimeType}, imageUrl: ${attachment.imageUrl}, " + + "thumbUrl: ${attachment.thumbUrl}" + } _state.update { currentState -> currentState.copy( isPreparingToShare = true, diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/Attachment.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/Attachment.kt index f6ea8db0abc..ceca76ca823 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/Attachment.kt +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/Attachment.kt @@ -24,5 +24,11 @@ public fun Attachment.getDisplayableName(): String? { return StringUtils.removeTimePrefix(title ?: name ?: upload?.name, StorageHelper.TIME_FORMAT) } +@Deprecated( + message = "Use the appropriate field for your attachment type: " + + "imageUrl for image attachments, " + + "thumbUrl for video thumbnails and link/giphy previews.", + level = DeprecationLevel.WARNING, +) public val Attachment.imagePreviewUrl: String? get() = thumbUrl ?: imageUrl diff --git a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/extensions/AttachmentExtensionsTest.kt b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/extensions/AttachmentExtensionsTest.kt new file mode 100644 index 00000000000..af16f4002e9 --- /dev/null +++ b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/extensions/AttachmentExtensionsTest.kt @@ -0,0 +1,66 @@ +/* + * 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.ui.common.utils.extensions + +import io.getstream.chat.android.models.Attachment +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test + +@Suppress("DEPRECATION") +internal class AttachmentExtensionsTest { + + @Test + fun `imagePreviewUrl returns thumbUrl when both thumbUrl and imageUrl are set`() { + val attachment = Attachment( + thumbUrl = "https://cdn.example.com/thumb.jpg", + imageUrl = "https://cdn.example.com/image.jpg", + ) + + assertEquals("https://cdn.example.com/thumb.jpg", attachment.imagePreviewUrl) + } + + @Test + fun `imagePreviewUrl returns thumbUrl when imageUrl is null`() { + val attachment = Attachment( + thumbUrl = "https://cdn.example.com/thumb.jpg", + imageUrl = null, + ) + + assertEquals("https://cdn.example.com/thumb.jpg", attachment.imagePreviewUrl) + } + + @Test + fun `imagePreviewUrl returns imageUrl when thumbUrl is null`() { + val attachment = Attachment( + thumbUrl = null, + imageUrl = "https://cdn.example.com/image.jpg", + ) + + assertEquals("https://cdn.example.com/image.jpg", attachment.imagePreviewUrl) + } + + @Test + fun `imagePreviewUrl returns null when both are null`() { + val attachment = Attachment( + thumbUrl = null, + imageUrl = null, + ) + + assertNull(attachment.imagePreviewUrl) + } +} diff --git a/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/info/shared/media/ChatInfoSharedMediaFragment.kt b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/info/shared/media/ChatInfoSharedMediaFragment.kt index 49607a99b2a..4538d4b59d7 100644 --- a/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/info/shared/media/ChatInfoSharedMediaFragment.kt +++ b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/info/shared/media/ChatInfoSharedMediaFragment.kt @@ -33,7 +33,6 @@ import io.getstream.chat.android.models.AttachmentType import io.getstream.chat.android.ui.ChatUI import io.getstream.chat.android.ui.common.feature.channel.attachments.ChannelAttachmentsViewAction import io.getstream.chat.android.ui.common.state.channel.attachments.ChannelAttachmentsViewState -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.chat.android.ui.feature.gallery.AttachmentGalleryDestination import io.getstream.chat.android.ui.feature.gallery.AttachmentGalleryItem import io.getstream.chat.android.ui.viewmodel.channel.ChannelAttachmentsViewModel @@ -49,7 +48,7 @@ class ChatInfoSharedMediaFragment : Fragment() { ChannelAttachmentsViewModelFactory( cid = args.cid!!, attachmentTypes = listOf(AttachmentType.IMAGE, AttachmentType.VIDEO), - localFilter = { !it.imagePreviewUrl.isNullOrEmpty() && it.titleLink.isNullOrEmpty() }, + localFilter = { !(it.imageUrl ?: it.thumbUrl).isNullOrEmpty() && it.titleLink.isNullOrEmpty() }, ) } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryImagePageFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryImagePageFragment.kt index 5765da1a195..1b61a24cae8 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryImagePageFragment.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryImagePageFragment.kt @@ -21,8 +21,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import io.getstream.chat.android.models.Attachment -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.chat.android.ui.databinding.StreamUiItemAttachmentGalleryImageBinding import io.getstream.chat.android.ui.utils.load @@ -72,10 +70,10 @@ internal class AttachmentGalleryImagePageFragment : Fragment() { companion object { private const val ARG_IMAGE_URL = "image_url" - fun create(attachment: Attachment, imageClickListener: () -> Unit = {}): Fragment { + fun create(imageUrl: String?, imageClickListener: () -> Unit = {}): Fragment { return AttachmentGalleryImagePageFragment().apply { arguments = Bundle().apply { - putString(ARG_IMAGE_URL, attachment.imagePreviewUrl) + putString(ARG_IMAGE_URL, imageUrl) } this.imageClickListener = imageClickListener } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryPagerAdapter.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryPagerAdapter.kt index 1c1b6797a4d..8de33e7d76e 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryPagerAdapter.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryPagerAdapter.kt @@ -33,8 +33,15 @@ internal class AttachmentGalleryPagerAdapter( val attachment = getItem(position) return when (attachment.type) { - AttachmentType.IMAGE -> AttachmentGalleryImagePageFragment.create(attachment, mediaClickListener) - AttachmentType.VIDEO -> AttachmentGalleryVideoPageFragment.create(attachment, mediaClickListener) + AttachmentType.IMAGE -> AttachmentGalleryImagePageFragment.create( + imageUrl = attachment.imageUrl, + imageClickListener = mediaClickListener, + ) + AttachmentType.VIDEO -> AttachmentGalleryVideoPageFragment.create( + thumbUrl = attachment.thumbUrl, + assetUrl = attachment.assetUrl, + imageClickListener = mediaClickListener, + ) else -> throw Throwable("Unsupported attachment type") } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt index df93ebd267a..4e531a67691 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt @@ -37,7 +37,6 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.source.MediaSource import androidx.media3.ui.PlayerView -import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.ui.ChatUI import io.getstream.chat.android.ui.R import io.getstream.chat.android.ui.databinding.StreamUiItemAttachmentGalleryVideoBinding @@ -260,11 +259,11 @@ internal class AttachmentGalleryVideoPageFragment : Fragment() { private const val CONTROLLER_SHOW_TIMEOUT = 2000 - fun create(attachment: Attachment, imageClickListener: () -> Unit = {}): Fragment { + fun create(thumbUrl: String?, assetUrl: String?, imageClickListener: () -> Unit = {}): Fragment { return AttachmentGalleryVideoPageFragment().apply { arguments = Bundle().apply { - putString(ARG_THUMB_URL, attachment.thumbUrl) - putString(ARG_ASSET_URL, attachment.assetUrl) + putString(ARG_THUMB_URL, thumbUrl) + putString(ARG_ASSET_URL, assetUrl) } this.imageClickListener = imageClickListener } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/overview/internal/MediaAttachmentAdapter.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/overview/internal/MediaAttachmentAdapter.kt index 86f50774a96..a6224952d52 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/overview/internal/MediaAttachmentAdapter.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/overview/internal/MediaAttachmentAdapter.kt @@ -27,7 +27,6 @@ import io.getstream.chat.android.client.utils.attachment.isVideo import io.getstream.chat.android.models.AttachmentType import io.getstream.chat.android.ui.ChatUI import io.getstream.chat.android.ui.common.images.resizing.applyStreamCdnImageResizingIfEnabled -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.chat.android.ui.databinding.StreamUiItemMediaAttachmentBinding import io.getstream.chat.android.ui.feature.gallery.AttachmentGalleryItem import io.getstream.chat.android.ui.feature.gallery.MediaAttachmentGridViewStyle @@ -94,14 +93,17 @@ internal class MediaAttachmentAdapter( val shouldLoadImage = attachmentGalleryItem.attachment.isImage() || (attachmentGalleryItem.attachment.isVideo() && ChatUI.videoThumbnailsEnabled) + val imageData = if (shouldLoadImage) { + val attachment = attachmentGalleryItem.attachment + val url = if (attachment.isImage()) attachment.imageUrl else attachment.thumbUrl + url?.applyStreamCdnImageResizingIfEnabled( + streamCdnImageResizing = ChatUI.streamCdnImageResizing, + ) + } else { + null + } binding.mediaImageView.load( - data = if (shouldLoadImage) { - attachmentGalleryItem.attachment.imagePreviewUrl?.applyStreamCdnImageResizingIfEnabled( - streamCdnImageResizing = ChatUI.streamCdnImageResizing, - ) - } else { - null - }, + data = imageData, placeholderDrawable = if (!isVideoAttachment) { style.imagePlaceholder } else { @@ -186,7 +188,8 @@ internal class MediaAttachmentAdapter( private object AttachmentGalleryItemDiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: AttachmentGalleryItem, newItem: AttachmentGalleryItem): Boolean { - return oldItem.attachment.imagePreviewUrl == newItem.attachment.imagePreviewUrl && + return (oldItem.attachment.imageUrl ?: oldItem.attachment.thumbUrl) == + (newItem.attachment.imageUrl ?: newItem.attachment.thumbUrl) && oldItem.createdAt == newItem.createdAt } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListView.kt index 2a2558c6ad5..0dc98d4f09d 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListView.kt @@ -74,59 +74,12 @@ import io.getstream.chat.android.ui.common.state.messages.UnblockUser import io.getstream.chat.android.ui.common.state.messages.list.DeletedMessageVisibility import io.getstream.chat.android.ui.common.state.messages.list.GiphyAction import io.getstream.chat.android.ui.common.state.messages.list.ModeratedMessageOption -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.chat.android.ui.databinding.StreamUiMessageListViewBinding import io.getstream.chat.android.ui.feature.gallery.AttachmentGalleryActivity import io.getstream.chat.android.ui.feature.gallery.AttachmentGalleryDestination import io.getstream.chat.android.ui.feature.gallery.AttachmentGalleryItem import io.getstream.chat.android.ui.feature.gallery.toAttachment import io.getstream.chat.android.ui.feature.messages.dialog.ModeratedMessageDialogFragment -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.AttachmentDownloadHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.BottomEndRegionReachedHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.ConfirmDeleteMessageHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.ConfirmFlagMessageHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.CustomActionHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.EndRegionReachedHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.ErrorEventHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.FlagMessageResultHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.GiphySendHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.LastMessageReadHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessageDeleteHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessageEditHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessageFlagHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessageListItemTransformer -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessageMarkAsUnreadHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessagePinHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessageReactionHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessageReplyHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessageRetryHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessageUnpinHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessageUserBlockHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.MessageUserUnblockHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.ModeratedMessageOptionHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnAttachmentClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnAttachmentDownloadClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnEnterThreadListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnGiphySendListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnLinkClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnMentionClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnMessageClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnMessageLongClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnMessageRetryListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnModeratedMessageLongClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnPollCloseClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnPollOptionClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnReactionViewClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnReplyMessageClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnShowAllPollOptionClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnThreadClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnTranslatedLabelClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnUserClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnUserReactionClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnViewPollResultClickListener -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OpenThreadHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.ThreadStartHandler -import io.getstream.chat.android.ui.feature.messages.list.MessageListView.ToggleOriginalTextHandler import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListItem import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListItemViewHolderFactory import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListListenerContainerImpl @@ -540,7 +493,7 @@ public class MessageListView : ConstraintLayout { } if (attachment.isGiphy()) { - val url = attachment.imagePreviewUrl ?: attachment.titleLink ?: attachment.ogUrl + val url = attachment.thumbUrl ?: attachment.titleLink ?: attachment.ogUrl if (url != null) { ChatUI.navigator.navigate(WebLinkDestination(context, url)) @@ -551,7 +504,7 @@ public class MessageListView : ConstraintLayout { val filteredAttachments = message.attachments .filter { ( - it.isImage() && !it.imagePreviewUrl.isNullOrEmpty() || + it.isImage() && !it.imageUrl.isNullOrEmpty() || it.isVideo() && !it.assetUrl.isNullOrEmpty() ) && !it.hasLink() diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/DefaultQuotedAttachmentView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/DefaultQuotedAttachmentView.kt index 9234812d657..416c5429074 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/DefaultQuotedAttachmentView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/DefaultQuotedAttachmentView.kt @@ -24,7 +24,6 @@ import io.getstream.chat.android.models.AttachmentType import io.getstream.chat.android.ui.ChatUI import io.getstream.chat.android.ui.common.images.internal.StreamImageLoader import io.getstream.chat.android.ui.common.images.resizing.applyStreamCdnImageResizingIfEnabled -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.chat.android.ui.feature.messages.list.DefaultQuotedAttachmentViewStyle import io.getstream.chat.android.ui.utils.extensions.createStreamThemeWrapper import io.getstream.chat.android.ui.utils.load @@ -82,7 +81,7 @@ internal class DefaultQuotedAttachmentView : AppCompatImageView { when (attachment.type) { AttachmentType.FILE, AttachmentType.VIDEO, AttachmentType.AUDIO_RECORDING -> loadAttachmentThumb(attachment) AttachmentType.IMAGE -> showAttachmentThumb( - attachment.imagePreviewUrl?.applyStreamCdnImageResizingIfEnabled(ChatUI.streamCdnImageResizing), + attachment.imageUrl?.applyStreamCdnImageResizingIfEnabled(ChatUI.streamCdnImageResizing), ) AttachmentType.GIPHY -> showAttachmentThumb(attachment.thumbUrl) else -> showAttachmentThumb(attachment.image) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/FileAttachmentsView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/FileAttachmentsView.kt index 7aad0926e4e..adfc87abae1 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/FileAttachmentsView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/FileAttachmentsView.kt @@ -171,6 +171,7 @@ private class FileAttachmentsAdapter( style, ) } + else -> StreamUiItemFileAttachmentBinding .inflate(parent.streamThemeInflater, parent, false) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/GiphyMediaAttachmentView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/GiphyMediaAttachmentView.kt index a98fe697b2f..efe99235b48 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/GiphyMediaAttachmentView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/GiphyMediaAttachmentView.kt @@ -28,7 +28,6 @@ import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.ui.common.utils.GiphyInfo import io.getstream.chat.android.ui.common.utils.GiphyInfoType import io.getstream.chat.android.ui.common.utils.GiphySizingMode -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.chat.android.ui.common.utils.giphyInfo import io.getstream.chat.android.ui.databinding.StreamUiGiphyMediaAttachmentViewBinding import io.getstream.chat.android.ui.feature.messages.list.adapter.view.GiphyMediaAttachmentViewStyle @@ -96,7 +95,7 @@ public class GiphyMediaAttachmentView : ConstraintLayout { val giphyInfo = attachment.giphyInfo(giphyType) val url = giphyInfo?.url ?: attachment.let { - it.imagePreviewUrl ?: it.titleLink ?: it.ogUrl + it.thumbUrl ?: it.titleLink ?: it.ogUrl } ?: return if (style.sizingMode == GiphySizingMode.ADAPTIVE) { diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/LinkAttachmentView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/LinkAttachmentView.kt index 4c2f240b0e6..2b7090a2070 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/LinkAttachmentView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/LinkAttachmentView.kt @@ -24,7 +24,6 @@ import androidx.core.view.isVisible import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.ui.R import io.getstream.chat.android.ui.common.images.internal.StreamImageLoader.ImageTransformation.RoundedCorners -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.chat.android.ui.databinding.StreamUiLinkAttachmentsViewBinding import io.getstream.chat.android.ui.feature.messages.list.MessageListItemStyle import io.getstream.chat.android.ui.font.TextStyle @@ -121,11 +120,12 @@ internal class LinkAttachmentView : FrameLayout { * Shows the attachment preview image if it is not null. */ private fun showAttachmentImage(attachment: Attachment) { - if (attachment.imagePreviewUrl != null) { + val linkPreviewUrl = attachment.thumbUrl ?: attachment.imageUrl + if (linkPreviewUrl != null) { binding.linkPreviewContainer.isVisible = true binding.linkPreviewImageView.load( - data = attachment.imagePreviewUrl, + data = linkPreviewUrl, placeholderResId = R.drawable.stream_ui_picture_placeholder, onStart = { binding.progressBar.isVisible = true }, onComplete = { binding.progressBar.isVisible = false }, diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/MediaAttachmentView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/MediaAttachmentView.kt index 38db07f0203..f0e5f723846 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/MediaAttachmentView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/MediaAttachmentView.kt @@ -29,7 +29,6 @@ import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.ui.ChatUI import io.getstream.chat.android.ui.R import io.getstream.chat.android.ui.common.images.resizing.applyStreamCdnImageResizingIfEnabled -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.chat.android.ui.databinding.StreamUiMediaAttachmentViewBinding import io.getstream.chat.android.ui.feature.messages.list.adapter.view.MediaAttachmentViewStyle import io.getstream.chat.android.ui.font.setTextStyle @@ -132,11 +131,12 @@ internal class MediaAttachmentView : ConstraintLayout { */ fun showAttachment(attachment: Attachment, andMoreCount: Int = NO_MORE_COUNT) { val url = - if (attachment.isImage() || - (attachment.isVideo() && ChatUI.videoThumbnailsEnabled && attachment.thumbUrl != null) - ) { - attachment.imagePreviewUrl?.applyStreamCdnImageResizingIfEnabled(ChatUI.streamCdnImageResizing) - ?: attachment.titleLink ?: attachment.ogUrl ?: attachment.upload ?: return + if (attachment.isImage()) { + attachment.imageUrl?.applyStreamCdnImageResizingIfEnabled(ChatUI.streamCdnImageResizing) + ?: attachment.upload ?: return + } else if (attachment.isVideo() && ChatUI.videoThumbnailsEnabled && attachment.thumbUrl != null) { + attachment.thumbUrl?.applyStreamCdnImageResizingIfEnabled(ChatUI.streamCdnImageResizing) + ?: return } else { null } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/GiphyViewHolder.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/GiphyViewHolder.kt index 7c8aae65893..ba2c28d3d67 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/GiphyViewHolder.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/GiphyViewHolder.kt @@ -23,7 +23,6 @@ import io.getstream.chat.android.ui.common.state.messages.list.CancelGiphy import io.getstream.chat.android.ui.common.state.messages.list.SendGiphy import io.getstream.chat.android.ui.common.state.messages.list.ShuffleGiphy import io.getstream.chat.android.ui.common.utils.GiphyInfoType -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl import io.getstream.chat.android.ui.common.utils.giphyInfo import io.getstream.chat.android.ui.databinding.StreamUiItemMessageGiphyBinding import io.getstream.chat.android.ui.feature.messages.list.GiphyViewHolderStyle @@ -74,7 +73,7 @@ public class GiphyViewHolder internal constructor( .firstOrNull() ?.let { val url = it.giphyInfo(GiphyInfoType.FIXED_HEIGHT)?.url ?: it.let { - it.imagePreviewUrl ?: it.titleLink ?: it.ogUrl + it.thumbUrl ?: it.titleLink ?: it.ogUrl } ?: return binding.giphyPreview.load( From 353cebcc18eea6c0a042815b7c4f058597c2095c Mon Sep 17 00:00:00 2001 From: VelikovPetar Date: Thu, 26 Mar 2026 12:28:10 +0100 Subject: [PATCH 2/2] Extract common extensions. --- .../content/LinkAttachmentContent.kt | 8 ++- .../content/MediaAttachmentQuotedContent.kt | 3 +- .../composer/ComposerLinkPreview.kt | 8 ++- .../ui/common/utils/extensions/Attachment.kt | 29 ++++++++ .../extensions/AttachmentExtensionsTest.kt | 66 +++++++++++++++++++ .../view/internal/GiphyMediaAttachmentView.kt | 5 +- .../view/internal/LinkAttachmentView.kt | 6 +- .../viewholder/impl/GiphyViewHolder.kt | 6 +- 8 files changed, 116 insertions(+), 15 deletions(-) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.kt index d61f8e6159a..8172d2c344d 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.kt @@ -67,6 +67,8 @@ import io.getstream.chat.android.compose.ui.util.AsyncImagePreviewHandler import io.getstream.chat.android.compose.ui.util.StreamAsyncImage import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.Message +import io.getstream.chat.android.ui.common.utils.extensions.linkPreviewImageUrl +import io.getstream.chat.android.ui.common.utils.extensions.linkUrl import io.getstream.chat.android.uiutils.extension.addSchemeToUrlIfNeeded import io.getstream.chat.android.uiutils.extension.hasLink @@ -136,7 +138,7 @@ public fun LinkAttachmentContent( "Missing link attachment." } - val previewUrl = attachment.titleLink ?: attachment.ogUrl + val previewUrl = attachment.linkUrl val urlWithScheme = previewUrl?.addSchemeToUrlIfNeeded() checkNotNull(previewUrl) { @@ -178,7 +180,7 @@ public fun LinkAttachmentContent( onLongClick = { onLongItemClick(message) }, ), ) { - val linkPreviewUrl = attachment.thumbUrl ?: attachment.imageUrl + val linkPreviewUrl = attachment.linkPreviewImageUrl if (linkPreviewUrl != null) { LinkAttachmentImagePreview(attachment, isMine) } @@ -197,7 +199,7 @@ public fun LinkAttachmentContent( @Composable private fun LinkAttachmentImagePreview(attachment: Attachment, isMine: Boolean) { - val data = attachment.thumbUrl ?: attachment.imageUrl + val data = attachment.linkPreviewImageUrl var maxWidth by remember { mutableStateOf(0.dp) } Box( diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentQuotedContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentQuotedContent.kt index c85ea30713d..6ff916a3e2f 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentQuotedContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentQuotedContent.kt @@ -49,6 +49,7 @@ import io.getstream.chat.android.compose.ui.util.extensions.internal.imagePrevie import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.AttachmentType import io.getstream.chat.android.ui.common.images.resizing.applyStreamCdnImageResizingIfEnabled +import io.getstream.chat.android.ui.common.utils.extensions.giphyFallbackPreviewUrl import java.io.File /** @@ -76,7 +77,7 @@ public fun MediaAttachmentQuotedContent( val data = when { - isGiphy -> attachment.thumbUrl ?: attachment.titleLink ?: attachment.ogUrl + isGiphy -> attachment.giphyFallbackPreviewUrl isImageContent -> attachment.imageUrl?.applyStreamCdnImageResizingIfEnabled(ChatTheme.streamCdnImageResizing) else -> attachment.imagePreviewData diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/ComposerLinkPreview.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/ComposerLinkPreview.kt index a24989db686..8b4de7c31de 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/ComposerLinkPreview.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/ComposerLinkPreview.kt @@ -60,6 +60,8 @@ import io.getstream.chat.android.compose.ui.util.AsyncImagePreviewHandler import io.getstream.chat.android.compose.ui.util.StreamAsyncImage import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.LinkPreview +import io.getstream.chat.android.ui.common.utils.extensions.linkPreviewImageUrl +import io.getstream.chat.android.ui.common.utils.extensions.linkUrl import io.getstream.chat.android.uiutils.extension.addSchemeToUrlIfNeeded import io.getstream.log.StreamLog @@ -90,7 +92,7 @@ public fun ComposerLinkPreview( val context = LocalContext.current val attachment = linkPreview.attachment - val previewUrl = attachment.titleLink ?: attachment.ogUrl + val previewUrl = attachment.linkUrl checkNotNull(previewUrl) { "Missing preview URL." @@ -132,7 +134,7 @@ public fun ComposerLinkPreview( @Composable private fun ComposerLinkImagePreview(attachment: Attachment) { - val linkPreviewUrl = attachment.thumbUrl ?: attachment.imageUrl ?: return + val linkPreviewUrl = attachment.linkPreviewImageUrl ?: return val theme = ChatTheme.messageComposerTheme.linkPreview Box( modifier = Modifier.padding(theme.imagePadding), @@ -224,7 +226,7 @@ private fun ComposerLinkCancelIcon( * @param preview The preview of the link attachment being clicked. */ private fun onLinkPreviewClick(context: Context, preview: LinkPreview) { - val previewUrl = preview.attachment.titleLink ?: preview.attachment.ogUrl + val previewUrl = preview.attachment.linkUrl checkNotNull(previewUrl) { "Missing preview URL." } diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/Attachment.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/Attachment.kt index ceca76ca823..1319bac0e93 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/Attachment.kt +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/Attachment.kt @@ -16,6 +16,7 @@ package io.getstream.chat.android.ui.common.utils.extensions +import io.getstream.chat.android.core.internal.InternalStreamChatApi import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.ui.common.helper.internal.StorageHelper import io.getstream.chat.android.ui.common.utils.StringUtils @@ -32,3 +33,31 @@ public fun Attachment.getDisplayableName(): String? { ) public val Attachment.imagePreviewUrl: String? get() = thumbUrl ?: imageUrl + +/** + * The image URL to display for link attachment previews. + * + * Prefers [Attachment.thumbUrl] over [Attachment.imageUrl]. + */ +@InternalStreamChatApi +public val Attachment.linkPreviewImageUrl: String? + get() = thumbUrl ?: imageUrl + +/** + * The navigation URL for link attachments. + * + * Prefers [Attachment.titleLink] over [Attachment.ogUrl]. + */ +@InternalStreamChatApi +public val Attachment.linkUrl: String? + get() = titleLink ?: ogUrl + +/** + * The fallback preview URL for Giphy attachments when [io.getstream.chat.android.ui.common.utils.giphyInfo] + * is not available. + * + * Falls back through [Attachment.thumbUrl], [Attachment.titleLink], and [Attachment.ogUrl]. + */ +@InternalStreamChatApi +public val Attachment.giphyFallbackPreviewUrl: String? + get() = thumbUrl ?: titleLink ?: ogUrl diff --git a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/extensions/AttachmentExtensionsTest.kt b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/extensions/AttachmentExtensionsTest.kt index af16f4002e9..b7e6d9dc2d1 100644 --- a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/extensions/AttachmentExtensionsTest.kt +++ b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/extensions/AttachmentExtensionsTest.kt @@ -63,4 +63,70 @@ internal class AttachmentExtensionsTest { assertNull(attachment.imagePreviewUrl) } + + // linkPreviewImageUrl tests + + @Test + fun `linkPreviewImageUrl returns thumbUrl when both thumbUrl and imageUrl are set`() { + val attachment = Attachment(thumbUrl = "thumb", imageUrl = "image") + assertEquals("thumb", attachment.linkPreviewImageUrl) + } + + @Test + fun `linkPreviewImageUrl returns imageUrl when thumbUrl is null`() { + val attachment = Attachment(thumbUrl = null, imageUrl = "image") + assertEquals("image", attachment.linkPreviewImageUrl) + } + + @Test + fun `linkPreviewImageUrl returns null when both are null`() { + val attachment = Attachment(thumbUrl = null, imageUrl = null) + assertNull(attachment.linkPreviewImageUrl) + } + + // linkUrl tests + + @Test + fun `linkUrl returns titleLink when both titleLink and ogUrl are set`() { + val attachment = Attachment(titleLink = "titleLink", ogUrl = "ogUrl") + assertEquals("titleLink", attachment.linkUrl) + } + + @Test + fun `linkUrl returns ogUrl when titleLink is null`() { + val attachment = Attachment(titleLink = null, ogUrl = "ogUrl") + assertEquals("ogUrl", attachment.linkUrl) + } + + @Test + fun `linkUrl returns null when both are null`() { + val attachment = Attachment(titleLink = null, ogUrl = null) + assertNull(attachment.linkUrl) + } + + // giphyFallbackPreviewUrl tests + + @Test + fun `giphyFallbackPreviewUrl returns thumbUrl when all are set`() { + val attachment = Attachment(thumbUrl = "thumb", titleLink = "titleLink", ogUrl = "ogUrl") + assertEquals("thumb", attachment.giphyFallbackPreviewUrl) + } + + @Test + fun `giphyFallbackPreviewUrl returns titleLink when thumbUrl is null`() { + val attachment = Attachment(thumbUrl = null, titleLink = "titleLink", ogUrl = "ogUrl") + assertEquals("titleLink", attachment.giphyFallbackPreviewUrl) + } + + @Test + fun `giphyFallbackPreviewUrl returns ogUrl when thumbUrl and titleLink are null`() { + val attachment = Attachment(thumbUrl = null, titleLink = null, ogUrl = "ogUrl") + assertEquals("ogUrl", attachment.giphyFallbackPreviewUrl) + } + + @Test + fun `giphyFallbackPreviewUrl returns null when all are null`() { + val attachment = Attachment(thumbUrl = null, titleLink = null, ogUrl = null) + assertNull(attachment.giphyFallbackPreviewUrl) + } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/GiphyMediaAttachmentView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/GiphyMediaAttachmentView.kt index efe99235b48..a722abaed02 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/GiphyMediaAttachmentView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/GiphyMediaAttachmentView.kt @@ -28,6 +28,7 @@ import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.ui.common.utils.GiphyInfo import io.getstream.chat.android.ui.common.utils.GiphyInfoType import io.getstream.chat.android.ui.common.utils.GiphySizingMode +import io.getstream.chat.android.ui.common.utils.extensions.giphyFallbackPreviewUrl import io.getstream.chat.android.ui.common.utils.giphyInfo import io.getstream.chat.android.ui.databinding.StreamUiGiphyMediaAttachmentViewBinding import io.getstream.chat.android.ui.feature.messages.list.adapter.view.GiphyMediaAttachmentViewStyle @@ -94,9 +95,7 @@ public class GiphyMediaAttachmentView : ConstraintLayout { ) { val giphyInfo = attachment.giphyInfo(giphyType) - val url = giphyInfo?.url ?: attachment.let { - it.thumbUrl ?: it.titleLink ?: it.ogUrl - } ?: return + val url = giphyInfo?.url ?: attachment.giphyFallbackPreviewUrl ?: return if (style.sizingMode == GiphySizingMode.ADAPTIVE) { applyAdaptiveSizing( diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/LinkAttachmentView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/LinkAttachmentView.kt index 2b7090a2070..c784a0d5bdc 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/LinkAttachmentView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/LinkAttachmentView.kt @@ -24,6 +24,8 @@ import androidx.core.view.isVisible import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.ui.R import io.getstream.chat.android.ui.common.images.internal.StreamImageLoader.ImageTransformation.RoundedCorners +import io.getstream.chat.android.ui.common.utils.extensions.linkPreviewImageUrl +import io.getstream.chat.android.ui.common.utils.extensions.linkUrl import io.getstream.chat.android.ui.databinding.StreamUiLinkAttachmentsViewBinding import io.getstream.chat.android.ui.feature.messages.list.MessageListItemStyle import io.getstream.chat.android.ui.font.TextStyle @@ -52,7 +54,7 @@ internal class LinkAttachmentView : FrameLayout { * @param style The style used for applying various things such as text styles. */ fun showLinkAttachment(attachment: Attachment, style: MessageListItemStyle) { - previewUrl = attachment.titleLink ?: attachment.ogUrl + previewUrl = attachment.linkUrl showTitle(attachment, style) showDescription(attachment, style) showLabel(attachment, style) @@ -120,7 +122,7 @@ internal class LinkAttachmentView : FrameLayout { * Shows the attachment preview image if it is not null. */ private fun showAttachmentImage(attachment: Attachment) { - val linkPreviewUrl = attachment.thumbUrl ?: attachment.imageUrl + val linkPreviewUrl = attachment.linkPreviewImageUrl if (linkPreviewUrl != null) { binding.linkPreviewContainer.isVisible = true diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/GiphyViewHolder.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/GiphyViewHolder.kt index ba2c28d3d67..da1f50a1a0f 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/GiphyViewHolder.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/GiphyViewHolder.kt @@ -23,6 +23,7 @@ import io.getstream.chat.android.ui.common.state.messages.list.CancelGiphy import io.getstream.chat.android.ui.common.state.messages.list.SendGiphy import io.getstream.chat.android.ui.common.state.messages.list.ShuffleGiphy import io.getstream.chat.android.ui.common.utils.GiphyInfoType +import io.getstream.chat.android.ui.common.utils.extensions.giphyFallbackPreviewUrl import io.getstream.chat.android.ui.common.utils.giphyInfo import io.getstream.chat.android.ui.databinding.StreamUiItemMessageGiphyBinding import io.getstream.chat.android.ui.feature.messages.list.GiphyViewHolderStyle @@ -72,9 +73,8 @@ public class GiphyViewHolder internal constructor( .attachments .firstOrNull() ?.let { - val url = it.giphyInfo(GiphyInfoType.FIXED_HEIGHT)?.url ?: it.let { - it.thumbUrl ?: it.titleLink ?: it.ogUrl - } ?: return + val url = it.giphyInfo(GiphyInfoType.FIXED_HEIGHT)?.url + ?: it.giphyFallbackPreviewUrl ?: return binding.giphyPreview.load( data = url,