Conversation
PR checklist ✅All required conditions are satisfied:
🎉 Great job! This PR is ready for review. |
|
DB Entities have been updated. Do we need to upgrade DB Version? |
130adec to
44e5c25
Compare
SDK Size Comparison 📏
|
…icIndicator and timerTextStyle
…for as long as the "hold to record" hint Snackbar is visible.
44e5c25 to
c6f1981
Compare
WalkthroughThis PR refactors the audio recording feature and snackbar system in Stream Chat Android Compose. It centralizes audio recording configuration into a new Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟡 MinorKeep 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 concurrentshow()calls.Each
show()call launches a new coroutine. If invoked in quick succession,SnackbarHostState.showSnackbarwill 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:SlideToCancelThresholdis defined here whileLockThresholdlives inAudioRecordingButton.kt.Both constants are consumed together in the same
RecordingGestureConfigconstruction (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
prevHoldingread-then-write pattern (lines 166-170) is a recognized Compose idiom for detecting state transitions. It works without recomposition loops becauseisReturningonly 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 whenisLockedchanges content height.When transitioning from Hold (contentHeight =
RecordingRowHeight) to Locked (contentHeight =RecordingRowHeight + ControlsRowHeight), the lock icon jumps becausecontentHeightdoubles in the offset calculation. Since the transition is sudden (no animation oncontentHeight), 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@StreamPreviewinstead of@Preview.The coding guidelines require Compose previews to use
@StreamPreviewhelpers. The three preview functions here use the standard@Previewannotation.As per coding guidelines: "Compose previews should use
@StreamPreviewhelpers".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.yis computed but never consumed byRecordingSlideToCancelIndicator.
RecordingSlideToCancelIndicator(line 266) hardcodesy = 0in its ownoffset {}, so theycomponent of theIntOffsetpassed here is silently discarded. Either removeyfrom 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.
StreamSnackbarignoreswithDismissAction, 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.
...ava/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingContent.kt
Show resolved
Hide resolved
...oid-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt
Show resolved
Hide resolved
...at-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/StreamSnackbar.kt
Show resolved
Hide resolved
gpunto
left a comment
There was a problem hiding this comment.
Looks good and especially the spring animation feels great!
...java/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingButton.kt
Outdated
Show resolved
Hide resolved
...ava/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingContent.kt
Show resolved
Hide resolved
...at-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/StreamSnackbar.kt
Show resolved
Hide resolved
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.
|



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
AudioRecordingThemetoChatConfigand extracting customizable UI pieces intoChatComponentFactory.Implementation
Theming cleanup: Removed
AudioRecordingThemeand all its sub-themes (AudioRecordingFloatingIconsTheme,AudioRecordingControlsTheme,AudioRecordingPlaybackTheme,AudioRecordingHoldToRecordTheme,AudioRecordingSlideToCancelTheme,IconContainerStyle), along with the unusedshowRecordButtonOverSendflag.Behavioral config via
ChatConfig: IntroducedChatConfig/ComposerConfigto holdaudioRecordingEnabledandaudioRecordingSendOnComplete, decoupling behavior from visual theming. Accessible throughChatTheme.config.Factory-based customization: Added
ChatComponentFactorymethods 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
MessageInputbackground 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, replacingPermanentlyDeniedPermissionSnackBarand other ad-hoc snackbar usages. Audio recording permission rationale now uses this component.Cleanup: Deleted the deprecated
AudioWaveVSeekbar. AddedborderCoreDefaultandbackgroundElevationElevation1color tokens toStreamColors.🎨 UI Changes
Screen_recording_20260217_091853.webm
Screen_recording_20260217_091941.webm
Screen_recording_20260217_114314.webm
Testing
StreamSnackbarappears with an action to open settingsSummary by CodeRabbit
Release Notes
New Features
Improvements
Removals