Skip to content

feat(ui): add snackbar feedback for user actions - #2872

Draft
xsahil03x wants to merge 4 commits into
masterfrom
feat/action-snackbars
Draft

feat(ui): add snackbar feedback for user actions#2872
xsahil03x wants to merge 4 commits into
masterfrom
feat/action-snackbars

Conversation

@xsahil03x

@xsahil03x xsahil03x commented Aug 6, 2026

Copy link
Copy Markdown
Member

Linear: FLU-682

🎯 Goal

Give users success/error snackbar feedback for actions that were previously fire-and-forget (.ignore()), silently swallowing both success and failures. RN is the reference here — its addNotification system surfaces feedback for ~66 call sites; the Flutter SDK surfaced almost none. The snackbar infrastructure (StreamSnackbar / StreamSnackbarMessenger) already exists in stream_core_flutter, so this is a wiring effort.

Also incorporates the cross-SDK decision (Slack thread, Aug 2026) to add a "Message copied to clipboard" snackbar — none of the SDKs had one.

🛠 What changed

SDK (stream_chat_flutter) — success/error snackbars for:

  • Message actions: copy, pin/unpin, delete, flag, mark unread, mute/unmute user (centralized in _onActionTap)
  • Poll: ending a poll
  • Audio: recording-permission denial (surfaced via the existing RecordStateIdle bridge)
  • Composer: message send/edit failures — only when no onError handler is supplied

The messenger is resolved from the page context before any await, so the snackbar still shows across confirmation dialogs and even if the message is removed by the action (e.g. delete).

Localizations (stream_chat_localizations) — 13 new keys implemented across all 11 locales.

Sample app — channel/member action snackbars (mute/pin/leave/delete channel, mute/block member) across the channel-list swipe, detail sheet, and chat/group info screens.

🧪 Testing

  • New end-to-end widget tests drive the real flow (long-press → actions modal → tap → snackbar) for pin success/error and copy.
  • Whole-package flutter analyze clean; stream_chat_flutter full non-golden suite 993 passing; stream_chat_localizations 23/23 (every locale implements all new keys).

📝 Notes / follow-ups

  • Translation strings for the 10 non-English locales were machine-generated and are asserted non-null only — worth a native-speaker pass (the bool-branch strings like muted/unmuted could be swapped without failing the test).
  • The audio-permission snackbar shows the message but has no "Open Settings" action button (RN has one) — deferred.
  • Sample-app snackbars were analyzed + reasoned about but not run in-app.
  • Out of scope (no Flutter UI to attach to): channel archive/hide/truncate, standalone flag-user.

✅ Contributor checklist

  • Changelog updated (stream_chat_flutter, stream_chat_localizations)
  • melos run analyze passes
  • melos run format passes
  • Tests added / updated
  • Native-speaker review of new translations
  • Manual QA in the sample app

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added success and error snackbar feedback for message actions, user moderation, poll ending, channel actions, and message send/edit failures.
    • Added clear guidance when audio-recording permission is denied.
    • Channel and group deletion or leaving now return to the previous screen only after successful completion.
  • Localization
    • Added translated feedback messages across all supported languages.
  • Tests
    • Expanded coverage for message action feedback and localization entries.

xsahil03x and others added 2 commits August 6, 2026 17:28
Wire user-facing success/error snackbars (via StreamSnackbar) for message
actions (copy, pin/unpin, delete, flag, mark unread, mute/unmute user),
ending a poll, audio-recording permission denial, and message send/edit
failures. Adds the supporting translations across all locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Show success/error snackbars for muting/pinning/leaving/deleting a channel
and muting/blocking a member, across the channel list swipe action, the
channel detail sheet, and the chat/group info screens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9231ce75-1239-470d-b3d3-5b2199451f32

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/stream_chat_flutter/test/src/message_widget/stream_message_item_test.dart (2)

55-117: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Generalize the long-press helper to remove the duplicated gesture block.

openActionsAndTapPin performs the long press and then taps "Pin to Conversation". The copy test repeats the same long-press sequence inline. Parameterize the helper with the action label.

♻️ Proposed refactor
-    Future<void> openActionsAndTapPin(WidgetTester tester) async {
+    Future<void> openActionsAndTap(WidgetTester tester, String actionLabel) async {
       final gesture = await tester.startGesture(
         tester.getCenter(find.byType(StreamMessageItem).first),
       );
       await tester.pump(const Duration(milliseconds: 700));
       await gesture.up();
       await tester.pumpAndSettle();
 
-      await tester.tap(find.text('Pin to Conversation'));
+      await tester.tap(find.text(actionLabel));
       await tester.pumpAndSettle();
     }

Then call openActionsAndTap(tester, 'Pin to Conversation') in the pin tests and openActionsAndTap(tester, 'Copy Message') in the copy test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_chat_flutter/test/src/message_widget/stream_message_item_test.dart`
around lines 55 - 117, Generalize openActionsAndTapPin into an
action-label-based helper, such as openActionsAndTap, while preserving its
existing long-press and settling behavior. Pass the requested label to the tap
lookup, update both pin tests to use “Pin to Conversation,” and replace the
duplicated gesture sequence in the copy test with the helper using “Copy
Message.”

79-88: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a call verification to the pin error test.

The success test verifies channel.pinMessage. The error test asserts only the snackbar text. Add the same verification so a regression that skips the call is detected.

💚 Proposed addition
       await openActionsAndTapPin(tester);
 
+      verify(() => channel.pinMessage(message)).called(1);
       expect(find.text('Error pinning message'), findsOneWidget);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_chat_flutter/test/src/message_widget/stream_message_item_test.dart`
around lines 79 - 88, Add a verification in the “shows an error snackbar when
pinning fails” test that confirms channel.pinMessage was called with message
after openActionsAndTapPin, matching the success test’s interaction assertion
while preserving the existing snackbar expectation.
packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart (1)

12-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Import poll_attachment.dart through the barrel instead of directly from src/.

package:stream_chat_flutter/src/utils/extensions.dart is already exported by packages/stream_chat_flutter/lib/stream_chat_flutter.dart. Move or add this import through the barrel so poll_attachment.dart still follows the package guideline for src files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart` at line
12, Update the imports in poll_attachment.dart to reference the public
stream_chat_flutter.dart barrel instead of importing src/utils/extensions.dart
directly, while preserving access to the same extensions.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_controller.dart`:
- Around line 73-75: In the permission-denied branch of the audio recorder
controller, cancel the pending info timer before assigning
RecordStateIdle(message: permissionDeniedMessage). Reuse the existing
timer-management mechanism associated with showInfo/onLongPressCancel so the
callback cannot later clear the denial message.

In `@sample_app/lib/utils/action_feedback.dart`:
- Around line 4-5: Update the documentation for the action feedback helper to
clarify that when messenger is null, action still executes and only success or
error snackbar feedback is omitted.

---

Nitpick comments:
In `@packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart`:
- Line 12: Update the imports in poll_attachment.dart to reference the public
stream_chat_flutter.dart barrel instead of importing src/utils/extensions.dart
directly, while preserving access to the same extensions.

In
`@packages/stream_chat_flutter/test/src/message_widget/stream_message_item_test.dart`:
- Around line 55-117: Generalize openActionsAndTapPin into an action-label-based
helper, such as openActionsAndTap, while preserving its existing long-press and
settling behavior. Pass the requested label to the tap lookup, update both pin
tests to use “Pin to Conversation,” and replace the duplicated gesture sequence
in the copy test with the helper using “Copy Message.”
- Around line 79-88: Add a verification in the “shows an error snackbar when
pinning fails” test that confirms channel.pinMessage was called with message
after openActionsAndTapPin, matching the success test’s interaction assertion
while preserving the existing snackbar expectation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ac428ac5-a463-4658-a20d-62792ef2b401

📥 Commits

Reviewing files that changed from the base of the PR and between 4b4c2ae and bf9ff57.

📒 Files selected for processing (25)
  • packages/stream_chat_flutter/CHANGELOG.md
  • packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart
  • packages/stream_chat_flutter/lib/src/localization/translations.dart
  • packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_controller.dart
  • packages/stream_chat_flutter/lib/src/message_input/stream_chat_message_input.dart
  • packages/stream_chat_flutter/lib/src/message_input/stream_message_composer.dart
  • packages/stream_chat_flutter/lib/src/message_widget/stream_message_item.dart
  • packages/stream_chat_flutter/test/src/message_widget/stream_message_item_test.dart
  • packages/stream_chat_localizations/CHANGELOG.md
  • packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart
  • packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart
  • packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart
  • packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart
  • packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart
  • packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart
  • packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart
  • packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart
  • packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart
  • packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart
  • packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart
  • packages/stream_chat_localizations/test/translations_test.dart
  • sample_app/lib/pages/chat_info_screen.dart
  • sample_app/lib/pages/group_info_screen.dart
  • sample_app/lib/utils/action_feedback.dart
  • sample_app/lib/widgets/channel_list.dart

Comment thread sample_app/lib/utils/action_feedback.dart Outdated
xsahil03x and others added 2 commits August 6, 2026 20:11
- Cancel the pending audio info timer before showing the permission-denied
  message so a stale timer can't clear it.
- Narrow the feedback try/catch to `on Exception` so programming errors
  surface instead of being masked by a friendly snackbar.
- Clarify the `pinned` param convention on the pin snackbar getters.
- Fix the null-messenger doc contract (the action still runs).
- Add delete success/error tests through the confirmation dialog; generalize
  the test action helper and verify the call in the pin error test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Convert message action `switch` statement to a switch expression in `StreamMessageItem`.
- Replace `if (mounted)` block with a guard clause in `StreamMessageComposer` and extract error message selection into a local variable.
- Inline `StreamSnackbar` definitions and method arguments in `PollAttachment` and `StreamChatMessageInput` for cleaner code formatting.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant