Skip to content

Update audio recording and file attachments design#6137

Merged
gpunto merged 13 commits intov7from
redesign/message-file-audio
Feb 10, 2026
Merged

Update audio recording and file attachments design#6137
gpunto merged 13 commits intov7from
redesign/message-file-audio

Conversation

@gpunto
Copy link
Contributor

@gpunto gpunto commented Feb 9, 2026

Goal

Update audio recording and file attachments design. For audio recording, this also entailed changing the playback speed logic to allow for 1) setting different speed per attachment and 2) changing speed while the audio is not playing

Implementation

Updated designs and audio speed controlling logic. Now it's handled per audio hash instead of being tied to a single aucio.

🎨 UI Changes

Before After
Screenshot_20260209_111155 Screenshot_20260209_145551

Testing

Can be tested in the sample by checking the designs and that the audio recording

Summary by CodeRabbit

  • New Features

    • Per-track audio speed control: each audio file now maintains its own playback speed setting independently.
  • Bug Fixes & Improvements

    • Enhanced audio attachment UI with improved layout and styling.
    • Improved file attachment rendering with better full-size support.
    • Refined playback timer display with updated visual styling.
  • Theme Updates

    • Simplified messaging theme system for better consistency.

@gpunto gpunto added the pr:new-feature New feature label Feb 9, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 9, 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.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 9, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.26 MB 5.26 MB 0.00 MB 🟢
stream-chat-android-offline 5.48 MB 5.48 MB 0.00 MB 🟢
stream-chat-android-ui-components 10.63 MB 10.62 MB -0.01 MB 🚀
stream-chat-android-compose 12.85 MB 11.66 MB -1.19 MB 🚀

@gpunto
Copy link
Contributor Author

gpunto commented Feb 9, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

Walkthrough

The PR refactors audio playback speed management to track speeds per-audio track using an audioHash, removes legacy theme classes (MessageTheme, AudioRecordingAttachmentTheme, FileAttachmentTheme) from the compose API, simplifies ChatTheme by eliminating own/other message theme parameters, and updates UI components for audio and file attachments to use simplified styling via MessageStyling utility functions.

Changes

