Skip to content

Redesign message edit experience with inline indicator and save button#6195

Open
andremion wants to merge 15 commits intov7from
redesign/AND-1041-composer-edit-state-redesign
Open

Redesign message edit experience with inline indicator and save button#6195
andremion wants to merge 15 commits intov7from
redesign/AND-1041-composer-edit-state-redesign

Conversation

@andremion
Copy link
Contributor

@andremion andremion commented Feb 26, 2026

Goal

Redesign the message editing UI in the Compose SDK to match the new Figma specifications. The edit experience moves from a header banner above the composer to a card-style indicator inside the composer's input area, consistent with the quoted-message pattern.

Implementation

  • Replace the old MessageInputOptions header banner with a new MessageComposerEditIndicator card inside MessageInput's header area, using the outgoing bubble style and QuotedMessageBodyBuilder for attachment-type icons.
  • Add a SaveButton (checkmark) shown during edit mode, disabled when content is empty. Unify save/send/record resolution in MessageComposerInputTrailingContent via a sealed ActionButton interface.
  • Keep the attachment picker visible during edit mode.
  • Rename MessageComposerAudioRecordButtonMessageComposerAudioRecordingButton for naming consistency.
  • Extract MessageComposerLeadingContent into its own file; update QuotedMessage to use ChatTheme.typography tokens.

🎨 UI Changes

Check the MessageComposerInputTest snapshots

Testing

  1. Long-press a sent message → Edit. Verify the inline edit indicator appears with title, text preview, and a cancel icon. The attachment picker and save button should be visible.
  2. Clear the text — save button should disable. Re-type and tap save — message should update. Tap cancel — edit should dismiss.
  3. Edit a message with attachments — verify the indicator shows the appropriate attachment-type icon.
  4. Quote-reply to a message — verify the quoted preview still renders correctly.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added edit indicator banner in message composer showing the message being edited with preview text.
    • Introduced dedicated save button for confirming message edits.
  • Bug Fixes

    • Improved message composer layout and visibility logic for edit mode.
  • Refactor

    • Restructured message composer components for clearer edit state handling.
    • Enhanced quoted message styling with theme-based typography.

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

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

@andremion andremion changed the title Redesign message edit state Redesign message edit experience with inline indicator and save button Feb 26, 2026
@andremion andremion marked this pull request as ready for review February 26, 2026 15:29
@andremion andremion requested a review from a team as a code owner February 26, 2026 15:29
@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Walkthrough

This PR introduces edit message functionality to the Android Compose message composer. It adds a new MessageComposerEditIndicator component, refactors the composer UI structure by moving button implementations, removes the MessageInputOptions composable, and updates the public ChatComponentFactory API to support message editing with save functionality.

Changes

Cohort / File(s) Summary
UI Test Updates
stream-chat-android-compose-sample/.../MessageListPage.kt
Renamed UI selector from cancelButton to cancelEditButton to align with new edit indicator naming.
Public API Surface
stream-chat-android-compose/api/stream-chat-android-compose.api
Added new ComposableSingletons classes for MessageComposerEditIndicator and MessageComposerInputTrailingContent; expanded MessageInputKt with four new lambda fields; introduced MessageComposerEditIndicator and MessageComposerSaveButton abstract functions to ChatComponentFactory; removed MessageInputOptionsKt public class.
New Edit Indicator Component
stream-chat-android-compose/src/.../MessageComposerEditIndicator.kt
New composable that renders an edit indicator banner showing edited message preview with cancel button; includes helper composables for card layout and subtitle rendering.
Message Input Layout
stream-chat-android-compose/src/.../MessageInput.kt
Renamed MessageInputHeader to MessageInputTop, added MessageComposerEditIndicator rendering in edit mode, adjusted layout constraints and padding, removed horizontal padding from quoted/link preview content, introduced edit-state preview compositions.
Removed Edit Options
stream-chat-android-compose/src/.../MessageInputOptions.kt
Completely removed MessageInputOptions composable that previously displayed edit mode header with icon and cancel button.
Typography Styling
stream-chat-android-compose/src/.../QuotedMessage.kt
Replaced explicit FontWeight styling with theme-based typography styles (metadataEmphasis, metadataDefault) in quoted message text components.
Composer Core Simplification
stream-chat-android-compose/src/.../MessageComposer.kt
Removed conditional rendering logic for leadingContent in edit mode; now always rendered regardless of active action.
Default Implementations Cleanup
stream-chat-android-compose/src/.../MessageComposerDefaults.kt
Removed DefaultMessageComposerHeaderContent, DefaultMessageComposerLeadingContent, and SendButton; updated DefaultMessageComposerFooterInThreadMode signature to accept alsoSendToChannel boolean instead of messageComposerState.
Trailing Content Enhancement
stream-chat-android-compose/src/.../MessageComposerInputTrailingContent.kt
Added save button support in edit mode, introduced crossfade logic for Save/Send/Record actions, added internal MessageComposerSendButton and MessageComposerSaveButton helpers with styling and accessibility features.
New Leading Content
stream-chat-android-compose/src/.../MessageComposerLeadingContent.kt
New internal composable for attachments button with conditional visibility, animation support for picker state, and validation checks.
Component Factory Updates
stream-chat-android-compose/src/.../ChatComponentFactory.kt
Added public MessageComposerEditIndicator and MessageComposerSaveButton functions; removed MessageComposerMessageInputOptions; renamed MessageComposerAudioRecordButton to MessageComposerAudioRecordingButton; removed default header wrapper; added horizontal padding to link preview and quoted message components.
String Resources
stream-chat-android-compose/src/main/res/values/strings.xml
Added new string resource stream_compose_save_message with value "Save".
Test Coverage
stream-chat-android-compose/src/test/kotlin/.../MessageComposerInputTest.kt
Added three new test cases: edit(), edit empty(), and edit, attachments, and link() for validating edit mode compositions.

