Skip to content

Merge develop to v7 (30.03.2026)#6309

Open
VelikovPetar wants to merge 28 commits intov7from
develop_to_v7_20260330
Open

Merge develop to v7 (30.03.2026)#6309
VelikovPetar wants to merge 28 commits intov7from
develop_to_v7_20260330

Conversation

@VelikovPetar
Copy link
Copy Markdown
Contributor

@VelikovPetar VelikovPetar commented Mar 30, 2026

Goal

Merge develop to V7

Implementation

Merge develop to V7

Testing

There should be no regressions, and the latest additions from develop should be present on V7

Summary by CodeRabbit

  • New Features

    • Added CDN integration for customizable content delivery and URL transformation.
    • Introduced document attachment handling with alternative rendering options.
    • Added recording completion callbacks for message composer.
    • Implemented server clock synchronization for improved accuracy.
  • Improvements

    • Enhanced attachment preview URL selection for better media display.
    • Optimized media playback with CDN-backed data sources.
    • Improved cache management with TTL and size-based eviction.
    • Better error handling for unresolved attachments.
  • Deprecations

    • AsyncImageHeadersProvider, DownloadAttachmentUriGenerator, and related legacy interfaces deprecated; use CDN configuration instead.

VelikovPetar and others added 19 commits March 23, 2026 14:15
…ver time (#6199)

* Fix createdLocallyAt using NTP-style server clock offset estimation

Co-Authored-By: Claude <noreply@anthropic.com>

* Pr remarks

* Adjust thread message createdLocallyAt.

* Ensure exceedsSyncThreshold is compared against estimated server time (where applicable).

* Add max allowed offset.

---------

Co-authored-by: Claude <noreply@anthropic.com>
…ers.IO)` (#6284)

Co-authored-by: Claude <noreply@anthropic.com>
* Update `DependencyResolverTest` to verify error handling when dependency resolution races with disconnection.

* Prevent race conditions during disconnects in `ChatClient`.
- Update `StorageHelper` and `AttachmentMetaDataMapper` to safely handle cases where content URIs (e.g. cloud-backed files) cannot be opened.
- Introduce `hasUnresolvedAttachments` state in `AttachmentsPickerViewModel` to track failed attachment resolutions.
- Show a toast message in both View-based and Compose attachment pickers when files are unavailable and need to be downloaded to the device.
- Add `clearUnresolvedAttachments` to reset the error state after it has been consumed by the UI.
- Add unit tests for unresolved attachment scenarios in `AttachmentsPickerViewModelTest`.
…l` (#6280)

* Deprecate imagePreviewUrl and use type-specific attachment URL fields

Co-Authored-By: Claude <noreply@anthropic.com>

* Extract common extensions.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: adasiewiczr <17440581+adasiewiczr@users.noreply.github.com>
* Add new CDN contract.

* Add CDN for document files.

* Add CDN support for downloading attachments.

* Deprecate current CDN methods.

* Add progress indicator snackbar.

* Add useDocumentGView config flag.

* Add file sharing cache handling.

* Add file sharing cache handling.

* Remove CDNResponse.kt

* Add tests

* PR remarks
# Conflicts:
#	README.md
#	gradle.properties
#	metrics/size.json
#	stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt
#	stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/state/ChatClientStateExtensions.kt
#	stream-chat-android-compose/api/stream-chat-android-compose.api
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/ImageAttachmentPreviewContent.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentPreviewContent.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentQuotedContent.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/handler/DocumentAttachmentPreviewHandler.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channel/attachments/ChannelMediaAttachmentsScreen.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/ComposerLinkPreview.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/QuotedMessage.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentsPicker.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/StorageHelperWrapper.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/extensions/internal/AttachmentExtensions.kt
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/AttachmentsPickerViewModel.kt
#	stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/AttachmentsPickerViewModelTest.kt
#	stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/Attachment.kt
#	stream-chat-android-ui-components/api/stream-chat-android-ui-components.api
#	stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogFragment.kt
#	stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/internal/AttachmentMetaDataMapper.kt
#	stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListView.kt
#	stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/navigation/destinations/AttachmentDestination.kt
#	stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageComposerViewModel.kt
@VelikovPetar VelikovPetar added the pr:internal Internal changes / housekeeping label Mar 30, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 30, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@VelikovPetar VelikovPetar marked this pull request as ready for review March 30, 2026 11:52
@VelikovPetar VelikovPetar requested a review from a team as a code owner March 30, 2026 11:52
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 30, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.86 MB 0.61 MB 🔴
stream-chat-android-ui-components 10.60 MB 11.18 MB 0.58 MB 🔴
stream-chat-android-compose 12.81 MB 12.42 MB -0.39 MB 🚀

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

Walkthrough

A comprehensive CDN abstraction is introduced alongside server clock offset synchronization. The changes integrate CDN-based URL rewriting and header injection across media playback, image loading, HTTP requests, and file downloads. Multiple legacy customization interfaces are deprecated in favor of the new CDN configuration model. Attachment URL handling is refactored to use specific fields instead of a generic imagePreviewUrl.

Changes

Cohort / File(s) Summary
CDN Core Abstraction
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/cdn/CDN.kt, CDNRequest.kt, stream-chat-android-client/api/stream-chat-android-client.api
New public CDN interface with imageRequest() and fileRequest() suspend methods; new CDNRequest data class with URL and optional headers; public API additions to builder and .api file.
Server Clock Offset
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/utils/internal/ServerClockOffset.kt, ServerClockOffsetTest.kt
New ServerClockOffset class providing NTP-style server time estimation via connection/health-check events; comprehensive test suite validating calibration and offset recalculation.
ChatClient & Module Integration
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt, ChatModule.kt, FakeChatSocket.kt
Added serverClockOffset and optional cdn parameters to ChatClient constructor; new Builder.cdn() method; propagated dependencies through module initialization and socket layer; updated test fixtures.
Socket & Connection Management
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/socket/ChatSocket.kt, SyncManager.kt, StreamStatePluginFactory.kt
Integrated ServerClockOffset into connection/health-check flows and sync staleness decision-making; updated message comparison logic to use server-estimated time.
Media DataSource & ExoPlayer Integration
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/cdn/internal/CDNDataSourceFactory.kt, StreamMediaDataSource.kt, NativeMediaPlayer.kt, CDNDataSourceTest.kt
New CDN-aware DataSource.Factory wrapper; StreamMediaDataSource factory for media playback with optional CDN; updated NativeMediaPlayerImpl to accept injected DataSource.Factory; comprehensive test coverage.
HTTP & OkHttp Integration
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/cdn/internal/CDNOkHttpInterceptor.kt, CDNOkHttpInterceptorTest.kt
New OkHttp interceptor for rewriting CDN URLs and applying headers in HTTP requests; tests covering URL rewriting, header injection, and error handling.
Download & File Management
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/state/ChatClientStateExtensions.kt, stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/file/StreamFileManager.kt, StreamFileManagerTest.kt
Updated downloadAttachment to apply CDN transformations; new evictCacheFiles() for TTL/size-based cache management; tests validating expiration and eviction behavior.
Compose UI: Attachment URL Refactoring
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/*, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/*, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channel/*
Replaced imagePreviewUrl usage with imageUrl, thumbUrl, and new extension properties (linkPreviewImageUrl, giphyFallbackPreviewUrl); updated LinkAttachmentContent, MediaAttachmentContent, QuotedMessageBodyBuilder, ComposerLinkPreview, ChannelMediaAttachmentsScreen.
Compose UI: Image Loading & CDN
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/internal/StreamMediaPlayerContent.kt, ChatTheme.kt, ImageHeadersInterceptor.kt, stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/images/internal/CDNImageInterceptor.kt, StreamCoil.kt
New Coil CDNImageInterceptor for image URL rewriting; ChatTheme parameter for document viewer selection and CDN-aware Coil interceptor setup; merged header handling in ImageHeadersInterceptor.
Compose Attachments Picker & Recording
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/AttachmentsPickerViewModel.kt, MessageComposerViewModel.kt, AttachmentPicker.kt, AttachmentsPickerViewModelTest.kt
New hasUnresolvedAttachments flag in picker; updated completeRecording() to accept result callback; UI observations and error handling for unresolved attachments.
UI Components: Attachment URL Refactoring
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/*, adapter/view/internal/*, MessageListView.kt
Replaced imagePreviewUrl with specific URL fields across gallery, message list, and attachment views; updated AttachmentGalleryImagePageFragment.create() and AttachmentGalleryVideoPageFragment.create() to accept raw URL strings.
UI Components: Document & File Handling
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/navigation/destinations/AttachmentDestination.kt, stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/documents/DocumentAttachmentHandler.kt, AttachmentDocumentActivity.java, AttachmentDestination.kt
New DocumentAttachmentHandler for in-app document opening; AttachmentDocumentActivity deprecated; ChatUI.useDocumentGView flag for conditional document viewing; conditional routing in AttachmentDestination.
File & Cache Management
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/internal/file/StreamShareFileManager.kt, StorageHelper.kt, StreamShareFileManagerTest.kt
New ShareCacheConfig for cache TTL/size configuration; progress callback support in writeAttachmentToShareableFile(); cache expiration validation; error handling for unreachable URIs.
Attachment Extensions & Utilities
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/Attachment.kt, AttachmentExtensionsTest.kt
Deprecated imagePreviewUrl extension; new @InternalStreamChatApi extensions: linkPreviewImageUrl, linkUrl, giphyFallbackPreviewUrl; test suite validating field precedence.
Deprecations & Configuration
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/*.kt, stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/ChatUI.kt, stream-chat-android-compose/api/stream-chat-android-compose.api, stream-chat-android-ui-components/api/stream-chat-android-ui-components.api
Deprecated AsyncImageHeadersProvider, DownloadAttachmentUriGenerator, DownloadRequestInterceptor, ImageAssetTransformer, ImageHeadersProvider, VideoHeadersProvider in favor of CDN; new ChatUI.useDocumentGView boolean flag; API signature updates.
Resources & Test Infrastructure
stream-chat-android-ui-common/src/main/res/values/strings.xml, detekt-baseline.xml, MockClientBuilder.kt, DependencyResolverTest.kt, ChatClientConnectionTests.kt, ChatClientTest.kt, BaseChatClientTest.kt, ChatClientDebuggerTest.kt, FakeChatSocket.kt, SyncManagerTest.kt
New string resources for attachment operations (opening, downloading, errors); detekt baseline cleanup; test infrastructure updated with serverClockOffset injection across client tests.
Sample Apps
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/channel/attachments/ChannelMediaAttachmentsActivity.kt, stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt, stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/info/shared/media/ChatInfoSharedMediaFragment.kt
Updated sample attachment filtering predicates to use imageUrl ?: thumbUrl instead of imagePreviewUrl.
Documentation
README.md, stream-chat-android-client/AndroidManifest.xml
README hero image source updated; manifest whitespace cleanup.

Sequence Diagram

sequenceDiagram
    actor User
    participant App as Application
    participant Client as ChatClient
    participant CDN as CDN Interface
    participant Network as Network Layer<br/>(OkHttp/Coil)
    participant Media as Media System<br/>(ExoPlayer)
    participant Server as Stream Server

    User->>App: Request image/file/media
    App->>Client: downloadAttachment() or loadMedia()
    Client->>CDN: imageRequest(url) or fileRequest(url)
    
    alt CDN Transformation
        CDN-->>Client: CDNRequest{newUrl, headers}
        Client->>Network: Request with transformed URL + headers
    else No CDN
        CDN-->>Client: CDNRequest{originalUrl, null}
        Client->>Network: Request with original URL
    end
    
    Network->>Server: HTTP GET (CDN URL + headers)
    Server-->>Network: Media/File content
    Network-->>Media: Stream data
    Media-->>App: Render/Playback
    App-->>User: Display content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #6280 — Deprecates Attachment.imagePreviewUrl and replaces it with imageUrl/thumbUrl across Compose and UI component call sites, overlapping significantly with the attachment URL refactoring in this PR.
  • PR #6090 — Modifies StreamFileManager and cache-clearing flows, introducing file eviction and TTL management that directly parallels the cache management additions here.
  • PR #6295 — Implements the same CDN abstraction (CDN, CDNRequest), ChatClient.Builder.cdn() wiring, and CDN integrations (OkHttp, ExoPlayer, Coil) as a parallel/related effort.

Suggested labels

released

Suggested reviewers

  • gpunto
  • andremion
  • aleksandar-apostolov

Poem

🐰 Hop along, dear SDK, with headers in hand,
CDN transformations across the land,
Clock offsets sync with the server so true,
Attachments preview in shiny new hue,
From old imagePreviewUrl we now say adieu! 📸✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop_to_v7_20260330

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/StorageHelper.kt (1)

271-275: ⚠️ Potential issue | 🟡 Minor

Bug: Wrong column index used in null check for duration.

The condition checks cursor.isNull(fileSizeIndex) but should check cursor.isNull(durationIndex).

🐛 Proposed fix
-        val duration = if (durationIndex != -1 && !cursor.isNull(fileSizeIndex)) {
+        val duration = if (durationIndex != -1 && !cursor.isNull(durationIndex)) {
             cursor.getLong(durationIndex)
         } else {
             0L
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/StorageHelper.kt`
around lines 271 - 275, The null-check for the duration column uses the wrong
index: change the condition in StorageHelper (where durationIndex and
fileSizeIndex are used) to call cursor.isNull(durationIndex) instead of
cursor.isNull(fileSizeIndex) so the code verifies the duration column before
calling cursor.getLong(durationIndex); update the conditional that sets duration
to use the correct durationIndex null check.
🧹 Nitpick comments (10)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageComposerViewModel.kt (1)

208-209: Consider adding KDoc for the public API.

The completeRecording function is part of the public API and now accepts a completion callback. Per coding guidelines, public APIs should be documented with KDoc. While other recording functions in this file also lack KDoc, this new parameter would benefit from documentation explaining when onComplete is invoked and what Result<Attachment> contains.

Suggested KDoc
+    /**
+     * Completes the current audio recording.
+     *
+     * `@param` onComplete Optional callback invoked with the resulting [Attachment] on success
+     * or an error on failure.
+     */
     public fun completeRecording(onComplete: ((Result<Attachment>) -> Unit)? = null): Unit =
         messageComposerController.completeRecording(onComplete)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageComposerViewModel.kt`
around lines 208 - 209, Add KDoc to the public function
MessageComposerViewModel.completeRecording to document the new onComplete
parameter: explain when the callback is invoked (e.g., after recording
successfully completes or fails), describe the Result<Attachment> contents for
success (the created Attachment) and failure (the error/exception), and note
threading/dispatch expectations and any nullability/optional behavior; reference
the delegation to messageComposerController.completeRecording in the docs so
callers understand behavior is forwarded to that controller.
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/overview/internal/MediaAttachmentAdapter.kt (1)

191-193: Consider aligning DiffUtil comparison with URL selection logic.

The areItemsTheSame uses (imageUrl ?: thumbUrl) uniformly, while loadImage selects the URL based on attachment type (imageUrl for images, thumbUrl otherwise). This inconsistency is unlikely to cause issues in practice since attachment types don't change, but for consistency the comparison could mirror the type-aware selection.

Optional: Type-aware comparison
 override fun areItemsTheSame(oldItem: AttachmentGalleryItem, newItem: AttachmentGalleryItem): Boolean {
-    return (oldItem.attachment.imageUrl ?: oldItem.attachment.thumbUrl) ==
-        (newItem.attachment.imageUrl ?: newItem.attachment.thumbUrl) &&
+    val oldUrl = if (oldItem.attachment.isImage()) oldItem.attachment.imageUrl else oldItem.attachment.thumbUrl
+    val newUrl = if (newItem.attachment.isImage()) newItem.attachment.imageUrl else newItem.attachment.thumbUrl
+    return oldUrl == newUrl &&
         oldItem.createdAt == newItem.createdAt
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/overview/internal/MediaAttachmentAdapter.kt`
around lines 191 - 193, The DiffUtil comparison in
MediaAttachmentAdapter::areItemsTheSame currently compares (imageUrl ?:
thumbUrl) but loadImage selects the URL based on attachment type; update
areItemsTheSame to mirror loadImage's type-aware URL selection (use
attachment.type or the same predicate loadImage uses to decide image vs
thumbnail) when comparing URLs and keep the createdAt check; modify the
comparison logic in areItemsTheSame to call the same helper or replicate the
same conditional used by loadImage so both routines pick the same URL for
comparison.
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogFragment.kt (1)

94-101: Consider: User feedback for unresolved attachments in XML UI.

The Compose AttachmentPicker shows a toast when attachments cannot be resolved, but the XML AttachmentsPickerDialogFragment silently drops them. This could confuse users when some selected files don't appear in the composer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogFragment.kt`
around lines 94 - 101, The XML picker silently drops unresolved attachments in
AttachmentsPickerDialogFragment.onDismiss when style.saveAttachmentsOnDismiss is
true; change the logic to detect which selectedAttachments failed to convert
(e.g., compare selectedAttachments to the result of
selectedAttachments.mapNotNull { it.toAttachment(requireContext()) } or collect
failed items via selectedAttachments.filter { it.toAttachment(requireContext())
== null }), call attachmentsSelectionListener?.onAttachmentsSelected(...) with
the successfully resolved attachments as before, and then show a user-facing
Toast (using requireContext()) that indicates how many or which attachments
could not be resolved so users get feedback about dropped files.
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/ImageAssetTransformer.kt (1)

37-54: Add thread-expectation note to transform KDoc.

Please document the expected calling thread/context for transform(asset: Any) (and any state assumptions), so custom implementations are safe.

As per coding guidelines **/*.kt: “Document public APIs with KDoc, including thread expectations and state notes”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/ImageAssetTransformer.kt`
around lines 37 - 54, The KDoc for ImageAssetTransformer.transform(asset: Any)
is missing the expected calling thread and state assumptions; update the KDoc
for the public function transform in ImageAssetTransformer to explicitly state
which thread/context callers must use (e.g., "Called on main/UI thread" or "May
be called from background threads; implementations must be thread-safe"), and
document any state assumptions (e.g., no mutation of shared state, no
long-running work, must be fast or offloaded). Mention whether implementations
may access UI-only resources or must avoid blocking I/O so custom
implementations implement correct synchronization or offloading.
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/factory/StreamStatePluginFactory.kt (1)

130-143: Add a focused non-zero-offset regression test for this wiring.

This is the handoff that makes sync staleness depend on the shared ServerClockOffset, so a small test proving behavior with a non-zero offset would make this much harder to regress.

If you add it, I’d keep it under runTest with virtual time because this path is timing-sensitive. Based on learnings: Applies to /src/test//*.kt : Use deterministic tests with runTest + virtual time for concurrency-sensitive logic (uploads, sync, message state).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/factory/StreamStatePluginFactory.kt`
around lines 130 - 143, Add a deterministic regression test that verifies
SyncManager's staleness logic respects a non-zero ServerClockOffset: create a
test under src/test/**/*.kt that uses runTest with virtual time, construct the
same wiring as in StreamStatePluginFactory (instantiate SyncManager with
chatClient.serverClockOffset set to a non-zero value and the now = {
System.currentTimeMillis() } replacement if needed), drive the timing-sensitive
path (trigger sync/reconnect) and assert that sync staleness/behavior changes
according to the provided offset; keep the test focused, use virtual time to
avoid flakiness, and target the SyncManager/ServerClockOffset wiring to prevent
regressions.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/handler/DocumentAttachmentPreviewHandler.kt (1)

25-37: Document the Activity-context/UI-call expectation for this handler.

The new behavior switch is clear, but this public KDoc still doesn't spell out the required call context even though handleAttachmentPreview starts activities. Please add the expected thread/call context and state notes so consumers know what kind of Context is valid here.

As per coding guidelines, **/*.kt: Document public APIs with KDoc, including thread expectations and state notes.

Also applies to: 57-63

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/handler/DocumentAttachmentPreviewHandler.kt`
around lines 25 - 37, Update the KDoc for DocumentAttachmentPreviewHandler to
state that the provided Context must be an Activity (or an unwrapped Context
capable of starting activities) and that handleAttachmentPreview will start
activities (via Intents/CustomTabs/Google Docs Viewer) so it must be invoked on
the main/UI thread when the Activity is in a valid, not-destroyed state;
reference the constructor parameter useDocumentGView and the public method
handleAttachmentPreview in the note so callers know which behavior depends on
that flag and when to supply an Activity-context.
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/DownloadAttachmentUriGenerator.kt (1)

22-30: Add thread/state notes to the deprecation KDoc.

These types are still public API until removal, and the new deprecation docs don't yet capture the expected thread/state contract for generateDownloadUri.

As per coding guidelines, **/*.kt: Document public APIs with KDoc, including thread expectations and state notes.

