Image and Video Previews

Introduction

The Compose UI Components chat SDK supports various types of attachments by using attachment factories. The one used to display image and video attachments is called MediaAttachmentFactory and offers a degree of customization.

Video Attachment preview thumbnails are a paid feature and as such can be turned off. You can find the pricing here and the information on how to turn them off in this section.

Customization

Message List

Let’s take a look at the function designed to produce the necessary attachment factory:

public fun MediaAttachmentFactory(
    maximumNumberOfPreviewedItems: Int = 4,
    itemOverlayContent: @Composable (attachmentType: String?) -> Unit = { attachmentType ->
        if (attachmentType == AttachmentType.VIDEO) {
            DefaultItemOverlayContent()
        }
    },
    previewItemOverlayContent: @Composable (attachmentType: String?) -> Unit = { attachmentType ->
        if (attachmentType == AttachmentType.VIDEO) {
            DefaultPreviewItemOverlayContent()
        }
    },
): AttachmentFactory =
    AttachmentFactory(
        canHandle = {
            it.none { attachment ->
                !attachment.isImage() && !attachment.isVideo()
            }
        },
        previewContent = { modifier, attachments, onAttachmentRemoved ->
            MediaAttachmentPreviewContent(
                attachments = attachments,
                onAttachmentRemoved = onAttachmentRemoved,
                modifier = modifier,
                previewItemOverlayContent = previewItemOverlayContent
            )
        },
        content = @Composable { modifier, state ->
            MediaAttachmentContent(
                modifier = modifier,
                attachmentState = state,
                maximumNumberOfPreviewedItems = maximumNumberOfPreviewedItems,
                itemOverlayContent = itemOverlayContent
            )
        }
    )
  • maximumNumberOfPreviewedItems: Controls the maximum number of tiles that can be displayed when previewing attachments inside the message list. In case there are more attachments inside the message than are able to be previewed, the remaining attachment count is displayed.
  • itemOverlayContent: Overlays a UI item over the attachment preview inside the message list. By default this is used to display a play button icon over video attachments, signaling their type to the user.
  • previewItemOverlayContent: Serves the same function as itemOverlayContent, but used inside MessageComposer when selecting attachments instead.

The stock experience we provide out of the box looks like this:

Default Image and Video Attachment Previews
Default Image and Video Attachment Previews

We’ll start changing it by replacing the stock play button in favor of a flatter, semi-transparent design.

Let’s create the new button:

@Composable
private fun CustomPlayButton(modifier: Modifier) {
    Box(
        modifier = modifier,
        contentAlignment = Alignment.Center
    ) {
        Icon(
            modifier = Modifier
                .padding(2.dp)
                .fillMaxSize(0.8f),
            painter = painterResource(id = R.drawable.stream_compose_ic_play),
            tint = Color.White,
            contentDescription = null
        )
    }
}

Now that we have created our custom button, we can use the slots provided by MediaAttachmentContent to override the default play button inside both the message list and the message composer. We’ll also use the opportunity to increase the maximum number of tiles that the factory can display.

val customMediaAttachmentFactory = MediaAttachmentFactory(
    // Increase the maximum number of previewed items to 5
    maximumNumberOfPreviewedItems = 5,
    // Render a custom item above attachments inside the message list
    itemOverlayContent = { attachmentType ->
        // Apply it only to video attachments
        if (attachmentType == AttachmentType.VIDEO) {
            CustomPlayButton(
                modifier = Modifier
                    .widthIn(10.dp)
                    .padding(2.dp)
                    .background(
                        color = Color(red = 256, blue = 256, green = 256, alpha = 220),
                        shape = RoundedCornerShape(8.dp)
                    )
                    .fillMaxWidth(0.3f)
                    .aspectRatio(1.20f),
            )
        }
    },
    // Render a custom item above attachments inside the message composer
    previewItemOverlayContent = { attachmentType ->
        // Apply it only to video attachments
        if (attachmentType == AttachmentType.VIDEO) {
            CustomPlayButton(
                modifier = Modifier
                    .padding(2.dp)
                    .background(
                        color = Color(red = 256, blue = 256, green = 256, alpha = 220),
                        shape = RoundedCornerShape(8.dp)
                    )
                    .fillMaxWidth(0.35f)
                    .aspectRatio(1.20f),
            )
        }
    })

Since we only want to modify MediaAttachmentFactory and keep the rest of the default factories, we’ll copy them in the same order they are created in StreamAttachmentFactories.defaultFactories().

val attachmentFactories = listOf(
    UploadAttachmentFactory(),
    LinkAttachmentFactory(linkDescriptionMaxLines = 5),
    GiphyAttachmentFactory(),
    customMediaAttachmentFactory,
    FileAttachmentFactory(),
    UnsupportedAttachmentFactory()
)

Order of precedence matters when creating a list of attachment factories, the first one whose canHandle() returns true will render the given attachment.

Finally, let’s replace the default attachment factories:

override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
    super.onCreate(savedInstanceState, persistentState)

    setContent {
        // Replace the default attachment factories
        ChatTheme(attachmentFactories = attachmentFactories) {
            MessagesScreen(
                viewModelFactory = messageListViewModelFactory,
                onBackPressed = { finish() }
            )
        }
    }
}

And the resulting experience looks like this:

Custom Image and Video Attachment Previews
Custom Image and Video Attachment Previews

The attachment gallery is used for previewing image and video attachments. By default, this is a feature-complete Activity, which provides multiple actions to the user, such as zooming in and out, swiping between attachments, downloading or sharing them, or deleting them from the message.

By default, the gallery looks like:

Default Image and Video Attachment Gallery (Light Mode)Default Image and Video Attachment Gallery with Overview (Light Mode)Default Image and Video Attachment Gallery with Options (Light Mode)
Default Image and Video Attachment Gallery Light Mode
Default Image and Video Attachment Gallery with Overview Light Mode
Default Image and Video Attachment Gallery with Options Light Mode
Default Image and Video Attachment Gallery (Dark Mode)Default Image and Video Attachment Gallery with Overview (Dark Mode)Default Image and Video Attachment Gallery with Options (Dark Mode)
Default Image and Video Attachment Gallery Dark Mode
Default Image and Video Attachment Gallery with Overview Dark Mode
Default Image and Video Attachment Gallery with Options Dark Mode

You can easily control which of the attachment actions are available to the user via the MediaGalleryConfig configuration class. This class is passed to the ChatTheme wrapping your MessagesScreen/MessageList and it is applied to the media gallery.

The MediaGalleryConfig class has the following properties:

  • isCloseVisible: Controls whether the close button is visible or not.

  • isOptionsVisible: Controls whether the options button is visible or not.

  • isShareVisible: Controls whether the share button is visible or not.

  • isGalleryVisible: Controls whether the gallery button is visible or not.

  • optionsConfig: Controls the visibility of the options inside the options menu. This is a MediaGalleryOptionsConfig class that has the following properties:

    • isShowInChatVisible: Controls whether the “Show in chat” option is visible or not.
    • isReplyVisible: Controls whether the “Reply” option is visible or not.
    • isSaveMediaVisible: Controls whether the “Save media” option is visible or not.
    • isDeleteVisible: Controls whether the “Delete” option is visible or not.

The MediaGalleryConfig prepared as following:

ChatTheme(
    mediaGalleryConfig = MediaGalleryConfig(
        isCloseVisible = true,
        isOptionsVisible = true,
        isShareVisible = false,
        isGalleryVisible = false,
        optionsConfig = MediaGalleryOptionsConfig(
            isShowInChatVisible = true,
            isReplyVisible = true,
            isSaveMediaVisible = false,
            isDeleteVisible = false,
        )
    )
) {
    MessagesScreen(/* ... */)
}

Will hide both footer actions (share and gallery), and will hide the “Save media” and “Delete” options from the options menu:

Custom Image and Video Attachment Gallery
Custom Image and Video Attachment Gallery

If you want to fully replace the default gallery with your own, you can do so by intercepting the click handler for media attachment messages, and presenting your own gallery screen. To do that, you need to override the onMediaContentItemClick method from the StreamAttachmentFactories.defaultFactories builder:

val attachmentFactories = StreamAttachmentFactories.defaultFactories(
    onMediaContentItemClick = { launcher, message, attachmentPosition, thumbnailsEnabled, downloadUriGenerator, downloadRequestInterceptor, imageResizing, skipEnrichUrl ->
        // Present your own gallery screen
    }
)
ChatTheme(
    attachmentFactories = attachmentFactories,
) {
    MessagesScreen(/* ... */)
}

If you would like to do more fine-grained customization of the gallery, we also expose the screen-level composable that is used to display the gallery.

Conclusion

If you need to put small personal touches to how your app displays image and video attachments while expending very little time on development, the approach we’ve discussed is optimal. If you need to do large changes, remember you can always create your own attachment factory and replace the default one with it.

You can read more about custom attachment factories here.

© Getstream.io, Inc. All Rights Reserved.