Sequence Diagram

sequenceDiagram
    actor User
    participant MessageComposer
    participant MessageInput
    participant MessageComposerEditIndicator
    participant MessageComposerInputTrailingContent
    participant SaveButton

    User->>MessageComposer: Initiates edit on message
    MessageComposer->>MessageInput: Sets edit state active
    MessageInput->>MessageComposerEditIndicator: Renders with edited message
    MessageComposerEditIndicator-->>User: Shows edit banner with message preview
    User->>MessageComposerEditIndicator: Clicks cancel button
    MessageComposerEditIndicator->>MessageComposer: Calls onCancelClick
    MessageComposer->>MessageInput: Clears edit state
    
    MessageInput->>MessageComposerInputTrailingContent: Checks edit state
    MessageComposerInputTrailingContent->>SaveButton: Renders save button (edit active)
    User->>SaveButton: Clicks save button
    SaveButton->>MessageComposer: Calls onClick handler
    MessageComposer-->>User: Confirms message updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • GetStream/stream-chat-android#6139: Rename of test UI element cancelButton to cancelEditButton directly aligns with the new MessageComposerEditIndicator edit control naming scheme.
  • GetStream/stream-chat-android#6066: Modifies message composer component API and ChatComponentFactory interfaces, overlapping with this PR's API restructuring and composer UI reorganization.

Suggested labels

pr:breaking-change

Suggested reviewers

  • gpunto
  • VelikovPetar

Poem

🐰 A hop, skip, and edit away—
New indicators show what's changed today,
Trailing buttons save the day,
While composers dance in a brand new way! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.47% 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 clearly and concisely describes the main change: redesigning the message edit experience with an inline indicator and save button, which aligns with the substantial refactoring shown across multiple composer files.
Description check ✅ Passed The description comprehensively covers Goal, Implementation, UI Changes, and Testing sections with specific details. All required template sections are addressed, including contributor checklist items and testing instructions.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch redesign/AND-1041-composer-edit-state-redesign

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
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: 1

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/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt (1)

1853-1870: ⚠️ Potential issue | 🔴 Critical

Update the API definition file to reflect the method rename.

The implementation has been renamed to MessageComposerAudioRecordingButton, but the API definition file (stream-chat-android-compose/api/stream-chat-android-compose.api) still contains the old method name MessageComposerAudioRecordButton at lines 2807 and 2999. The .api file must be regenerated or manually updated to reference the new method name to maintain API compatibility checking.

Additionally, the CHANGELOG.md does not document this breaking change for v7. Consider adding a migration note for consumers upgrading from v6 to document the renamed method.

🤖 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/theme/ChatComponentFactory.kt`
around lines 1853 - 1870, The API surface still references the old method name
MessageComposerAudioRecordButton while the implementation was renamed to
MessageComposerAudioRecordingButton; update the API definition
(stream-chat-android-compose.api) to replace occurrences of
MessageComposerAudioRecordButton with MessageComposerAudioRecordingButton (check
the entries that previously lived at the reported locations) or regenerate the
.api file so it matches the new symbol, and add a short migration note to
CHANGELOG.md describing the breaking rename from
MessageComposerAudioRecordButton -> MessageComposerAudioRecordingButton for v7
consumers.
🧹 Nitpick comments (2)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt (1)

1701-1724: Consider documenting the unused state parameter or using it.

The state: MessageComposerState parameter is accepted but not used in the current implementation. While this is likely intentional for API consistency with other composer methods (like MessageComposerQuotedMessage) and for consumer extensibility when overriding, consider either:

  1. Adding a brief note in the KDoc explaining it's provided for override flexibility, or
  2. Using it if there's valuable state (e.g., currentUser) that could enhance the indicator

This isn't blocking since the pattern matches other methods in this factory.

🤖 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/theme/ChatComponentFactory.kt`
around lines 1701 - 1724, The MessageComposerEditIndicator function currently
accepts a state: MessageComposerState parameter that isn't used; update the KDoc
for MessageComposerEditIndicator to explicitly state that the state parameter is
intentionally provided for API consistency and override/extensibility (similar
to MessageComposerQuotedMessage) and may be used by consumers when overriding
(e.g., to access currentUser or other composer state), or alternatively, if you
prefer behaviour change, wire a relevant value from state into the composable
(e.g., use state.currentUser) — reference the MessageComposerEditIndicator,
MessageComposerState, MessageComposerQuotedMessage and MessageComposerInput
symbols so reviewers can locate and verify the change.
stream-chat-android-compose/api/stream-chat-android-compose.api (1)

2819-2834: Document the new ChatComponentFactory hooks in upgrade notes.

Line 2819 and Line 2834 add new public customization points, with defaults at Line 3011 and Line 3026. Please ensure migration notes explicitly call these out for teams maintaining custom component factories.

Also applies to: 3011-3026

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

In `@stream-chat-android-compose/api/stream-chat-android-compose.api` around lines
2819 - 2834, Add explicit upgrade/migration notes documenting the new
ChatComponentFactory hooks by naming each new public function signature added
(MessageComposerEditIndicator, MessageComposerFooterContent,
MessageComposerHeaderContent, MessageComposerInput,
MessageComposerInputCenterContent, MessageComposerInputLeadingContent,
MessageComposerInputTrailingContent, MessageComposerLeadingContent,
MessageComposerLinkPreview, MessageComposerMentionSuggestionItem,
MessageComposerMentionSuggestionItemCenterContent,
MessageComposerMentionSuggestionItemLeadingContent,
MessageComposerMentionSuggestionItemTrailingContent,
MessageComposerMentionsPopupContent, MessageComposerQuotedMessage,
MessageComposerSaveButton) and indicate that defaults are provided (referencing
the default implementations at MessageComposer... defaults around the spots you
noted). In the upgrade notes describe that teams with custom
ChatComponentFactory implementations must implement or forward these new hooks,
show a simple migration action (add pass-through stubs for each new method in
custom factory), and call out the exact method names to update so maintainers
can locate them quickly.
🤖 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/test/kotlin/io/getstream/chat/android/compose/ui/messages/MessageComposerInputTest.kt`:
- Line 93: Rename the test function edit() to use a backtick-delimited,
sentence-style name per test conventions; locate the function named edit in
MessageComposerInputTest (fun edit()) and change its declaration to a backtick
name that describes the test intent (for example: fun `edit message shows ...`()
), keeping the body unchanged so the test still runs but follows the readable
test naming rule.

---

Outside diff comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt`:
- Around line 1853-1870: The API surface still references the old method name
MessageComposerAudioRecordButton while the implementation was renamed to
MessageComposerAudioRecordingButton; update the API definition
(stream-chat-android-compose.api) to replace occurrences of
MessageComposerAudioRecordButton with MessageComposerAudioRecordingButton (check
the entries that previously lived at the reported locations) or regenerate the
.api file so it matches the new symbol, and add a short migration note to
CHANGELOG.md describing the breaking rename from
MessageComposerAudioRecordButton -> MessageComposerAudioRecordingButton for v7
consumers.

---

Nitpick comments:
In `@stream-chat-android-compose/api/stream-chat-android-compose.api`:
- Around line 2819-2834: Add explicit upgrade/migration notes documenting the
new ChatComponentFactory hooks by naming each new public function signature
added (MessageComposerEditIndicator, MessageComposerFooterContent,
MessageComposerHeaderContent, MessageComposerInput,
MessageComposerInputCenterContent, MessageComposerInputLeadingContent,
MessageComposerInputTrailingContent, MessageComposerLeadingContent,
MessageComposerLinkPreview, MessageComposerMentionSuggestionItem,
MessageComposerMentionSuggestionItemCenterContent,
MessageComposerMentionSuggestionItemLeadingContent,
MessageComposerMentionSuggestionItemTrailingContent,
MessageComposerMentionsPopupContent, MessageComposerQuotedMessage,
MessageComposerSaveButton) and indicate that defaults are provided (referencing
the default implementations at MessageComposer... defaults around the spots you
noted). In the upgrade notes describe that teams with custom
ChatComponentFactory implementations must implement or forward these new hooks,
show a simple migration action (add pass-through stubs for each new method in
custom factory), and call out the exact method names to update so maintainers
can locate them quickly.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt`:
- Around line 1701-1724: The MessageComposerEditIndicator function currently
accepts a state: MessageComposerState parameter that isn't used; update the KDoc
for MessageComposerEditIndicator to explicitly state that the state parameter is
intentionally provided for API consistency and override/extensibility (similar
to MessageComposerQuotedMessage) and may be used by consumers when overriding
(e.g., to access currentUser or other composer state), or alternatively, if you
prefer behaviour change, wire a relevant value from state into the composable
(e.g., use state.currentUser) — reference the MessageComposerEditIndicator,
MessageComposerState, MessageComposerQuotedMessage and MessageComposerInput
symbols so reviewers can locate and verify the change.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 686c6e7 and bac05f0.

⛔ Files ignored due to path filters (11)
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.reactionpicker_ReactionsPickerTest_Default_reaction_picker.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.reactionpicker_ReactionsPickerTest_Reaction_picker_with_header.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_attachments_and_link.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_edit,_attachments,_and_link.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_edit.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_edit_empty.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_link.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_reply,_attachments,_and_link.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_reply.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageListTest_scroll_to_bottom_button.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageListTest_scroll_to_bottom_button_in_dark_mode.png is excluded by !**/*.png
📒 Files selected for processing (13)
  • stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt
  • stream-chat-android-compose/api/stream-chat-android-compose.api
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageComposerEditIndicator.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInput.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInputOptions.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/composer/MessageComposer.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerDefaults.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerInputTrailingContent.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerLeadingContent.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt
  • stream-chat-android-compose/src/main/res/values/strings.xml
  • stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/MessageComposerInputTest.kt
💤 Files with no reviewable changes (2)
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerDefaults.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInputOptions.kt

@github-actions
Copy link
Contributor

github-actions bot commented Feb 26, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.69 MB 0.44 MB 🟡
stream-chat-android-ui-components 10.60 MB 10.97 MB 0.37 MB 🟡
stream-chat-android-compose 12.81 MB 11.96 MB -0.86 MB 🚀

@andremion andremion force-pushed the redesign/AND-1041-composer-edit-state-redesign branch 2 times, most recently from 0f31873 to 357a5fc Compare February 27, 2026 08:38
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.

We have an issue when editing a message, now that support for adding attachments during edit is allowed. In the MessageCompsoerController#sendMessage we have the following logic:

if (isInEditMode && !activeMessage.isModerationError(currentUserId)) {

            // The following line will prevent sending the call, if we didn't edit the message - and just changed the attachments
            if (activeMessage.text == message.text) {
                logger.i { "[sendMessage] No changes in the message text, skipping edit." }
                clearData()
                return
            }
            val editCall = getEditMessageCall(message)
            clearData()
            editCall.enqueue(callback)
            return
        }

We should either remove this check, or extend to also verify for no changes in the attachments field as well

@andremion
Copy link
Contributor Author

We should either remove this check, or extend to also verify for no changes in the attachments field as well

Good catch, @VelikovPetar
Let me check that

- Remove `MessageInputOptions` in favor of a new `MessageComposerEditIndicator` within the component factory.
- Introduce `MessageComposerSaveButton` with a checkmark icon, replacing the send button when in edit mode.
- Consolidate input top content (replies, edits, attachments, and link previews) into a unified `MessageInputTop` layout with improved spacing.
- Update `QuotedMessage` to use theme typography and better alignment.
- Refactor trailing content logic to explicitly handle "save", "send", and "record" states.
- Ensure leading content (like the attachment picker button) remains visible while editing a message.
- Add `stream_compose_save_message` string resource and corresponding test previews for edit states.
@andremion andremion force-pushed the redesign/AND-1041-composer-edit-state-redesign branch from 357a5fc to 136b742 Compare February 27, 2026 16:41
@andremion
Copy link
Contributor Author

We should either remove this check, or extend to also verify for no changes in the attachments field as well

@VelikovPetar , Let me know what you think 5067089

@sonarqubecloud
Copy link

@andremion andremion enabled auto-merge (squash) February 27, 2026 16:56
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