Also applies to: 42-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/DownloadAttachmentUriGenerator.kt`
around lines 22 - 30, Update the deprecation KDoc for the public interface
DownloadAttachmentUriGenerator (and its generateDownloadUri contract) to include
thread and state expectations: state whether implementations may be called on
any thread or must be called on the main/UI thread, whether it must be
non-blocking, and whether it may be invoked after related message/attachment
objects are recycled or mutated; add a brief "State/Threading" note describing
these expectations so callers and implementers know concurrency and lifecycle
guarantees. Reference the DownloadAttachmentUriGenerator interface and its
generateDownloadUri method when adding this KDoc note, and mirror the same
change for the analogous KDoc block at the other occurrence mentioned.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/QuotedMessageBodyBuilder.kt (1)

201-212: Resize video thumbnail URLs here as well.

Images already go through applyStreamCdnImageResizingIfEnabled, and Attachment.imagePreviewData now does the same for video thumbnails. Quoted video replies still use the raw thumbUrl, so they skip the CDN downsizing path.

💡 Suggested change
                 type == AttachmentType.VIDEO -> {
                     videoCount++
                     fileCount++
-                    mediaPreviewData = attachment.upload ?: attachment.thumbUrl
+                    mediaPreviewData = attachment.upload ?: attachment.thumbUrl
+                        ?.applyStreamCdnImageResizingIfEnabled(streamCdnImageResizing)
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/QuotedMessageBodyBuilder.kt`
around lines 201 - 212, The video branch in QuotedMessageBodyBuilder sets
mediaPreviewData to attachment.upload ?: attachment.thumbUrl but does not apply
CDN image resizing; update the AttachmentType.VIDEO branch so that when
attachment.upload is null you pass attachment.thumbUrl through
applyStreamCdnImageResizingIfEnabled(streamCdnImageResizing) (same helper used
for images) so mediaPreviewData receives the resized thumb URL; adjust the
assignment in the video case where mediaPreviewData is set (and ensure you
reference the same helper used for images and imagePreviewData).
stream-chat-android-client/src/test/java/io/getstream/chat/android/client/cdn/internal/CDNOkHttpInterceptorTest.kt (1)

65-82: Add a same-key header override regression test.

This suite proves additive merging, but it never locks in what should happen when the original request already has Authorization. Without that case, a regression that keeps both values would still pass here.

♻️ Suggested test addition
+    `@Test`
+    fun `intercept CDN headers override existing headers for same key`() {
+        val cdn = object : CDN {
+            override suspend fun fileRequest(url: String) =
+                CDNRequest(url, headers = mapOf("Authorization" to "new-token"))
+        }
+        val interceptor = CDNOkHttpInterceptor(cdn)
+        val originalRequest = Request.Builder()
+            .url("https://original.com/file.mp4")
+            .addHeader("Authorization", "old-token")
+            .build()
+        val chain = FakeChain(FakeResponse(200), request = originalRequest)
+
+        val response = interceptor.intercept(chain)
+
+        assertEquals("new-token", response.request.header("Authorization"))
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-client/src/test/java/io/getstream/chat/android/client/cdn/internal/CDNOkHttpInterceptorTest.kt`
around lines 65 - 82, Add a regression test that verifies same-key header
overriding for Authorization: create a CDN stub (implementing CDN.fileRequest)
that returns a CDNRequest with an "Authorization" header, build an original
Request with an existing "Authorization" header value, run
CDNOkHttpInterceptor.intercept using a FakeChain/FakeResponse, and assert that
response.request.header("Authorization") equals the CDN-provided value (and not
the original), ensuring the interceptor replaces the original Authorization
header rather than keeping both.
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/internal/file/StreamShareFileManager.kt (1)

95-95: Use a share-specific cache prefix before relying on prefix-scoped eviction.

Line 95 now evicts by cacheFilePrefix, while Lines 150 and 169 still default to "TMP". That prefix is generic enough to let this manager clean up unrelated cache entries if anything else in the app uses the same convention. A feature-specific prefix keeps the TTL/size policy scoped to share files only.

♻️ Suggested diff
 public data class ShareCacheConfig(
-    val cacheFilePrefix: String = "TMP",
+    val cacheFilePrefix: String = "stream_share_",
     val bitmapShareFilename: String = "shared_image.png",
     val bitmapQuality: Int = 90,
     val cacheTtlMs: Long = 5 * 60 * 1000L,
     val maxCacheSizeBytes: Long = 25L * 1024 * 1024,
 )

Also applies to: 150-150, 168-170

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/internal/file/StreamShareFileManager.kt`
at line 95, StreamShareFileManager is evicting and creating cache files using a
generic prefix ("TMP" / config.cacheFilePrefix) which can collide with other
cache users; change the calls in StreamShareFileManager (the evictCacheFiles
call and places that default to "TMP" around file creation) to use a
share-specific prefix (e.g., derive a constant like SHARE_CACHE_PREFIX or append
a suffix to config.cacheFilePrefix such as "${config.cacheFilePrefix}_SHARE") so
eviction and TTL/size policies only target share files; update every usage in
this class (the evictCacheFiles invocation and the code paths that currently use
"TMP") to use that new share-specific prefix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 5: The hero image tag <img
src="/docs/stream-chat-android-github-cover.png"/> is missing an alt attribute;
update that element in README.md to include a concise descriptive alt text (for
example alt="Stream Chat Android GitHub cover") so screen readers can interpret
the image and satisfy markdownlint MD045.

In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/cdn/internal/CDNDataSourceFactory.kt`:
- Around line 71-78: The catch block in CDNDataSourceFactory around runBlocking
{ cdn.fileRequest(url) } must not swallow thread interruptions or coroutine
cancellations; change the exception handling so that InterruptedException and
CancellationException (or any Throwable that represents cancellation) are
re-thrown immediately and only non-cancellation exceptions are logged and cause
fallback to CDNRequest(url). Update the catch-site for the generic exception
suppression (`@Suppress`("TooGenericExceptionCaught")) to include a brief comment
explaining why it remains (per coding guidelines) or narrow the caught type to
Exception after rethrowing cancellations, and keep the logger.e(...) and
fallback to CDNRequest only for non-cancellation failures.

In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt`:
- Around line 411-420: The early snapshot of plugins in resolvePluginDependency
breaks when initialization completes after the snapshot; instead of using the
pre-wait currentPlugins, re-read the plugins list after
awaitInitializationState(RESOLVE_DEPENDENCY_TIMEOUT) (or hold the same
synchronization used by connect/disconnect) so that the resolver lookup uses the
up-to-date plugins; specifically update the code around
currentPlugins/awaitInitializationState/InitializationState.COMPLETE and the
subsequent resolver lookup to fetch plugins again (or perform the lookup under
the same lock) to avoid stale-plugin "was not found" errors.

In `@stream-chat-android-compose/api/stream-chat-android-compose.api`:
- Around line 7053-7054: Add a migration note documenting the breaking change:
the JVM no-arg entry point for MessageComposerViewModel.completeRecording was
removed and now requires a callback parameter (see completeRecording and
completeRecording$default signatures); update CHANGELOG.md and the v7 migration
guide to explain that Kotlin callers will keep no-arg calls after recompilation
(due to default parameters) but Java callers must now supply a Function1
callback or use the synthetic default helper, include an example of the new Java
call shape and a short snippet explaining how to migrate existing Java usages.
- Around line 720-721: Add an entry to the v7 CHANGELOG under
stream-chat-android-compose that documents the breaking JVM signature changes:
list the new signatures for
AttachmentPreviewHandler.defaultAttachmentHandlers(Context, boolean), the
DocumentAttachmentPreviewHandler constructor (now taking useDocumentGView
boolean), and ChatTheme (with useDocumentGView), explain the upgrade path for
Java callers and existing binaries — the new useDocumentGView flag defaults to
true, existing behavior is preserved, and Java callers can pass false to opt
into CDN-aware document handling — and include a short code snippet example
showing how Java callers should call the updated
AttachmentPreviewHandler.defaultAttachmentHandlers(...) and construct
DocumentAttachmentPreviewHandler with useDocumentGView=false to opt out.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController.kt`:
- Around line 1021-1037: The current completeRecording implementation adds the
finished Attachment into selected attachments via
addAttachments(listOf(result.value)), which duplicates/stages the recording
because recordingState already sets _recordingAttachment and syncAttachments()
appends that field; remove the explicit staging to _selectedAttachments: in
MessageComposerController.completeRecording (and the branch that calls
audioRecordingController.completeRecordingSync()), stop calling
addAttachments(...) on successful Result.Success and instead rely on the
existing _recordingAttachment flow (recordingState, _recordingAttachment,
syncAttachments()) so the attachment is only appended once.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/ImageAssetTransformer.kt`:
- Around line 58-65: Update the KDoc for DefaultImageAssetTransformer to reflect
that it is a pass-through transformer (it returns the asset unchanged) instead
of saying it "doesn't provide any headers"; locate the KDoc block above the
object declaration for DefaultImageAssetTransformer (implementing
ImageAssetTransformer) and replace the outdated phrase with wording like
"returns the asset unchanged" or "pass-through asset transformer" while
preserving the existing deprecation note.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/images/internal/CDNImageInterceptor.kt`:
- Around line 52-59: The catch block around cdn.imageRequest(...) in
CDNImageInterceptor currently catches all Exceptions and swallows
CancellationException, preventing coroutine cancellation from propagating;
modify the handler so it rethrows CancellationException (e.g., if (e is
CancellationException) throw e) before logging and falling back to
chain.proceed(), ensuring cancellation is propagated for cdn.imageRequest, and
only non-cancellation exceptions are logged and trigger the fallback behavior.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/internal/file/StreamShareFileManager.kt`:
- Line 63: The code currently always compresses to PNG in
StreamShareFileManager.bitmap.compress(...), so the exposed config.bitmapQuality
has no effect; update the config and compress call so the format is configurable
and the quality is honored for lossy formats: add a bitmapFormat (e.g.,
Bitmap.CompressFormat) option to the existing configuration (or replace
bitmapQuality with a format+quality pair), use that bitmapFormat in the
bitmap.compress call instead of hardcoding PNG, and preserve the existing
bitmapQuality for formats that support it (or ignore it when bitmapFormat == PNG
with a short comment); adjust any KDoc to reflect the new configurable format
and behavior.

In
`@stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/internal/file/StreamShareFileManagerTest.kt`:
- Around line 361-414: The tests use a hard-coded 6*60*1000L TTL which may not
exercise the expiration branch; update both tests to compute the stale timestamp
using ShareCacheConfig().cacheTtlMs (e.g. lastModified =
System.currentTimeMillis() - (ShareCacheConfig().cacheTtlMs + 1)) so the cached
file is definitely expired when shareFileManager calls
fileManager.getFileFromCache; also strengthen assertions by verifying the
miss-specific interactions: for writeAttachmentToShareableFile assert that
chatClient.downloadFile() and fileManager.writeFileInCache(...) are invoked (or
that writeFileInCache is called with the downloaded file) and for
getShareableUriForAttachment assert that no valid URI is returned (isFailure)
and optionally verify download/write are not called or are called according to
expected behavior.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/GiphyViewHolder.kt`:
- Around line 76-77: The bindData in GiphyViewHolder currently uses a non-local
return via "?: return" when resolving the GIF URL, which skips the rest of bind
logic (including setting giphyQueryTextView) and can leave recycled row state
stale; change the logic to avoid returning early by assigning the URL to a
nullable local (e.g., val url = it.giphyInfo(... ) ?:
it.giphyFallbackPreviewUrl) and then handle the null case inline (clear or hide
the image view, set a placeholder, but still proceed to set
giphyQueryTextView.text and other UI state). Ensure any image loading calls (the
code that used the URL) are guarded with an if (url != null) block and that
giphyQueryTextView and other fields on GiphyViewHolder are always updated
regardless of URL presence.

---

Outside diff comments:
In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/StorageHelper.kt`:
- Around line 271-275: The null-check for the duration column uses the wrong
index: change the condition in StorageHelper (where durationIndex and
fileSizeIndex are used) to call cursor.isNull(durationIndex) instead of
cursor.isNull(fileSizeIndex) so the code verifies the duration column before
calling cursor.getLong(durationIndex); update the conditional that sets duration
to use the correct durationIndex null check.

---

Nitpick comments:
In
`@stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/factory/StreamStatePluginFactory.kt`:
- Around line 130-143: Add a deterministic regression test that verifies
SyncManager's staleness logic respects a non-zero ServerClockOffset: create a
test under src/test/**/*.kt that uses runTest with virtual time, construct the
same wiring as in StreamStatePluginFactory (instantiate SyncManager with
chatClient.serverClockOffset set to a non-zero value and the now = {
System.currentTimeMillis() } replacement if needed), drive the timing-sensitive
path (trigger sync/reconnect) and assert that sync staleness/behavior changes
according to the provided offset; keep the test focused, use virtual time to
avoid flakiness, and target the SyncManager/ServerClockOffset wiring to prevent
regressions.

In
`@stream-chat-android-client/src/test/java/io/getstream/chat/android/client/cdn/internal/CDNOkHttpInterceptorTest.kt`:
- Around line 65-82: Add a regression test that verifies same-key header
overriding for Authorization: create a CDN stub (implementing CDN.fileRequest)
that returns a CDNRequest with an "Authorization" header, build an original
Request with an existing "Authorization" header value, run
CDNOkHttpInterceptor.intercept using a FakeChain/FakeResponse, and assert that
response.request.header("Authorization") equals the CDN-provided value (and not
the original), ensuring the interceptor replaces the original Authorization
header rather than keeping both.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/handler/DocumentAttachmentPreviewHandler.kt`:
- Around line 25-37: Update the KDoc for DocumentAttachmentPreviewHandler to
state that the provided Context must be an Activity (or an unwrapped Context
capable of starting activities) and that handleAttachmentPreview will start
activities (via Intents/CustomTabs/Google Docs Viewer) so it must be invoked on
the main/UI thread when the Activity is in a valid, not-destroyed state;
reference the constructor parameter useDocumentGView and the public method
handleAttachmentPreview in the note so callers know which behavior depends on
that flag and when to supply an Activity-context.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/QuotedMessageBodyBuilder.kt`:
- Around line 201-212: The video branch in QuotedMessageBodyBuilder sets
mediaPreviewData to attachment.upload ?: attachment.thumbUrl but does not apply
CDN image resizing; update the AttachmentType.VIDEO branch so that when
attachment.upload is null you pass attachment.thumbUrl through
applyStreamCdnImageResizingIfEnabled(streamCdnImageResizing) (same helper used
for images) so mediaPreviewData receives the resized thumb URL; adjust the
assignment in the video case where mediaPreviewData is set (and ensure you
reference the same helper used for images and imagePreviewData).

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/DownloadAttachmentUriGenerator.kt`:
- Around line 22-30: Update the deprecation KDoc for the public interface
DownloadAttachmentUriGenerator (and its generateDownloadUri contract) to include
thread and state expectations: state whether implementations may be called on
any thread or must be called on the main/UI thread, whether it must be
non-blocking, and whether it may be invoked after related message/attachment
objects are recycled or mutated; add a brief "State/Threading" note describing
these expectations so callers and implementers know concurrency and lifecycle
guarantees. Reference the DownloadAttachmentUriGenerator interface and its
generateDownloadUri method when adding this KDoc note, and mirror the same
change for the analogous KDoc block at the other occurrence mentioned.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/ImageAssetTransformer.kt`:
- Around line 37-54: The KDoc for ImageAssetTransformer.transform(asset: Any) is
missing the expected calling thread and state assumptions; update the KDoc for
the public function transform in ImageAssetTransformer to explicitly state which
thread/context callers must use (e.g., "Called on main/UI thread" or "May be
called from background threads; implementations must be thread-safe"), and
document any state assumptions (e.g., no mutation of shared state, no
long-running work, must be fast or offloaded). Mention whether implementations
may access UI-only resources or must avoid blocking I/O so custom
implementations implement correct synchronization or offloading.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/internal/file/StreamShareFileManager.kt`:
- Line 95: StreamShareFileManager is evicting and creating cache files using a
generic prefix ("TMP" / config.cacheFilePrefix) which can collide with other
cache users; change the calls in StreamShareFileManager (the evictCacheFiles
call and places that default to "TMP" around file creation) to use a
share-specific prefix (e.g., derive a constant like SHARE_CACHE_PREFIX or append
a suffix to config.cacheFilePrefix such as "${config.cacheFilePrefix}_SHARE") so
eviction and TTL/size policies only target share files; update every usage in
this class (the evictCacheFiles invocation and the code paths that currently use
"TMP") to use that new share-specific prefix.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/overview/internal/MediaAttachmentAdapter.kt`:
- Around line 191-193: The DiffUtil comparison in
MediaAttachmentAdapter::areItemsTheSame currently compares (imageUrl ?:
thumbUrl) but loadImage selects the URL based on attachment type; update
areItemsTheSame to mirror loadImage's type-aware URL selection (use
attachment.type or the same predicate loadImage uses to decide image vs
thumbnail) when comparing URLs and keep the createdAt check; modify the
comparison logic in areItemsTheSame to call the same helper or replicate the
same conditional used by loadImage so both routines pick the same URL for
comparison.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogFragment.kt`:
- Around line 94-101: The XML picker silently drops unresolved attachments in
AttachmentsPickerDialogFragment.onDismiss when style.saveAttachmentsOnDismiss is
true; change the logic to detect which selectedAttachments failed to convert
(e.g., compare selectedAttachments to the result of
selectedAttachments.mapNotNull { it.toAttachment(requireContext()) } or collect
failed items via selectedAttachments.filter { it.toAttachment(requireContext())
== null }), call attachmentsSelectionListener?.onAttachmentsSelected(...) with
the successfully resolved attachments as before, and then show a user-facing
Toast (using requireContext()) that indicates how many or which attachments
could not be resolved so users get feedback about dropped files.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageComposerViewModel.kt`:
- Around line 208-209: Add KDoc to the public function
MessageComposerViewModel.completeRecording to document the new onComplete
parameter: explain when the callback is invoked (e.g., after recording
successfully completes or fails), describe the Result<Attachment> contents for
success (the created Attachment) and failure (the error/exception), and note
threading/dispatch expectations and any nullability/optional behavior; reference
the delegation to messageComposerController.completeRecording in the docs so
callers understand behavior is forwarded to that controller.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c0c45f42-3bdb-4741-8651-252eba521771

📥 Commits

Reviewing files that changed from the base of the PR and between 0a6ceb0 and 9db7522.

⛔ Files ignored due to path filters (2)
  • docs/sdk-hero-android.png is excluded by !**/*.png
  • docs/stream-chat-android-github-cover.png is excluded by !**/*.png
📒 Files selected for processing (90)
  • README.md
  • stream-chat-android-client/api/stream-chat-android-client.api
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/state/ChatClientStateExtensions.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/audio/NativeMediaPlayer.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/cdn/CDN.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/cdn/CDNRequest.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/cdn/internal/CDNDataSourceFactory.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/cdn/internal/CDNOkHttpInterceptor.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/cdn/internal/StreamMediaDataSource.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/di/ChatModule.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/file/StreamFileManager.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/plugin/factory/StreamStatePluginFactory.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/state/sync/internal/SyncManager.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/socket/ChatSocket.kt
  • stream-chat-android-client/src/main/java/io/getstream/chat/android/client/utils/internal/ServerClockOffset.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/ChatClientConnectionTests.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/ChatClientTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/DependencyResolverTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/MockClientBuilder.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/cdn/internal/CDNDataSourceTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/cdn/internal/CDNOkHttpInterceptorTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/chatclient/BaseChatClientTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/debugger/ChatClientDebuggerTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/file/StreamFileManagerTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/internal/state/internal/SyncManagerTest.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/socket/FakeChatSocket.kt
  • stream-chat-android-client/src/test/java/io/getstream/chat/android/client/utils/internal/ServerClockOffsetTest.kt
  • stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/channel/attachments/ChannelMediaAttachmentsActivity.kt
  • stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt
  • stream-chat-android-compose/api/stream-chat-android-compose.api
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentContent.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewScreen.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/handler/AttachmentPreviewHandler.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/handler/DocumentAttachmentPreviewHandler.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/internal/MediaGalleryPage.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/internal/StreamMediaPlayerContent.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channel/attachments/ChannelMediaAttachmentsScreen.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/ComposerLinkPreview.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/QuotedMessageBodyBuilder.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentPicker.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/ImageHeadersInterceptor.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/extensions/internal/AttachmentExtensions.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channel/ChannelMediaAttachmentsPreviewViewModel.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/AttachmentsPickerViewModel.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModel.kt
  • stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/messages/QuotedMessageBodyBuilderTest.kt
  • stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/AttachmentsPickerViewModelTest.kt
  • stream-chat-android-ui-common/src/main/AndroidManifest.xml
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/documents/AttachmentDocumentActivity.java
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/documents/DocumentAttachmentHandler.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/AsyncImageHeadersProvider.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/DownloadAttachmentUriGenerator.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/DownloadRequestInterceptor.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/ImageAssetTransformer.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/ImageHeadersProvider.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/VideoHeadersProvider.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/helper/internal/StorageHelper.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/images/StreamImageLoaderFactory.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/images/internal/CDNImageInterceptor.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/images/internal/StreamCoil.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/internal/file/StreamShareFileManager.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/Attachment.kt
  • stream-chat-android-ui-common/src/main/res/values/strings.xml
  • stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/images/internal/CDNImageInterceptorTest.kt
  • stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/internal/file/StreamShareFileManagerTest.kt
  • stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/utils/extensions/AttachmentExtensionsTest.kt
  • stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/info/shared/media/ChatInfoSharedMediaFragment.kt
  • stream-chat-android-ui-components/api/stream-chat-android-ui-components.api
  • stream-chat-android-ui-components/detekt-baseline.xml
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/ChatUI.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryImagePageFragment.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryPagerAdapter.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/overview/internal/MediaAttachmentAdapter.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/AttachmentsPickerDialogFragment.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/internal/AttachmentMetaDataMapper.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListView.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/DefaultQuotedAttachmentView.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/FileAttachmentsView.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/GiphyMediaAttachmentView.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/LinkAttachmentView.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/MediaAttachmentView.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/GiphyViewHolder.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/navigation/destinations/AttachmentDestination.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageComposerViewModel.kt
💤 Files with no reviewable changes (3)
  • stream-chat-android-ui-common/src/main/AndroidManifest.xml
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/images/StreamImageLoaderFactory.kt
  • stream-chat-android-ui-components/detekt-baseline.xml

Copy link
Copy Markdown
Contributor

@gpunto gpunto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good on my side, but I'm not familiar with some of the changes that went into develop

Comment on lines +277 to +279
if (attachments.size < metadata.size) {
hasUnresolvedAttachments = true
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, I think this check can never be true.

storageHelper.toAttachments(metadata) uses .map internally, so it always returns the same number of items as metadata. It never drops anything. It just stores the content URI in extraData for later resolution. So attachments.size < metadata.size is always false, and hasUnresolvedAttachments will never be set here.

On develop, this worked because the equivalent code was calling getAttachmentsForUpload(), which eagerly cached files using getCachedFileFromUri() + mapNotNull. So items that couldn't be resolved were actually dropped at that point. But in v7, the file resolution is deferred to send time (resolveAttachmentFiles), so nothing gets filtered here anymore.

In practice this means: if a user picks a Google Drive file via the system picker, the toast "Some files could not be loaded and were skipped" never shows. The attachment gets staged in the composer and then silently dropped when sending.

Let me think about how to properly fix that and will get back to you.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it is: 0e2fc6a

I also noticed this was missed: 6066a0c

let me know what you think

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, I missed this part. yes if this is not a performance issue, I think we can take this direction.

andremion and others added 4 commits April 2, 2026 14:41
…ickerViewModel` and `AttachmentStorageHelper`.

- Add `isUriResolvable` to `StorageHelper` to verify if a content URI can be opened for reading.
- Implement `partitionResolvable` in `AttachmentStorageHelper` to separate metadata based on URI accessibility.
- Update `AttachmentsPickerViewModel.resolveAndSubmitUris` to exclude inaccessible URIs (e.g., undownloaded cloud files) from the submission.
- Ensure `hasUnresolvedAttachments` is correctly set when URIs are inaccessible, independent of file type support.
- Add unit tests in `AttachmentStorageHelperTest` and `AttachmentsPickerViewModelTest` to verify partitioning logic and view model state updates.
# Conflicts:
#	stream-chat-android-compose/api/stream-chat-android-compose.api
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/QuotedMessageBodyBuilder.kt
#	stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/helper/internal/AttachmentStorageHelperTest.kt
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 3, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
58.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:internal Internal changes / housekeeping

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants