Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3245,7 +3245,7 @@ public final class io/getstream/chat/android/compose/ui/theme/ChatTheme {
}

public final class io/getstream/chat/android/compose/ui/theme/ChatThemeKt {
public static final fun ChatTheme (ZZZZZLio/getstream/chat/android/ui/common/permissions/SystemAttachmentsPickerConfig;Lio/getstream/chat/android/compose/ui/theme/StreamColors;Lio/getstream/chat/android/compose/ui/theme/StreamDimens;Lio/getstream/chat/android/compose/ui/theme/StreamTypography;Lio/getstream/chat/android/compose/ui/theme/StreamShapes;Lio/getstream/chat/android/compose/ui/theme/StreamRippleConfiguration;Lio/getstream/chat/android/ui/common/model/UserPresence;Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Ljava/util/List;Lio/getstream/chat/android/compose/ui/components/messages/factory/MessageContentFactory;Ljava/util/List;Ljava/util/List;Lio/getstream/chat/android/compose/ui/util/ReactionIconFactory;Lio/getstream/chat/android/ui/common/helper/ReactionPushEmojiFactory;Lio/getstream/chat/android/compose/ui/theme/ReactionOptionsTheme;Lio/getstream/chat/android/compose/ui/util/MessagePreviewIconFactory;Lio/getstream/chat/android/compose/ui/util/PollSwitchItemFactory;ZLio/getstream/chat/android/ui/common/helper/DateFormatter;Lio/getstream/chat/android/ui/common/helper/TimeProvider;Lio/getstream/chat/android/ui/common/helper/DurationFormatter;Lio/getstream/chat/android/ui/common/utils/ChannelNameFormatter;Lio/getstream/chat/android/compose/ui/util/MessagePreviewFormatter;Lio/getstream/chat/android/compose/ui/util/SearchResultNameFormatter;Lio/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory;Lio/getstream/chat/android/ui/common/helper/ImageHeadersProvider;Lio/getstream/chat/android/ui/common/helper/DownloadAttachmentUriGenerator;Lio/getstream/chat/android/ui/common/helper/DownloadRequestInterceptor;Lio/getstream/chat/android/ui/common/helper/ImageAssetTransformer;Lio/getstream/chat/android/compose/ui/util/MessageAlignmentProvider;Lio/getstream/chat/android/compose/ui/theme/MessageOptionsTheme;Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme;Lio/getstream/chat/android/ui/common/state/messages/list/MessageOptionsUserReactionAlignment;Ljava/util/List;ZLio/getstream/chat/android/ui/common/images/resizing/StreamCdnImageResizing;ZLio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageDateSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageUnreadSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageComposerTheme;Lio/getstream/chat/android/compose/ui/theme/AttachmentPickerTheme;Lio/getstream/chat/android/compose/ui/util/MessageTextFormatter;Lio/getstream/chat/android/compose/ui/util/QuotedMessageTextFormatter;Lio/getstream/sdk/chat/audio/recording/StreamMediaRecorder;Lio/getstream/chat/android/compose/ui/theme/StreamKeyboardBehaviour;Lio/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryConfig;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;IIIIIIII)V
public static final fun ChatTheme (ZZZZZLio/getstream/chat/android/ui/common/permissions/SystemAttachmentsPickerConfig;Lio/getstream/chat/android/compose/ui/theme/StreamColors;Lio/getstream/chat/android/compose/ui/theme/StreamDimens;Lio/getstream/chat/android/compose/ui/theme/StreamTypography;Lio/getstream/chat/android/compose/ui/theme/StreamShapes;Lio/getstream/chat/android/compose/ui/theme/StreamRippleConfiguration;Lio/getstream/chat/android/ui/common/model/UserPresence;Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Ljava/util/List;Lio/getstream/chat/android/compose/ui/components/messages/factory/MessageContentFactory;Ljava/util/List;Ljava/util/List;Lio/getstream/chat/android/compose/ui/util/ReactionIconFactory;Lio/getstream/chat/android/ui/common/helper/ReactionPushEmojiFactory;Lio/getstream/chat/android/compose/ui/theme/ReactionOptionsTheme;Lio/getstream/chat/android/compose/ui/util/MessagePreviewIconFactory;Lio/getstream/chat/android/compose/ui/util/PollSwitchItemFactory;ZLio/getstream/chat/android/ui/common/helper/DateFormatter;Lio/getstream/chat/android/ui/common/helper/TimeProvider;Lio/getstream/chat/android/ui/common/helper/DurationFormatter;Lio/getstream/chat/android/ui/common/utils/ChannelNameFormatter;Lio/getstream/chat/android/compose/ui/util/MessagePreviewFormatter;Lio/getstream/chat/android/compose/ui/util/SearchResultNameFormatter;Lio/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory;Lio/getstream/chat/android/ui/common/helper/ImageHeadersProvider;Lio/getstream/chat/android/ui/common/helper/AsyncImageHeadersProvider;Lio/getstream/chat/android/ui/common/helper/DownloadAttachmentUriGenerator;Lio/getstream/chat/android/ui/common/helper/DownloadRequestInterceptor;Lio/getstream/chat/android/ui/common/helper/ImageAssetTransformer;Lio/getstream/chat/android/compose/ui/util/MessageAlignmentProvider;Lio/getstream/chat/android/compose/ui/theme/MessageOptionsTheme;Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme;Lio/getstream/chat/android/ui/common/state/messages/list/MessageOptionsUserReactionAlignment;Ljava/util/List;ZLio/getstream/chat/android/ui/common/images/resizing/StreamCdnImageResizing;ZLio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageDateSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageUnreadSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageComposerTheme;Lio/getstream/chat/android/compose/ui/theme/AttachmentPickerTheme;Lio/getstream/chat/android/compose/ui/util/MessageTextFormatter;Lio/getstream/chat/android/compose/ui/util/QuotedMessageTextFormatter;Lio/getstream/sdk/chat/audio/recording/StreamMediaRecorder;Lio/getstream/chat/android/compose/ui/theme/StreamKeyboardBehaviour;Lio/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryConfig;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;IIIIIIII)V
public static final fun getLocalComponentFactory ()Landroidx/compose/runtime/ProvidableCompositionLocal;
}

Expand Down Expand Up @@ -4734,12 +4734,17 @@ public final class io/getstream/chat/android/compose/ui/util/StorageHelperWrappe
public abstract interface class io/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory {
public static final field Companion Lio/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory$Companion;
public abstract fun imageLoader (Landroid/content/Context;)Lcoil3/ImageLoader;
public abstract fun imageLoader (Landroid/content/Context;Ljava/util/List;)Lcoil3/ImageLoader;
}

public final class io/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory$Companion {
public final fun defaultFactory ()Lio/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory;
}

public final class io/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory$DefaultImpls {
public static fun imageLoader (Lio/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory;Landroid/content/Context;Ljava/util/List;)Lcoil3/ImageLoader;
}

public final class io/getstream/chat/android/compose/ui/util/StreamImageLoaderProvidableCompositionLocal {
public static final synthetic fun box-impl (Landroidx/compose/runtime/ProvidableCompositionLocal;)Lio/getstream/chat/android/compose/ui/util/StreamImageLoaderProvidableCompositionLocal;
public static synthetic fun constructor-impl$default (Landroidx/compose/runtime/ProvidableCompositionLocal;ILkotlin/jvm/internal/DefaultConstructorMarker;)Landroidx/compose/runtime/ProvidableCompositionLocal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
Expand All @@ -49,6 +50,7 @@ import io.getstream.chat.android.compose.ui.messages.attachments.factory.Attachm
import io.getstream.chat.android.compose.ui.messages.attachments.factory.AttachmentsPickerTabFactory
import io.getstream.chat.android.compose.ui.theme.messages.attachments.FileAttachmentTheme
import io.getstream.chat.android.compose.ui.util.DefaultPollSwitchItemFactory
import io.getstream.chat.android.compose.ui.util.ImageHeadersInterceptor
import io.getstream.chat.android.compose.ui.util.LocalStreamImageLoader
import io.getstream.chat.android.compose.ui.util.MessageAlignmentProvider
import io.getstream.chat.android.compose.ui.util.MessagePreviewFormatter
Expand All @@ -59,6 +61,7 @@ import io.getstream.chat.android.compose.ui.util.QuotedMessageTextFormatter
import io.getstream.chat.android.compose.ui.util.ReactionIconFactory
import io.getstream.chat.android.compose.ui.util.SearchResultNameFormatter
import io.getstream.chat.android.compose.ui.util.StreamCoilImageLoaderFactory
import io.getstream.chat.android.ui.common.helper.AsyncImageHeadersProvider
import io.getstream.chat.android.ui.common.helper.DateFormatter
import io.getstream.chat.android.ui.common.helper.DefaultDownloadAttachmentUriGenerator
import io.getstream.chat.android.ui.common.helper.DefaultImageAssetTransformer
Expand Down Expand Up @@ -160,6 +163,12 @@ private val LocalQuotedMessageTextFormatter = compositionLocalOf<QuotedMessageTe
private val LocalSearchResultNameFormatter = compositionLocalOf<SearchResultNameFormatter> {
error("No SearchResultNameFormatter provided! Make sure to wrap all usages of Stream components in a ChatTheme.")
}

@Deprecated(
message = "ImageHeadersProvider is deprecated. Use asyncImageHeadersProvider in ChatTheme instead. " +
"Headers are now injected via Coil's interceptor pipeline, which is thread-safe and supports " +
"blocking/suspending operations.",
)
private val LocalStreamImageHeadersProvider = compositionLocalOf<ImageHeadersProvider> {
error("No ImageHeadersProvider provided! Make sure to wrap all usages of Stream components in a ChatTheme.")
}
Expand Down Expand Up @@ -291,9 +300,17 @@ private val LocalMediaGalleryConfig = compositionLocalOf<MediaGalleryConfig> {
* @param durationFormatter [DurationFormatter] Used to format durations in the app.
* @param channelNameFormatter [ChannelNameFormatter] Used throughout the app for channel names.
* @param messagePreviewFormatter [MessagePreviewFormatter] Used to generate a string preview for the given message.
* @param imageLoaderFactory A factory that creates new Coil [ImageLoader] instances.
* @param imageLoaderFactory A factory that creates new Coil [ImageLoader] instances. If used in combination with
* [asyncImageHeadersProvider] you must override the [StreamCoilImageLoaderFactory.imageLoader] method accepting the
* interceptors parameter.
* @param imageAssetTransformer [ImageAssetTransformer] Used to transform image assets.
* @param imageHeadersProvider [ImageHeadersProvider] Used to provide headers for image requests.
* @param imageHeadersProvider [ImageHeadersProvider] Deprecated. Use [asyncImageHeadersProvider] instead. Headers
* provided here are injected synchronously on the main thread, which blocks the UI for any non-trivial work.
* @param asyncImageHeadersProvider [AsyncImageHeadersProvider] Used to provide headers for image
* requests. Invoked on IO Dispatcher inside Coil's interceptor pipeline, making it safe for blocking or suspending
* operations such as reading an auth token. Prefer this over [imageHeadersProvider]. If you are using this in
* combination with a custom [StreamCoilImageLoaderFactory] you must override the
* [StreamCoilImageLoaderFactory.imageLoader] method accepting the interceptors parameter.
* @param downloadAttachmentUriGenerator [DownloadAttachmentUriGenerator] Used to generate download URIs for
* attachments.
* @param downloadRequestInterceptor [DownloadRequestInterceptor] Used to intercept download requests.
Expand Down Expand Up @@ -361,6 +378,7 @@ public fun ChatTheme(
searchResultNameFormatter: SearchResultNameFormatter = SearchResultNameFormatter.defaultFormatter(),
imageLoaderFactory: StreamCoilImageLoaderFactory = StreamCoilImageLoaderFactory.defaultFactory(),
imageHeadersProvider: ImageHeadersProvider = DefaultImageHeadersProvider,
asyncImageHeadersProvider: AsyncImageHeadersProvider? = null,
downloadAttachmentUriGenerator: DownloadAttachmentUriGenerator = DefaultDownloadAttachmentUriGenerator,
downloadRequestInterceptor: DownloadRequestInterceptor = DownloadRequestInterceptor { },
imageAssetTransformer: ImageAssetTransformer = DefaultImageAssetTransformer,
Expand Down Expand Up @@ -430,6 +448,19 @@ public fun ChatTheme(
ChatClient.VERSION_PREFIX_HEADER = VersionPrefixHeader.Compose
}

val context = LocalContext.current
val imageLoader = remember(imageLoaderFactory, asyncImageHeadersProvider) {
if (asyncImageHeadersProvider == null) {
imageLoaderFactory.imageLoader(context.applicationContext)
} else {
imageLoaderFactory.imageLoader(
context.applicationContext,
listOf(ImageHeadersInterceptor(asyncImageHeadersProvider)),
)
}
}

@Suppress("DEPRECATION")
CompositionLocalProvider(
LocalColors provides colors,
LocalDimens provides dimens,
Expand Down Expand Up @@ -463,7 +494,7 @@ public fun ChatTheme(
LocalMessageUnreadSeparatorTheme provides messageUnreadSeparatorTheme,
LocalMessageComposerTheme provides messageComposerTheme,
LocalAttachmentPickerTheme provides attachmentPickerTheme,
LocalStreamImageLoader provides imageLoaderFactory.imageLoader(LocalContext.current.applicationContext),
LocalStreamImageLoader provides imageLoader,
LocalStreamImageHeadersProvider provides imageHeadersProvider,
LocalStreamDownloadAttachmentUriGenerator provides downloadAttachmentUriGenerator,
LocalStreamDownloadRequestInterceptor provides downloadRequestInterceptor,
Expand Down Expand Up @@ -820,7 +851,15 @@ public object ChatTheme {

/**
* Retrieves the current [ImageHeadersProvider] at the call site's position in the hierarchy.
*
* @deprecated Use [asyncImageHeadersProvider] in [ChatTheme] for thread-safe header injection.
*/
@Deprecated(
message = "ImageHeadersProvider is deprecated. Pass asyncImageHeadersProvider to ChatTheme instead. " +
"Headers are now injected via Coil's interceptor pipeline, which is thread-safe and supports " +
"blocking/suspending operations.",
)
@Suppress("DEPRECATION")
public val streamImageHeadersProvider: ImageHeadersProvider
@Composable
@ReadOnlyComposable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.util

import coil3.intercept.Interceptor
import coil3.network.httpHeaders
import coil3.request.ImageResult
import io.getstream.chat.android.ui.common.helper.AsyncImageHeadersProvider
import io.getstream.chat.android.ui.common.images.internal.toNetworkHeaders
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

/**
* A Coil [Interceptor] that injects HTTP headers provided by [AsyncImageHeadersProvider] into
* each image request. The provider is invoked as part of Coil's background pipeline, so
* blocking or suspending operations (e.g. fetching an auth token) are safe to perform inside
* [AsyncImageHeadersProvider.getImageRequestHeaders].
*/
internal class ImageHeadersInterceptor(private val headersProvider: AsyncImageHeadersProvider) : Interceptor {

override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
val url = chain.request.data.toString()
val headers = withContext(Dispatchers.IO) {
headersProvider.getImageRequestHeaders(url)
}
val newRequest = chain.request.newBuilder()
.httpHeaders(headers.toNetworkHeaders())
.build()
return chain.withRequest(newRequest).proceed()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package io.getstream.chat.android.compose.ui.util
import android.content.Context
import coil3.ImageLoader
import coil3.SingletonImageLoader
import coil3.intercept.Interceptor
import io.getstream.chat.android.ui.common.images.StreamImageLoaderFactory

/**
Expand All @@ -31,15 +32,40 @@ public fun interface StreamCoilImageLoaderFactory {
*/
public fun imageLoader(context: Context): ImageLoader

/**
* Returns a new Coil [ImageLoader] with the given [interceptors] prepended to the component
* registry, ahead of all decoders and Coil's built-in EngineInterceptor.
*
* The default implementation **ignores [interceptors]** and delegates to [imageLoader].
* This means that when a custom [StreamCoilImageLoaderFactory] is used alongside
* [ChatTheme]'s `asyncImageHeadersProvider`, the async headers will **not** be injected —
* the custom factory's loader is returned as-is.
*
* Custom class implementations that want to support interceptor injection should override this
* method, for example by forwarding [interceptors] to [StreamImageLoaderFactory]:
* ```kotlin
* override fun imageLoader(context: Context, interceptors: List<Interceptor>): ImageLoader =
* StreamImageLoaderFactory(interceptors = interceptors, builder = myCustomBuilder)
* .newImageLoader(context)
* ```
*
* Integrators using a custom [StreamCoilImageLoaderFactory] who also need auth headers on
* image requests should either override this method or inject the headers directly inside
* their factory's [imageLoader] implementation (e.g. via a custom OkHttp client).
*
* @param context The [Context] to build the [ImageLoader] with.
* @param interceptors Coil [Interceptor]s to prepend to the component registry.
*/
public fun imageLoader(context: Context, interceptors: List<Interceptor>): ImageLoader =
imageLoader(context)

public companion object {
/**
* Returns the default singleton instance of [StreamCoilImageLoaderFactory].
*
* @return The default implementation of [StreamCoilImageLoaderFactory].
*/
public fun defaultFactory(): StreamCoilImageLoaderFactory {
return DefaultStreamCoilImageLoaderFactory
}
public fun defaultFactory(): StreamCoilImageLoaderFactory = DefaultStreamCoilImageLoaderFactory
}
}

Expand Down Expand Up @@ -68,6 +94,13 @@ internal object DefaultStreamCoilImageLoaderFactory : StreamCoilImageLoaderFacto
*/
override fun imageLoader(context: Context): ImageLoader = imageLoader ?: newImageLoader(context)

override fun imageLoader(context: Context, interceptors: List<Interceptor>): ImageLoader =
if (interceptors.isEmpty()) {
imageLoader(context)
} else {
StreamImageLoaderFactory(interceptors = interceptors).newImageLoader(context)
}

/**
* Builds a new [ImageLoader] using the given Android [Context]. If the loader already exists, we return it.
*
Expand Down
Loading
Loading