Skip to content

Update audio recording design and refactor theming into ChatConfig + ChatComponentFactory#6159

Merged
andremion merged 37 commits intov7from
redesign/AND-1039-update-audio-recording-compose-design
Feb 17, 2026
Merged

Update audio recording design and refactor theming into ChatConfig + ChatComponentFactory#6159
andremion merged 37 commits intov7from
redesign/AND-1039-update-audio-recording-compose-design

Conversation

@andremion
Copy link
Contributor

@andremion andremion commented Feb 16, 2026

Goal

Update the audio recording UI in the Compose SDK to match the new design specifications. This involves a visual overhaul of the entire audio recording flow, including the idle button, hold content, locked content, overview content, floating icons, and controls. Additionally, it involves migrating behavioral configuration from AudioRecordingTheme to ChatConfig and extracting customizable UI pieces into ChatComponentFactory.

Implementation

Theming cleanup: Removed AudioRecordingTheme and all its sub-themes (AudioRecordingFloatingIconsTheme, AudioRecordingControlsTheme, AudioRecordingPlaybackTheme, AudioRecordingHoldToRecordTheme, AudioRecordingSlideToCancelTheme, IconContainerStyle), along with the unused showRecordButtonOverSend flag.

Behavioral config via ChatConfig: Introduced ChatConfig / ComposerConfig to hold audioRecordingEnabled and audioRecordingSendOnComplete, decoupling behavior from visual theming. Accessible through ChatTheme.config.

Factory-based customization: Added ChatComponentFactory methods for hold content, locked content, permission rationale, recording hint, and floating lock icon.

UI redesign: Updated the idle mic button, hold/locked/overview content, floating lock icon, and MessageInput background to follow the new design specs. Added pop-in scale animation for floating icons and an enter transition for hold content. The mic button now stays visually pressed while the "hold to record" hint is visible. New icons for lock-open, lock-closed, chevrons, play, pause, and stop.

StreamSnackbar: Introduced a reusable themed StreamSnackbar / StreamSnackbarHost, replacing PermanentlyDeniedPermissionSnackBar and other ad-hoc snackbar usages. Audio recording permission rationale now uses this component.

Cleanup: Deleted the deprecated AudioWaveVSeekbar. Added borderCoreDefault and backgroundElevationElevation1 color tokens to StreamColors.

🎨 UI Changes

Audio Recording Permissions RTL
Screen_recording_20260217_091853.webm
Screen_recording_20260217_091941.webm
Screen_recording_20260217_114314.webm

Testing

  1. Open the Compose sample app and navigate to a channel
  2. Tap the mic button — verify the "hold to record" hint snackbar appears and the button stays in a pressed state while the hint is visible
  3. Hold the mic button — verify the hold content appears with an animated entrance, waveform bars and timer are visible, and the floating mic and lock icons pop in with a scale animation
  4. While holding, drag up past the lock threshold — verify the recording locks, the lock icon updates to a closed state, and the control buttons (delete, stop, complete) appear
  5. While locked, tap delete — verify the recording is discarded
  6. Record again, lock, tap stop — verify the recording stops and the overview content is shown with playback controls
  7. In overview, tap complete — verify the voice message is sent
  8. While holding, slide horizontally to cancel — verify the recording is cancelled
  9. Deny microphone permission, then tap the mic button again — verify the permission rationale snackbar appears
  10. Deny storage permission permanently in the attachment picker — verify the StreamSnackbar appears with an action to open settings

Summary by CodeRabbit

Release Notes

  • New Features

    • Centralized audio recording configuration through new config system for easier customization
    • Improved snackbar UI with enhanced styling across the app
    • Enhanced permission handling with snackbar-based rationale flow
  • Improvements

    • Redesigned audio recording UI with updated visual states
    • Message input styling refined with new shape and color treatment
    • Expanded icon library with lock icons and updated media control icons
  • Removals

    • Removed deprecated audio waveform seekbar component
    • Streamlined audio recording theming by consolidating into centralized configuration

@andremion andremion added the pr:new-feature New feature label Feb 16, 2026
@andremion andremion changed the base branch from develop to v7 February 16, 2026 17:19
@github-actions
Copy link
Contributor

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

DB Entities have been updated. Do we need to upgrade DB Version?
Modified Entities :

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channel/internal/ChannelEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/channelconfig/internal/ChannelConfigEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/attachment/internal/AttachmentEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/attachment/internal/ReplyAttachmentEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/DraftMessageEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/MessageEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/Poll.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/message/internal/ReplyMessageEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/queryChannels/internal/QueryChannelsEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/reaction/internal/ReactionEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/syncState/internal/SyncStateEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/threads/internal/ThreadEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/threads/internal/ThreadOrderEntity.kt%0Astream-chat-android-client/src/main/java/io/getstream/chat/android/client/internal/offline/repository/domain/user/internal/UserEntity.kt

@andremion andremion force-pushed the redesign/AND-1039-update-audio-recording-compose-design branch from 130adec to 44e5c25 Compare February 16, 2026 17:22
@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.66 MB 0.41 MB 🟡
stream-chat-android-ui-components 10.60 MB 10.82 MB 0.23 MB 🟢
stream-chat-android-compose 12.81 MB 11.86 MB -0.96 MB 🚀

@andremion andremion force-pushed the redesign/AND-1039-update-audio-recording-compose-design branch from 44e5c25 to c6f1981 Compare February 17, 2026 09:07
@andremion andremion changed the title Update the audio recording compose design Update audio recording design and refactor theming into ChatConfig + ChatComponentFactory Feb 17, 2026
@andremion andremion marked this pull request as ready for review February 17, 2026 09:23
@andremion andremion requested a review from a team as a code owner February 17, 2026 09:23
@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Walkthrough

This PR refactors the audio recording feature and snackbar system in Stream Chat Android Compose. It centralizes audio recording configuration into a new ChatConfig structure with ComposerConfig, removes the deprecated AudioWaveVSeekbar component, introduces a new StreamSnackbar system, and updates associated UI components, permissions handling, and resources accordingly.

Changes

Cohort / File(s) Summary
Configuration & Theme Architecture
ChatConfig.kt, ChatTheme.kt, MessageComposerTheme.kt, StreamColors.kt
Introduces new ChatConfig and ComposerConfig data classes to centralize audio recording settings (enabled, sendOnComplete). Removes audioRecording from MessageComposerTheme. Adds LocalChatConfig composition local and public ChatTheme.config accessor. Adds backgroundElevationElevation1 color to StreamColors and new neutral900 primitive color.
Audio Recording Components (Core)
AudioRecordingButton.kt, AudioRecordingContent.kt, AudioRecordingGesture.kt, AudioRecordingPermission.kt
Redesigns audio recording UI and gesture handling. Replaces Column with Box layout in button component; introduces FloatingMicState and entrance animations; adds new constants (SlideToCancelThreshold, MicButtonSize, LockThreshold). Refactors permission handling to use snackbar-based rationale instead of popup. Delegates recording content to new factory methods in ChatComponentFactory.
Snackbar System Replacement
StreamSnackbar.kt, SnackbarPopup.kt, MessageInput.kt
Creates new StreamSnackbar and StreamSnackbarHost composables. Replaces SnackbarHost with StreamSnackbarHost in multiple files; updates SnackbarPopup to default to StreamSnackbar. Updates color references in MessageInput to use new theme colors.
Attachment & Media UI
MediaGalleryPreviewScreen.kt, ChannelMediaAttachmentsPreviewScreen.kt, AttachmentFilePicker.kt, AttachmentMediaPicker.kt
Replaces SnackbarHost with StreamSnackbarHost in media preview screens. Replaces deprecated PermanentlyDeniedPermissionSnackBar with StreamSnackbarHost and handles SnackbarResult.ActionPerformed to open system settings.
Removed Permission & Audio Components
PermanentlyDeniedPermissionSnackBar.kt, AudioRecordingTheme.kt, AudioWaveSeekbar.kt
Removes deprecated permission snackbar composable, large audio recording theme data class hierarchy, and AudioWaveVSeekbar component.
Composer & Factory Updates
MessageComposer.kt, ChatComponentFactory.kt, MessageComposerInputTrailingContent.kt
Updates references to audio recording configuration from ChatTheme.messageComposerTheme.audioRecording.* to ChatTheme.config.composer.*. Expands ChatComponentFactory with six new public composables for audio recording UI composition (lock icon, hold/locked/overview content, hints, rationale).
Drawable Resources
stream_compose_ic_*.xml
Updates 10+ icon/drawable resources: resizes and recolors chevrons (left, top), stop, play, pause, mic icons to black (#000000); adds new lock open/closed icons; removes mic_lock and mic_locked icons; adds autoMirrored support to play icon.
Test & Documentation
MessageComposerScreenTest.kt, AudioRecordingButtonTest.kt, AudioRecordingContentTest.kt, StreamSnackbarTest.kt, MessageComposer.kt (docs), CustomComposerAndAttachmentsPicker.kt (docs)
Updates test setup to use ChatConfig(composer = ComposerConfig(...)) instead of MessageComposerTheme. Adds new snapshot tests for audio recording content variants and StreamSnackbar. Updates documentation examples with new config paths.
Public API Changes
stream-chat-android-compose.api
Adds ChatConfig, ComposerConfig, StreamSnackbar, StreamSnackbarHost to public API. Removes AudioWaveSeekbarKt, audio recording theme classes (AudioRecordingTheme, AudioRecordingControlsTheme, etc.). Updates ChatTheme signatures to include config parameter. Modifies MessageComposerTheme to remove audioRecording component.
Sample Application
MessagesActivity.kt
Replaces inline messageComposerTheme customization with ChatConfig(composer = ComposerConfig(audioRecordingEnabled = true)) configuration pattern.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #6150: Modifies message composer audio-recording and snackbar surfaces alongside updates to ChatComponentFactory, MessageComposer, and snackbar system integration.

Suggested labels

pr:breaking-change

Poem

🐰 Hops with glee, config takes the stage,
Audio recordings now, a modern page!
Snackbars streaming, icons shining bright,
Themes refactored, UI set right! 🎚️✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: updating audio recording design and refactoring configuration/customization into ChatConfig and ChatComponentFactory.
Linked Issues check ✅ Passed The PR fully implements the audio recording redesign requirements from AND-1039 by redesigning UI states, refactoring theming into ChatConfig/ChatComponentFactory, and providing testing verification steps.
Out of Scope Changes check ✅ Passed All changes are directly related to the audio recording redesign and theme refactoring objectives; no unrelated modifications detected in the changeset.
Description check ✅ Passed The PR description is well-structured and comprehensive, following the template closely with clear Goal, Implementation, and UI Changes sections. Testing steps are detailed and actionable.

✏️ 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/AND-1039-update-audio-recording-compose-design

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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: 3

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-compose/src/main/res/drawable/stream_compose_ic_mic.xml (1)

25-31: ⚠️ Potential issue | 🟡 Minor

Keep placeholder color consistent with tinted icon convention.

For stream_compose_ic_*.xml, the placeholder color is expected to be a stable base since tint is applied in Compose. Changing it to black can cause inconsistent appearance in previews or any path that forgets to tint. Please either revert to the established placeholder color or confirm the design spec requires this base color and that all usages always apply tint. Based on learnings: “For drawable XMLs named stream_compose_ic_*.xml … use hardcoded placeholder colors because tint is applied programmatically when used in composables.”

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

In `@stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_mic.xml`
around lines 25 - 31, The stroke color in stream_compose_ic_mic.xml was changed
to hardcoded black; revert android:strokeColor (and any other hardcoded path
colors) to the shared placeholder color used by other stream_compose_ic_*.xml
drawables (i.e., the same value used for android:fillColor in the family) so
tinting in Compose works consistently, or if the design spec truly requires a
different base, confirm and apply that spec uniformly across all
stream_compose_ic_*.xml files.
🧹 Nitpick comments (7)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingPermission.kt (1)

125-136: Consider guarding against concurrent show() calls.

Each show() call launches a new coroutine. If invoked in quick succession, SnackbarHostState.showSnackbar will dismiss the current snackbar and re-show, causing a brief flicker. This is unlikely in practice (the trigger is a permission denial), but a simple guard would make it more robust.

♻️ Optional: debounce with a `currentSnackbarData` check
     fun show() {
+        if (snackbarHostState.currentSnackbarData != null) return
         scope.launch {
             val result = snackbarHostState.showSnackbar(
🤖 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/messages/composer/internal/AudioRecordingPermission.kt`
around lines 125 - 136, The show() function currently launches a new coroutine
every call which can dismiss and re-show the snackbar when invoked concurrently;
guard against concurrent calls by preventing re-entry—e.g., before calling
snackbarHostState.showSnackbar in show(), check
snackbarHostState.currentSnackbarData and return early if a snackbar is already
displayed, or keep a reference to the active Job/Boolean flag (e.g., isShowing)
scoped to this helper and skip launching if active; ensure the guard is cleared
after showSnackbar completes so onAction still runs when the action is
performed.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingGesture.kt (1)

31-32: SlideToCancelThreshold is defined here while LockThreshold lives in AudioRecordingButton.kt.

Both constants are consumed together in the same RecordingGestureConfig construction (AudioRecordingButton.kt lines 300-301). Consider co-locating them for discoverability, either both here (near the gesture logic) or both in the button file.

🤖 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/messages/composer/internal/AudioRecordingGesture.kt`
around lines 31 - 32, SlideToCancelThreshold is defined in
AudioRecordingGesture.kt while LockThreshold lives in AudioRecordingButton.kt,
which makes discovery harder when both are used to build RecordingGestureConfig;
move one constant so both live together—either relocate LockThreshold into
AudioRecordingGesture.kt next to SlideToCancelThreshold, or move
SlideToCancelThreshold into AudioRecordingButton.kt—then update references in
RecordingGestureConfig construction and any usages of SlideToCancelThreshold or
LockThreshold to the new location to avoid duplicate definitions or unresolved
imports.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingButton.kt (2)

160-213: State-transition detection during composition is correct here but worth a brief comment.

The prevHolding read-then-write pattern (lines 166-170) is a recognized Compose idiom for detecting state transitions. It works without recomposition loops because isReturning only flips once per Hold→Idle edge and stabilizes on the next recomposition. A small inline comment noting this is intentional would help future readers.

🤖 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/messages/composer/internal/AudioRecordingButton.kt`
around lines 160 - 213, Add a short inline comment inside
rememberFloatingMicState explaining that the prevHolding read-then-write pattern
(the remember { mutableStateOf(false) } assigned to prevHolding and then updated
based on isHolding) is intentionally used to detect the Hold→Idle transition,
and that toggling isReturning once per transition is safe and will not cause
recomposition loops; reference the prevHolding, isHolding, and isReturning
variables in the comment so future readers understand this Compose idiom and why
it stabilizes on the next recomposition.

458-513: Verify the lock icon y-offset math when isLocked changes content height.

When transitioning from Hold (contentHeight = RecordingRowHeight) to Locked (contentHeight = RecordingRowHeight + ControlsRowHeight), the lock icon jumps because contentHeight doubles in the offset calculation. Since the transition is sudden (no animation on contentHeight), this could cause a visual discontinuity. If the design intends a smooth lock transition, consider animating the content height component of the offset.

🤖 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/messages/composer/internal/AudioRecordingButton.kt`
around lines 458 - 513, The lock icon jumps because contentHeight is switched
instantly between RecordingRowHeight and RecordingRowHeight + ControlsRowHeight
inside MessageComposerAudioRecordingFloatingLockIcon, so animate the height used
in the offset (instead of switching it abruptly): compute contentHeightDp from
the same values (RecordingRowHeight and ControlsRowHeight), use animateDpAsState
(or animateIntAsState after converting to px) to produce an
animatedContentHeight, and then use that animated value when computing offset
(along with dragOffsetY) so the Popup y-offset transitions smoothly when
isLocked changes.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingContent.kt (2)

368-374: Previews should use @StreamPreview instead of @Preview.

The coding guidelines require Compose previews to use @StreamPreview helpers. The three preview functions here use the standard @Preview annotation.

As per coding guidelines: "Compose previews should use @StreamPreview helpers".

Also applies to: 386-392, 405-411

🤖 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/messages/composer/internal/AudioRecordingContent.kt`
around lines 368 - 374, Replace the standard Compose `@Preview` annotations on the
three preview functions (e.g., AudioRecordingHoldContentPreview and the other
two preview functions referenced) with the project helper `@StreamPreview` and
update/remove the corresponding import for
androidx.compose.ui.tooling.preview.Preview to import
io.getstream.chat.android.compose.preview.StreamPreview; keep the function
bodies (e.g., calls to AudioRecordingHoldContent) unchanged so the previews use
the StreamPreview wrapper as required by the coding guidelines.

143-149: offset.y is computed but never consumed by RecordingSlideToCancelIndicator.

RecordingSlideToCancelIndicator (line 266) hardcodes y = 0 in its own offset {}, so the y component of the IntOffset passed here is silently discarded. Either remove y from the parameter or use it in the indicator.

🤖 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/messages/composer/internal/AudioRecordingContent.kt`
around lines 143 - 149, The call builds an IntOffset with state.offsetY but
RecordingSlideToCancelIndicator ignores y (it hardcodes y = 0); either remove
the unused y here or update the indicator to consume it. Fix options: (A) change
this call to pass y = 0 (i.e., remove state.offsetY usage) if vertical offset is
not intended, or (B) update RecordingSlideToCancelIndicator (referenced by name)
to accept and use the provided IntOffset.y (use the incoming offset.y inside its
offset { ... } rather than hardcoding 0). Choose one approach and make the
corresponding change so the y value is not silently discarded.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/StreamSnackbar.kt (1)

51-96: Consider honoring SnackbarVisuals.withDismissAction.
StreamSnackbar ignores withDismissAction, so snackbars that request a dismiss affordance won’t render one. If parity with Material3 Snackbar is expected, add a dismiss action or document the limitation.

🤖 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/util/StreamSnackbar.kt`
around lines 51 - 96, StreamSnackbar currently ignores
SnackbarVisuals.withDismissAction; update the StreamSnackbar composable to check
snackbarData.visuals.withDismissAction and render a dismiss affordance (e.g., a
small IconButton or StreamTextButton) that calls snackbarData::dismiss when
present, placing it alongside or after the existing action button so
layout/spacing (Row, Arrangement.spacedBy) remains consistent; reference
StreamSnackbar, snackbarData.visuals.withDismissAction, and
snackbarData::dismiss when implementing the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingContent.kt`:
- Around line 321-348: RecordingControlButton currently sets Icon
contentDescription to null and provides no accessibility semantics on the
IconButton; update the RecordingControlButton composable to accept a
contentDescription: String? parameter (or `@StringRes` id if strings are
localized) and use it to 1) set the Icon's contentDescription and 2) add a
semantics label on the IconButton (e.g., Modifier.semantics {
this.contentDescription = contentDescription }) so screen readers announce the
button purpose (reference RecordingControlButton, IconButton, and Icon in your
changes); ensure callers pass meaningful strings for delete/stop/confirm.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt`:
- Around line 1887-2011: Add brief KDoc lines to each new public composable to
state threading and state expectations: for
MessageComposerAudioRecordingFloatingLockIcon,
MessageComposerAudioRecordingPermissionRationale,
MessageComposerAudioRecordingHoldContent,
MessageComposerAudioRecordingLockedContent,
MessageComposerAudioRecordingOverviewContent, and
MessageComposerAudioRecordingHint add a short "Threading: Main thread only" and
"State: Stateless — driven entirely by parameters" (or equivalent phrasing) to
their KDocs so consumers know they must be called on the UI thread and that
these composables hold no internal state beyond the passed-in parameters.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/StreamSnackbar.kt`:
- Around line 139-162: The preview composables StreamSnackbarMessageOnlyPreview
and StreamSnackbarWithActionPreview use `@Preview` but should use the project
helper `@StreamPreview`; update the annotations on these functions (and any other
preview helpers in this file) to `@StreamPreview` and add the corresponding import
for StreamPreview so the previews follow the module's Compose preview convention
(look for StreamSnackbarMessageOnlyPreview, StreamSnackbarWithActionPreview, and
StreamSnackbarMessageOnly to locate the spots to change).

---

Outside diff comments:
In `@stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_mic.xml`:
- Around line 25-31: The stroke color in stream_compose_ic_mic.xml was changed
to hardcoded black; revert android:strokeColor (and any other hardcoded path
colors) to the shared placeholder color used by other stream_compose_ic_*.xml
drawables (i.e., the same value used for android:fillColor in the family) so
tinting in Compose works consistently, or if the design spec truly requires a
different base, confirm and apply that spec uniformly across all
stream_compose_ic_*.xml files.

---

Duplicate comments:
In `@stream-chat-android-compose/api/stream-chat-android-compose.api`:
- Around line 3417-3431: There is a duplicate reviewer comment about
"ComposerConfig defaults check"; remove the redundant check/comment and
consolidate the validation into a single location. Locate usages/tests/reviewer
notes referencing ComposerConfig (class
io.getstream.chat.android.compose.ui.theme.ComposerConfig and its methods like
getAudioRecordingEnabled/getAudioRecordingSendOnComplete/component1/component2)
and delete the duplicate assertion or comment block so only one definitive
defaults check remains, updating any nearby test descriptions or comments for
clarity.
- Line 3677: The declaration component120-0d7_KjU is a duplicate of
functionality already covered by the StreamColors defaults check above; remove
this redundant generated accessor (component120-0d7_KjU) from the API surface or
consolidate it with the existing component function to avoid duplicate
declarations, and ensure any callers reference the single, canonical component
exposed by the StreamColors defaults instead.
- Line 3798: The API file contains a duplicate accessor declaration
getBackgroundElevationElevation1-0d7_KjU which is already covered by the
StreamColors defaults check above; remove this duplicate declaration (or
consolidate it with the existing accessor) so only the single, canonical
getBackgroundElevationElevation1-0d7_KjU accessor remains in the API definition
to avoid redundancy and potential build conflicts.

---

Nitpick comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingButton.kt`:
- Around line 160-213: Add a short inline comment inside
rememberFloatingMicState explaining that the prevHolding read-then-write pattern
(the remember { mutableStateOf(false) } assigned to prevHolding and then updated
based on isHolding) is intentionally used to detect the Hold→Idle transition,
and that toggling isReturning once per transition is safe and will not cause
recomposition loops; reference the prevHolding, isHolding, and isReturning
variables in the comment so future readers understand this Compose idiom and why
it stabilizes on the next recomposition.
- Around line 458-513: The lock icon jumps because contentHeight is switched
instantly between RecordingRowHeight and RecordingRowHeight + ControlsRowHeight
inside MessageComposerAudioRecordingFloatingLockIcon, so animate the height used
in the offset (instead of switching it abruptly): compute contentHeightDp from
the same values (RecordingRowHeight and ControlsRowHeight), use animateDpAsState
(or animateIntAsState after converting to px) to produce an
animatedContentHeight, and then use that animated value when computing offset
(along with dragOffsetY) so the Popup y-offset transitions smoothly when
isLocked changes.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingContent.kt`:
- Around line 368-374: Replace the standard Compose `@Preview` annotations on the
three preview functions (e.g., AudioRecordingHoldContentPreview and the other
two preview functions referenced) with the project helper `@StreamPreview` and
update/remove the corresponding import for
androidx.compose.ui.tooling.preview.Preview to import
io.getstream.chat.android.compose.preview.StreamPreview; keep the function
bodies (e.g., calls to AudioRecordingHoldContent) unchanged so the previews use
the StreamPreview wrapper as required by the coding guidelines.
- Around line 143-149: The call builds an IntOffset with state.offsetY but
RecordingSlideToCancelIndicator ignores y (it hardcodes y = 0); either remove
the unused y here or update the indicator to consume it. Fix options: (A) change
this call to pass y = 0 (i.e., remove state.offsetY usage) if vertical offset is
not intended, or (B) update RecordingSlideToCancelIndicator (referenced by name)
to accept and use the provided IntOffset.y (use the incoming offset.y inside its
offset { ... } rather than hardcoding 0). Choose one approach and make the
corresponding change so the y value is not silently discarded.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingGesture.kt`:
- Around line 31-32: SlideToCancelThreshold is defined in
AudioRecordingGesture.kt while LockThreshold lives in AudioRecordingButton.kt,
which makes discovery harder when both are used to build RecordingGestureConfig;
move one constant so both live together—either relocate LockThreshold into
AudioRecordingGesture.kt next to SlideToCancelThreshold, or move
SlideToCancelThreshold into AudioRecordingButton.kt—then update references in
RecordingGestureConfig construction and any usages of SlideToCancelThreshold or
LockThreshold to the new location to avoid duplicate definitions or unresolved
imports.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingPermission.kt`:
- Around line 125-136: The show() function currently launches a new coroutine
every call which can dismiss and re-show the snackbar when invoked concurrently;
guard against concurrent calls by preventing re-entry—e.g., before calling
snackbarHostState.showSnackbar in show(), check
snackbarHostState.currentSnackbarData and return early if a snackbar is already
displayed, or keep a reference to the active Job/Boolean flag (e.g., isShowing)
scoped to this helper and skip launching if active; ensure the guard is cleared
after showSnackbar completes so onAction still runs when the action is
performed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/StreamSnackbar.kt`:
- Around line 51-96: StreamSnackbar currently ignores
SnackbarVisuals.withDismissAction; update the StreamSnackbar composable to check
snackbarData.visuals.withDismissAction and render a dismiss affordance (e.g., a
small IconButton or StreamTextButton) that calls snackbarData::dismiss when
present, placing it alongside or after the existing action button so
layout/spacing (Row, Arrangement.spacedBy) remains consistent; reference
StreamSnackbar, snackbarData.visuals.withDismissAction, and
snackbarData::dismiss when implementing the change.

Copy link
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 and especially the spring animation feels great!

This commit enhances the accessibility of the audio recording feature by adding content descriptions to various UI elements. These changes ensure a better experience for users relying on screen readers.

Specifically, the following elements now have content descriptions:
- Audio recording progress slider
- Play/pause button
- Delete, stop, and send recording control buttons
- Lock recording icon
The slide-to-cancel and slide-to-lock gestures for audio recording were not working correctly in right-to-left (RTL) layouts.

This has been fixed by making the gesture detection logic aware of the layout direction, ensuring the drag gestures behave as expected irrespective of the UI orientation. The enter animation for the `HoldContent` has also been updated to respect the layout direction.
@andremion andremion enabled auto-merge (squash) February 17, 2026 11:46
@sonarqubecloud
Copy link

@andremion andremion merged commit e19765c into v7 Feb 17, 2026
15 checks passed
@andremion andremion deleted the redesign/AND-1039-update-audio-recording-compose-design branch February 17, 2026 12:09
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