Cohort / File(s) Summary
Audio Player Per-Track Speed Management
stream-chat-android-client/.../AudioPlayer.kt, stream-chat-android-client/.../StreamAudioPlayer.kt, stream-chat-android-client/.../StreamMediaPlayerTest.kt
Added per-audio speed tracking via speedMap: MutableMap<Int, Float>. Changed changeSpeed() signature to changeSpeed(audioHash: Int): Float, returning the new cycled speed. Speed is now tracked independently per audioHash and applied to the currently playing track when changed. Added three test cases validating per-audio speed cycling, immediate application to playing audio, and independent speed tracking per audio.
Theme Removal & ChatTheme Simplification
stream-chat-android-compose/.../ChatTheme.kt, stream-chat-android-compose/.../MessageTheme.kt, stream-chat-android-compose/.../AudioRecordingAttachmentTheme.kt, stream-chat-android-compose/.../FileAttachmentTheme.kt
Deleted MessageTheme, AudioRecordingAttachmentTheme, and FileAttachmentTheme classes entirely. Removed ownMessageTheme and otherMessageTheme parameters from ChatTheme, along with their composition locals and public accessors. Eliminates legacy theme composition infrastructure in favor of simplified styling approach.
Audio Attachment UI Refactoring
stream-chat-android-compose/.../AudioRecordAttachmentContent.kt, stream-chat-android-compose/.../AudioRecordAttachmentPreviewContent.kt, stream-chat-android-compose/.../PlaybackTimer.kt
Converted audio attachment from Card-based layout to Row-based layout with custom backgrounds and borders. Updated PlaybackTimerText signature from style: TextStyle parameter to explicit color: Color, countdown: Boolean parameters. Refactored speed button rendering and replaced per-track style theming with direct color/styling from ChatTheme.
File Attachment UI Refactoring
stream-chat-android-compose/.../FileAttachmentContent.kt, stream-chat-android-compose/.../LinkAttachmentContent.kt
Replaced file attachment Surface-based layout with Row-based layout and consistent rounding via fileAttachmentShape. Added conditional full-size behavior via shouldBeFullSize flag. Updated link attachment background color to use MessageStyling.attachmentBackgroundColor() instead of custom helper. Simplified internal component styling to rely on ChatTheme and StreamTokens.
Color & Style System Updates
stream-chat-android-compose/.../StreamColors.kt, stream-chat-android-compose/.../WaveformSliderStyle.kt, stream-chat-android-compose/.../MessageStyling.kt
Added new public color fields to StreamColors: chatBorderOnChatIncoming and chatBorderOnChatOutgoing. Changed WaveformThumbStyle default widths from 7.dp/10.dp to 0.dp. Added new attachmentBackgroundColor(outgoing: Boolean): Color method to MessageStyling.
Audio Player State & Controller
stream-chat-android-ui-common/.../AudioPlayerState.kt, stream-chat-android-ui-common/.../AudioPlayerController.kt
Added speeds: IntFloatMap property to AudioPlayerState to track speed per audioHash. Updated changeSpeed() logic to call audioPlayer.changeSpeed(audioHash) and update the speeds map accordingly. Updated onAudioPlayingSpeed() to capture audioHash and record speed in map.
UI Component Call Sites
stream-chat-android-ui-components/.../AudioRecordAttachmentPreviewFactory.kt, stream-chat-android-ui-components/.../AudioRecordingAttachmentsGroupView.kt, stream-chat-android-ui-components/.../FileAttachmentsView.kt
Updated speed button click handlers to pass audioHash (or hashCode) to audioPlayer.changeSpeed(audioHash) instead of parameterless calls, aligning with new per-audio speed tracking API.
Utility & Configuration
stream-chat-android-compose/.../ModifierUtils.kt, stream-chat-android-compose/.../ChatComponentFactory.kt, stream-chat-android-compose/.../DefaultMessageComposerRecordingContent.kt, stream-chat-android-compose-sample/.../MessagesActivity.kt
Refactored modifier utility chaining for improved readability. Reordered named arguments in FileAttachmentItem call. Simplified PlaybackTimerText usage with explicit color/countdown parameters. Removed explicit ownMessageTheme setup from sample activity.
Resource Updates
stream-chat-android-compose/.../stream_compose_ic_pause.xml
Updated pause icon vector from 24x24 to 20x20 with two filled paths and explicit color values, replacing previous tinted single-path definition.

Sequence Diagram

sequenceDiagram
    participant UI as UI Component
    participant Controller as AudioPlayerController
    participant Player as StreamAudioPlayer
    participant PlayerImpl as MediaPlayer
    
    UI->>Controller: changeSpeed(attachment)
    activate Controller
    Controller->>Player: changeSpeed(audioHash)
    activate Player
    
    Note over Player: Lookup current speed from speedMap<br/>or use INITIAL_SPEED
    
    Player->>Player: Calculate next speed<br/>(1.0x → 1.5x → 2.0x → 1.0x)
    
    alt Audio Currently Playing
        Player->>PlayerImpl: speed = newSpeed
        activate PlayerImpl
        PlayerImpl-->>Player: ✓
        deactivate PlayerImpl
    end
    
    Player->>Player: speedMap[audioHash] = newSpeed
    Player->>Player: publishSpeed(audioHash, newSpeed)
    Player-->>Controller: return newSpeed (Float)
    deactivate Player
    
    Controller->>Controller: Update speeds map<br/>Update state
    Controller-->>UI: Emit updated AudioPlayerState
    deactivate Controller
    
    UI->>UI: Display new speed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

compose

Suggested reviewers

  • VelikovPetar
  • andremion
  • aleksandar-apostolov

Poem

🐰 Hop! The speed now cycles per track,
Themes fade, the simpler path is back,
Colors bloom in MessageStyling's glow,
Attachments lean where colors flow,
Per-audio grace, cleaner all the way! 🎵

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Update audio recording and file attachments design' clearly and concisely summarizes the main change: design updates for audio recording and file attachments with speed control enhancements.
Description check ✅ Passed The description includes Goal, Implementation, and UI Changes sections with before/after screenshots. However, it is incomplete: Testing section lacks detail, the Contributor Checklist is missing, and the GIF section is empty. The provided testing explanation is minimal.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch redesign/message-file-audio

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 5

Caution

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

⚠️ Outside diff range comments (2)
stream-chat-android-client/src/main/java/io/getstream/chat/android/client/audio/StreamAudioPlayer.kt (1)

312-317: ⚠️ Potential issue | 🔴 Critical

Bug: currentIndex compared against audioHash instead of currentAudioHash.

Line 313 compares currentIndex (the index in audioTracks) with audioHash (a hash identifier). These are different domains and the comparison is almost certainly wrong. It should be currentAudioHash == audioHash.

🐛 Proposed fix
 override fun getCurrentPositionInMs(audioHash: Int): Int {
-    if (currentIndex == audioHash) {
+    if (currentAudioHash == audioHash) {
         return mediaPlayer.currentPosition
     }
     return seekMap[audioHash] ?: 0
 }
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentPreviewContent.kt (1)

87-91: ⚠️ Potential issue | 🟡 Minor

Stale KDoc: "fallback content for unsupported attachments" doesn't describe this component.

This KDoc appears to be a copy-paste from another component. It should describe the audio recording preview content item.

📝 Suggested fix
-/**
- * Represents fallback content for unsupported attachments.
- *
- * `@param` modifier Modifier for styling.
- */
+/**
+ * Represents a single audio recording attachment item in the composer preview.
+ *
+ * `@param` modifier Modifier for styling.
+ * `@param` attachment The audio recording attachment to display.
+ * `@param` playerState The state of the audio player.
+ * `@param` onPlayToggleClick Callback when the play/pause button is clicked.
+ * `@param` onThumbDragStart Callback when the waveform thumb drag starts.
+ * `@param` onThumbDragStop Callback when the waveform thumb drag stops.
+ * `@param` onAttachmentRemoved Callback when the attachment is removed.
+ */
🤖 Fix all issues with AI agents
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt`:
- Around line 252-257: AudioRecordAttachmentContentItemBase currently lacks the
isMine flag so timer text always uses chatTextIncoming; update the call sites
that instantiate AudioRecordAttachmentContentItemBase to pass the message
ownership boolean (isMine) through, add an isMine parameter to
AudioRecordAttachmentContentItemBase, and change its timerTextColor logic in
AudioRecordAttachmentContent.kt so that when playing it still uses
ChatTheme.colors.accentPrimary, otherwise it selects
ChatTheme.colors.chatTextOutgoing when isMine is true and
ChatTheme.colors.chatTextIncoming when false (mirror the pattern used in
FileAttachmentContent.kt).

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/StreamColors.kt`:
- Around line 481-482: The slate color variables in StreamColors.kt have
inverted luminance: slate300 (Color(0xFFA3ACBA)) is darker than slate400
(Color(0xFFB8BEC4)); update the values to match the design spec by either
swapping the hex values between slate300 and slate400 or replacing them with the
correct hex codes from the design spec so that higher numeric shade (slate400)
is a darker color than slate300; ensure the change is made where slate300 and
slate400 are declared in StreamColors.kt.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/WaveformSliderStyle.kt`:
- Line 61: The TODO about WaveformThumbStyle in WaveformSliderStyle.kt must be
resolved rather than left in the public API: either remove the class from the
public surface or make its intent explicit. If you want to keep it
private/unstable, annotate or move WaveformThumbStyle as internal (e.g., add an
`@InternalStreamChatApi` annotation or move it to an internal package) and remove
the TODO; if you intend it as a supported API, remove the TODO and add KDoc
describing its purpose and public contract (constructor parameters and usage) or
mark it `@Deprecated` with guidance if you plan to remove it later—update
references in WaveformSliderStyle to match the chosen option.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/ModifierUtils.kt`:
- Around line 58-61: The branch handling size.height == Dp.Infinity incorrectly
uses fillMaxSize(), which fills both dimensions and prevents the subsequent
.width(size.width) from constraining width; change fillMaxSize() to
fillMaxHeight() in that conditional (the modifier chain in the size.height ==
Dp.Infinity case) so the height is filled while width(size.width) still applies,
and add the corresponding compose modifier import if missing.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/messages/list/AudioPlayerState.kt`:
- Around line 79-81: In AudioPlayerState.stringify(), the concatenation of
"seekTo.size=${seekTo.size}" and "speeds.size=${speeds.size}" lacks a separator
causing outputs like "seekTo.size=0speeds.size=0)"; update the stringify()
implementation (method stringify in class AudioPlayerState) to insert a clear
separator (e.g., ", " or " | ") between those two interpolations so the
resulting string reads "seekTo.size=0, speeds.size=0)".
🧹 Nitpick comments (6)
stream-chat-android-client/src/test/java/io/getstream/chat/android/client/audio/StreamMediaPlayerTest.kt (1)

180-194: Potential flaky test if randomInt() produces the same value for both hashes.

While the collision probability is negligible (~1 in 2³²), you could make this fully deterministic by using fixed distinct values or adding a guard assertion.

💡 Optional: add a precondition or use fixed values
-        val audioHash1 = randomInt()
-        val audioHash2 = randomInt()
+        val audioHash1 = 1
+        val audioHash2 = 2

Or add a guard:

val audioHash1 = randomInt()
var audioHash2 = randomInt()
while (audioHash2 == audioHash1) audioHash2 = randomInt()
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/audio/PlaybackTimer.kt (1)

49-72: Preview uses ChatPreviewTheme instead of @StreamPreview helpers.

The coding guidelines state that Compose previews should use @StreamPreview helpers. The rest of the file's preview composables in the codebase may follow the same pattern, so this might be an existing convention — flagging for awareness.

As per coding guidelines, **/stream-chat-android-compose/**/*.kt: "Compose previews should use @StreamPreview helpers".

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt (2)

306-323: SpeedButton uses raw Text + clickable instead of a semantically accessible button.

The PlaybackToggleButton properly uses StreamButton (which applies Role.Button), but SpeedButton manually composes Text with a .clickable modifier. This means it lacks button semantics for accessibility (TalkBack won't announce it as a button).

Consider wrapping with StreamButton for consistency, or at minimum adding .semantics { role = Role.Button }.


325-345: Preview uses ChatPreviewTheme instead of @StreamPreview helpers.

The coding guidelines state that Compose previews should use @StreamPreview helpers. This applies to both this file and the preview content file. If ChatPreviewTheme is the established pattern and @StreamPreview refers to it, this is fine — but worth confirming consistency. Based on learnings: "Compose previews should use StreamPreview helpers."

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/FileAttachmentContent.kt (2)

154-155: Open TODO: // TODO [G.] semantic value?

This TODO questions whether fileAttachmentShape should have a semantic name/token. Note that AudioRecordAttachmentContent.kt (line 110) uses the same RoundedCornerShape(StreamTokens.radiusLg) inline. If you resolve this by promoting the shape to a shared token (e.g., in StreamTokens or ChatTheme.shapes), both files could reference it.

Would you like me to open an issue to track unifying the attachment shape into a shared token?


258-272: Preview functions use bare ChatTheme while audio previews use ChatPreviewTheme.

ChatPreviewTheme builds a ChatClient before wrapping in ChatTheme. If these previews don't require ChatClient, bare ChatTheme is fine, but the inconsistency across files in this PR is worth noting. Consider aligning on one pattern.

@gpunto gpunto force-pushed the redesign/message-file-audio branch 3 times, most recently from 7a23b74 to 136d3b1 Compare February 9, 2026 13:51
@gpunto gpunto force-pushed the redesign/message-file-audio branch from 136d3b1 to 58545bc Compare February 9, 2026 15:31
@gpunto
Copy link
Contributor Author

gpunto commented Feb 9, 2026

@coderabbitai review

@gpunto gpunto marked this pull request as ready for review February 9, 2026 16:16
@gpunto gpunto requested a review from a team as a code owner February 9, 2026 16:16
@gpunto gpunto force-pushed the redesign/message-file-audio branch from a31d2b7 to 603f3a2 Compare February 10, 2026 08:18
Copy link
Contributor

@VelikovPetar VelikovPetar left a comment

Choose a reason for hiding this comment

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

Left couple of questions but looks pretty good!

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
47.2% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@gpunto gpunto merged commit d2c1d8f into v7 Feb 10, 2026
14 of 15 checks passed
@gpunto gpunto deleted the redesign/message-file-audio branch February 10, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:new-feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants