Skip to content

Conversation

@xsahil03x
Copy link
Member

@xsahil03x xsahil03x commented Sep 25, 2025

Submit a pull request

Fixes: FLU-225
Persistence PR: #2395

Description of the pull request

This PR introduces the ability to delete a message only for the current user.

The following changes are included:

  • Added MessageDeleteScope to represent the scope of deletion for a message.
  • Added deleteMessageForMe method to StreamChatClient and Channel to delete a message only for the current user.
  • Updated Message model to include deletedOnlyForMe field.
  • Updated Event model to include deletedForMe field.
  • Updated Member model to include deletedMessages field.
  • Updated MessageState to include deletingForMe, deletedForMe, and deletingForMeFailed states.
  • Updated MessageApi to include delete_for_me parameter in deleteMessage method.
  • Updated sample app to include "Delete Message for Me" option in message actions.

Screenshots / Videos

Demo
Screen.Recording.2025-09-25.at.11.17.45.mov

Summary by CodeRabbit

  • New Features

    • "Delete for me" action to remove a message for the current user.
  • Improvements

    • Choose soft vs hard deletes for everyone; hard deletes also remove server attachments.
    • Message states and indicators now reflect deletion scope (e.g., deleted for me).
    • Retry and deletion flows handle scope-aware outcomes.
    • Consistent sentence-case labels across commands, attachment actions, and Giphy controls.
  • Documentation

    • Updated guidance for deletion options including "Delete for me".

This commit introduces the ability to delete a message only for the current user.

The following changes are included:
- Added `MessageDeleteScope` to represent the scope of deletion for a message.
- Added `deleteMessageForMe` method to `StreamChatClient` and `Channel` to delete a message only for the current user.
- Updated `Message` model to include `deletedOnlyForMe` field.
- Updated `Event` model to include `deletedForMe` field.
- Updated `Member` model to include `deletedMessages` field.
- Updated `MessageState` to include `deletingForMe`, `deletedForMe`, and `deletingForMeFailed` states.
- Updated `MessageApi` to include `delete_for_me` parameter in `deleteMessage` method.
- Updated sample app to include "Delete Message for Me" option in message actions.
This commit introduces a new `sentenceCase` extension for strings and replaces all usages of the deprecated `capitalize` extension with it.

The `sentenceCase` extension converts a string to sentence case, where the first letter is capitalized and the rest of the string is in lowercase. This provides more accurate capitalization for various UI elements.

The `capitalize` extension has been deprecated and will be removed in a future release.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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.

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

Walkthrough

Adds per-user ("for me") deletion scope: introduces MessageDeleteScope, makes message state and API scope-aware, adds Channel/Client delete-for-me methods, updates models/serialization/tests, adjusts attachment and local-delete flows, and updates Flutter UI casing utilities and sample app action.

Changes

Cohort / File(s) Summary
Scope model & export
packages/stream_chat/lib/src/core/models/message_delete_scope.dart, .../message_delete_scope.freezed.dart, .../message_delete_scope.g.dart, packages/stream_chat/lib/stream_chat.dart
Adds sealed MessageDeleteScope (DeleteForMe, DeleteForAll{hard}), JSON wiring, helper constants, and exports the model.
Message state → scope-aware
packages/stream_chat/lib/src/core/models/message_state.dart, .../message_state.freezed.dart, .../message_state.g.dart
Replaces boolean hard with MessageDeleteScope across Deleting/Deleted/DeletingFailed; adds scope-specific factories, getters, JSON changes, and scope-aware state helpers and constants.
Core models updated
packages/stream_chat/lib/src/core/models/message.dart, .../message.g.dart, packages/stream_chat/lib/src/core/models/event.dart, .../event.g.dart, packages/stream_chat/lib/src/core/models/member.dart, .../member.g.dart
Adds deletedForMe to Message and Event, deletedMessages to Member; integrates field into (de)serialization, state computation, and copy/merge paths.
Client / Channel / API changes
packages/stream_chat/lib/src/client/channel.dart, packages/stream_chat/lib/src/client/client.dart, packages/stream_chat/lib/src/core/api/message_api.dart
Channel: scope-aware _deleteMessage with local-delete path, _deleteLocalMessage, _deleteMessageAttachments, and public deleteMessage/deleteMessageForMe. Client: new deleteMessageForMe(String) and removal of persistence-side local delete in deleteMessage. MessageApi: adds deleteForMe param and validation.
Generated Freezed/JSON updates
packages/stream_chat/lib/src/core/models/*.freezed.dart, .../*.g.dart
Regenerated Freezed and JSON code to reflect MessageDeleteScope and scope-bearing message states, copyWith and serialization changes across affected unions.
Flutter UI casing utilities & usages
packages/stream_chat_flutter/lib/src/utils/extensions.dart, .../message_widget/message_widget.dart, .../attachment/builder/url_attachment_builder.dart, .../attachment_actions_modal/attachment_actions_modal.dart, .../autocomplete/stream_command_autocomplete_options.dart, .../message_widget/giphy_ephemeral_message.dart
Adds sentenceCase getter to shared extensions, deprecates capitalize() (delegates to sentenceCase), removes duplicate local sentenceCase extension, and updates UI label usages to sentenceCase.
Tests & fixtures
packages/stream_chat/test/src/client/channel_test.dart, .../client/client_test.dart, .../core/models/message_state_test.dart, .../core/models/member_test.dart, packages/stream_chat/test/fixtures/member.json, packages/stream_chat/test/src/core/api/message_api_test.dart
Updates tests to expect scope-based states, adds deleteMessageForMe tests and API expectations, updates fixture deleted_messages, and expands message-state tests for scopes.
Sample app: Delete for me action
sample_app/lib/pages/channel_page.dart
Adds DeleteMessageForMe custom action, confirmation modal, _deleteMessageForMe handler calling channel.deleteMessageForMe(message), and wires action into the UI; minor icon changes.
Message actions (Flutter)
packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart
Appends a destructive delete action for failed messages (HardDeleteMessage) in the failed-message action list.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant UI as App UI
  participant Channel as Channel
  participant Client as Client
  participant API as MessageApi
  participant Server

  rect rgba(220,240,255,0.5)
  User->>UI: Tap "Delete for me"
  UI->>Channel: deleteMessageForMe(message)
  Channel->>Client: deleteMessageForMe(message.id)
  Client->>API: deleteMessage(id, deleteForMe=true)
  API->>Server: DELETE /messages/{id}?delete_for_me=true
  Server-->>API: 200 EmptyResponse
  API-->>Client: EmptyResponse
  Client-->>Channel: EmptyResponse
  Channel->>Channel: update message -> Deleted(scope: DeleteForMe)
  Channel-->>UI: notify updated message
  end

  alt Delete for all (soft/hard)
    UI->>Channel: deleteMessage(message, hard=?)
    Channel->>Client: deleteMessage(message.id, hard)
    Client->>API: deleteMessage(id, hard=flag)
    API->>Server: DELETE /messages/{id}?hard=flag
    Server-->>API: 200
    API-->>Client: EmptyResponse
    Channel->>Channel: update message -> Deleted(scope: DeleteForAll{hard})
    opt hard delete
      Channel->>Server: delete attachments
      Server-->>Channel: 2xx
    end
    Channel-->>UI: notify updated message
  else Local-only / bounced
    UI->>Channel: deleteMessage(local)
    Channel->>Channel: remove local message (no API)
    Channel-->>UI: notify removed message
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • renefloor
  • Brazol

Poem

I thump with glee at tidy scopes,
Delete for me, or all the folks.
Soft hops, hard hops, states aligned,
Attachments vanish, neatly signed.
UI sings sentence case — bunny-approved. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning In addition to the delete-for-me feature, this PR includes unrelated refactors and UI tweaks in stream_chat_flutter such as replacing capitalize with sentenceCase across attachment builders, removing and reintroducing String extensions, and adding a delete action for failed messages, none of which align with the delete-for-me objective in FLU-225. Please isolate the delete-for-me implementation in a dedicated PR and move the text case refactors and failed-message delete action into separate review requests to keep the scope focused on FLU-225.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title “feat(llc): add delete message for me” concisely conveys the primary feature introduced by this changeset without extraneous details, follows conventional commit style, and clearly signals the scope of the update to any collaborator reviewing the history.
Linked Issues Check ✅ Passed The pull request fully implements the code requirements specified in FLU-225 by introducing the deleteMessageForMe API in both client and channel classes, extending Message, Event, Member, and MessageState models to track per-user deletions, updating the MessageApi with the delete_for_me parameter, and augmenting the sample app and test suites to exercise the new delete-for-me flow.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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.

@xsahil03x xsahil03x marked this pull request as draft September 25, 2025 05:50
@xsahil03x
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

✅ Actions performed

Review triggered.

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

Copy link
Contributor

@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)
sample_app/lib/pages/channel_page.dart (1)

396-402: Hide messages deleted only for the current user.

After “Delete for me,” the message likely remains visible because defaultFilter doesn’t check deletedOnlyForMe. Update it:

bool defaultFilter(Message m) {
  final currentUser = StreamChat.of(context).currentUser;
  final isMyMessage = m.user?.id == currentUser?.id;
  if (m.deletedOnlyForMe == true) return false;
  final isDeletedOrShadowed = m.isDeleted == true || m.shadowed == true;
  if (isDeletedOrShadowed && !isMyMessage) return false;
  return true;
}
🧹 Nitpick comments (10)
packages/stream_chat_flutter/lib/src/attachment/builder/url_attachment_builder.dart (1)

68-71: Consistent casing LGTM; consider safer host parsing (optional).

Switch to sentenceCase improves UI consistency. As an optional hardening, avoid a crash if titleLink is null/invalid by using Uri.tryParse and guarding empty hosts.

Apply:

-      final host = Uri.parse(urlPreview.titleLink!).host;
-      final splitList = host.split('.');
-      final hostName = splitList.length == 3 ? splitList[1] : splitList[0];
+      final host = Uri.tryParse(urlPreview.titleLink ?? '')?.host ?? '';
+      final splitList = host.split('.');
+      final hostName = splitList.length == 3
+          ? splitList[1]
+          : (splitList.isNotEmpty ? splitList.first : '');
       final hostDisplayName = urlPreview.authorName?.sentenceCase ??
           getWebsiteName(hostName.toLowerCase()) ??
           hostName.sentenceCase;
packages/stream_chat_flutter/lib/src/utils/extensions.dart (1)

47-60: Deprecation + new sentenceCase: good; handle grapheme clusters to avoid splitting emojis/combined chars.

Current logic uses code units; multi-codepoint graphemes (e.g., emoji, accented chars) may get split. Prefer Characters API (already used elsewhere in this file).

Apply:

   @Deprecated('Use sentenceCase instead')
-  String capitalize() => sentenceCase;
+  String capitalize() => sentenceCase;
 
   /// Returns the string in sentence case.
   ///
   /// Example: 'hello WORLD' -> 'Hello world'
   String get sentenceCase {
-    if (isEmpty) return this;
-
-    final firstChar = this[0].toUpperCase();
-    final restOfString = substring(1).toLowerCase();
-
-    return '$firstChar$restOfString';
+    if (isEmpty) return this;
+    final chars = characters;
+    final first = chars.first.toUpperCase();
+    final rest = chars.skip(1).toString().toLowerCase();
+    return '$first$rest';
   }
packages/stream_chat/lib/src/core/models/event.dart (1)

126-128: Naming consistency: align with Message.deletedOnlyForMe.
Event uses deletedForMe while Message uses deletedOnlyForMe. Consider unifying names across models to avoid mental mapping.

packages/stream_chat/lib/src/client/client.dart (1)

1760-1770: Verify backend support & document side-effects for deleteMessageForMe
Confirmed MessageApi.deleteMessage accepts and sends the delete_for_me flag. Recommend updating the Dart docs to note that calling deleteMessageForMe emits an Event.deletedForMe (updating Member.deletedMessages and MessageState.isDeletedForMe) and that channels apply an optimistic UI update.

packages/stream_chat/test/src/client/channel_test.dart (1)

2310-2381: New deleteMessageForMe flow tests are on point.

Covers happy path and the “sending/failed” early-hard-delete (local removal) behavior. Consider adding a thread-message case to ensure thread state updates mirror channel state.

packages/stream_chat/lib/src/core/models/message_delete_scope.dart (1)

6-11: Docstring fixes for accuracy (rename references and a typo).

Update references to match actual factory names and fix a minor type name typo.

-/// - [forMe]: The message is deleted only for the current user.
-/// - [forAll]: The message is deleted for all users. The [hard]
-/// parameter indicates whether the deletion is permanent (hard) or soft.
+/// - [deleteForMe]: The message is deleted only for the current user.
+/// - [deleteForAll]: The message is deleted for all users.
+///   Use [hard] to indicate whether the deletion is permanent (hard) or soft.
@@
-  /// Creates a MessageDeletionScope from a JSON map.
+  /// Creates a MessageDeleteScope from a JSON map.

Also applies to: 30-33

packages/stream_chat/lib/src/core/models/message_state.freezed.dart (1)

1107-1149: DeletingFailed now carries scope — OK.

Equality/hash and copyWith updated accordingly. Confirm error-path telemetry/logs include the scope to aid debugging.

Also applies to: 1152-1194

packages/stream_chat/test/src/core/models/member_test.dart (1)

15-15: LGTM: Asserting deletedMessages parsing.

Good coverage for the new field; consider adding a small test for copyWith(deletedMessages) later.

sample_app/lib/pages/channel_page.dart (1)

378-394: Handle errors when deleting for me.

Currently the call is ignored; surface failures to users.

Apply:

-    final channel = StreamChannel.of(context).channel;
-    return channel.deleteMessageForMe(message).ignore();
+    final channel = StreamChannel.of(context).channel;
+    try {
+      await channel.deleteMessageForMe(message);
+    } catch (e) {
+      if (!mounted) return;
+      ScaffoldMessenger.of(context).showSnackBar(
+        const SnackBar(content: Text('Failed to delete message for you')),
+      );
+    }
packages/stream_chat/lib/src/core/models/member.dart (1)

29-31: New deletedMessages field — OK, but use immutable view and deep equality.

  • Good: top-level field, default to empty list, copyWith wiring.
  • Concern: Equatable’s default List equality is by identity, not by contents. Also, exposing a mutable List can cause accidental mutation.

Recommended changes (non-generated):

  • Wrap deletedMessages in an unmodifiable view.
  • Use deep equality in props (or ensure stable identity by wrapping).

Example:

import 'package:collection/collection.dart';

final List<String> deletedMessages;
Member({
  ...
  List<String> deletedMessages = const [],
  ...
}) : deletedMessages = List.unmodifiable(deletedMessages),
     ...

@override
List<Object?> get props => [
  ...,
  const ListEquality().equals, // or just keep props but ensure unmodifiable identity
];

Alternatively, keep props as-is but store EqualUnmodifiableListView(deletedMessages) to stabilize identity.

Also applies to: 57-59, 103-108, 129-129, 147-148, 172-173

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6a52cb0 and 4c762d5.

📒 Files selected for processing (28)
  • packages/stream_chat/lib/src/client/channel.dart (6 hunks)
  • packages/stream_chat/lib/src/client/client.dart (1 hunks)
  • packages/stream_chat/lib/src/core/api/message_api.dart (1 hunks)
  • packages/stream_chat/lib/src/core/models/event.dart (5 hunks)
  • packages/stream_chat/lib/src/core/models/event.g.dart (2 hunks)
  • packages/stream_chat/lib/src/core/models/member.dart (6 hunks)
  • packages/stream_chat/lib/src/core/models/member.g.dart (2 hunks)
  • packages/stream_chat/lib/src/core/models/message.dart (8 hunks)
  • packages/stream_chat/lib/src/core/models/message.g.dart (1 hunks)
  • packages/stream_chat/lib/src/core/models/message_delete_scope.dart (1 hunks)
  • packages/stream_chat/lib/src/core/models/message_delete_scope.freezed.dart (1 hunks)
  • packages/stream_chat/lib/src/core/models/message_delete_scope.g.dart (1 hunks)
  • packages/stream_chat/lib/src/core/models/message_state.dart (30 hunks)
  • packages/stream_chat/lib/src/core/models/message_state.freezed.dart (12 hunks)
  • packages/stream_chat/lib/src/core/models/message_state.g.dart (3 hunks)
  • packages/stream_chat/lib/stream_chat.dart (1 hunks)
  • packages/stream_chat/test/fixtures/member.json (1 hunks)
  • packages/stream_chat/test/src/client/channel_test.dart (6 hunks)
  • packages/stream_chat/test/src/client/client_test.dart (2 hunks)
  • packages/stream_chat/test/src/core/models/member_test.dart (1 hunks)
  • packages/stream_chat/test/src/core/models/message_state_test.dart (7 hunks)
  • packages/stream_chat_flutter/lib/src/attachment/builder/url_attachment_builder.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/message_widget/giphy_ephemeral_message.dart (3 hunks)
  • packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart (0 hunks)
  • packages/stream_chat_flutter/lib/src/utils/extensions.dart (1 hunks)
  • sample_app/lib/pages/channel_page.dart (5 hunks)
💤 Files with no reviewable changes (1)
  • packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart
🧰 Additional context used
🪛 Gitleaks (8.28.0)
packages/stream_chat/test/src/client/client_test.dart

[high] 28-28: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (ios)
  • GitHub Check: build (android)
🔇 Additional comments (52)
packages/stream_chat_flutter/lib/src/message_widget/giphy_ephemeral_message.dart (1)

162-162: UI copy casing alignment LGTM.

Updated labels to sentenceCase are consistent with the new extension.

Also applies to: 176-176, 190-190

packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart (1)

68-69: Command title casing LGTM.

Switch to sentenceCase matches the shared extension and avoids deprecated capitalize().

packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart (1)

198-199: Delete action label casing LGTM.

Adopts sentenceCase and stays consistent with other UI labels.

packages/stream_chat_flutter/lib/src/utils/extensions.dart (1)

47-49: No lingering .capitalize() usages found. All call sites now use sentenceCase, so deprecation warnings will be clean.

packages/stream_chat/lib/src/core/models/event.dart (3)

33-33: Good addition: deletedForMe surfaced in constructor.
Wires the new flag cleanly into the model.

Please confirm which event types include deleted_for_me from the API so downstream consumers can rely on it (e.g., message.deleted vs. custom events).


196-197: Top-level field mapping looks correct.
deleted_for_me added to known fields keeps the serializer behavior consistent.


241-242: CopyWith wiring is complete.
deletedForMe is included in both the signature and the initializer; no gaps spotted.

Also applies to: 279-279

packages/stream_chat/lib/src/core/models/event.g.dart (1)

51-51: JSON mapping for deleted_for_me is correct.
Round-trips the new field as expected.

Also applies to: 109-109

packages/stream_chat/lib/src/core/models/message_delete_scope.g.dart (1)

18-27: Scope (de)serialization is solid.
Defaults and discriminators look correct.

packages/stream_chat/test/fixtures/member.json (1)

15-19: Approve fixture update and mapping
Member.deletedMessages maps to deleted_messages and is asserted in member_test.dart.

packages/stream_chat/lib/src/core/models/message_delete_scope.freezed.dart (1)

88-107: No action required: MessageDeleteScope.softDeleteForAll is already defined as a static constant in message_delete_scope.dart

packages/stream_chat/lib/src/core/models/message_state.g.dart (1)

74-82: Drop migration suggestion: no legacy top-level hard key in message_state JSON

Likely an incorrect or invalid review comment.

packages/stream_chat/lib/src/client/client.dart (1)

1747-1758: No action needed: local state and persistence are already handled
Channel.deleteMessage updates in-memory state via ChannelState.deleteMessage and invokes chatPersistenceClient.deleteMessageById, so offline data won’t remain stale.

packages/stream_chat/lib/src/core/models/message.dart (3)

86-101: Per-user delete state/type override order looks correct.

Good precedence: deletedOnlyForMe → state: deletedForMe and type: deleted; else softDeleted via deletedAt; else updated via timestamps. Keep this until backend enriches type; please track and remove the TODO once server sends enriched types.


323-327: JSON contract for deleted_for_me is consistent.

  • Field mapped from JSON: name deleted_for_me, default false.
  • Excluded from toJson and added to topLevelFields for correct extraction.
    LGTM.

Also applies to: 376-377


436-437: Propagation into copyWith/merge/props is complete.

deletedOnlyForMe correctly flows through copyWith/merge and participates in equality.

Also applies to: 515-516, 629-630

packages/stream_chat/test/src/core/models/message_state_test.dart (1)

78-111: Solid coverage for scope-aware deleting/deleted/failed states.

The tests clearly validate soft/hard DeleteForAll and DeleteForMe across Outgoing/Completed/Failed. Nicely done.

Also applies to: 140-175, 204-238, 261-299, 318-356, 395-433

packages/stream_chat/test/src/client/channel_test.dart (2)

2243-2273: Hard-delete cleans up attachments as expected.

Verifies image/file deletion counts correctly. Looks good.


7285-7343: Retry preserves delete scope correctly.

Retrying for hard/soft delete-for-all and delete-for-me routes to the right client methods. LGTM.

packages/stream_chat/lib/src/core/models/message_delete_scope.dart (1)

11-29: Union design and ergonomics look good.

Freezed union, predefined constants, and hard getter are clean and intuitive. No issues.

Also applies to: 34-47, 49-61

packages/stream_chat/lib/src/core/api/message_api.dart (1)

180-191: Rejecting incompatible query params is correct, but we still allow delete_for_me=false.

Nice guard on the mutually-exclusive flags and thanks for wiring the new query parameter.

packages/stream_chat/lib/src/core/models/message_state.dart (22)

4-4: LGTM: Proper import for new MessageDeleteScope model.

The import statement correctly references the new MessageDeleteScope model that enables scope-based message deletion.


46-50: LGTM: Scope-based soft deleting logic is correct.

The implementation correctly checks for DeleteForAll scope and ensures it's not a hard delete by checking !deletingScope.hard. The logic properly differentiates between soft and hard deletion within the DeleteForAll scope.


60-64: LGTM: Scope-based hard deleting logic is correct.

The implementation correctly identifies hard deleting by checking for DeleteForAll scope with deletingScope.hard set to true. This properly distinguishes hard deletion from soft deletion.


66-76: LGTM: New isDeletingForMe getter implementation.

The new getter correctly identifies when a message is being deleted only for the current user by checking for the DeleteForMe scope type. The logic is consistent with the pattern used for other deletion state checks.


91-91: LGTM: Comprehensive deleted state logic.

The updated isDeleted getter now correctly encompasses all deleted states: soft deleted, hard deleted, and deleted for me. This provides complete coverage of deletion completion states.


101-105: LGTM: Scope-based soft deleted logic is correct.

The implementation correctly identifies soft deleted messages by checking for DeleteForAll scope with !deletingScope.hard. This maintains consistency with the deleting state logic.


115-119: LGTM: Scope-based hard deleted logic is correct.

The implementation correctly identifies hard deleted messages by checking for DeleteForAll scope with deletingScope.hard set to true, maintaining consistency with the deleting state logic.


121-131: LGTM: New isDeletedForMe getter implementation.

The new getter correctly identifies when a message has been deleted only for the current user by checking for the DeleteForMe scope type. The implementation follows the established pattern and is consistent with other state checks.


150-151: LGTM: Comprehensive deleting failed state logic.

The updated isDeletingFailed getter now correctly encompasses all failed deletion states: soft deleting failed, hard deleting failed, and deleting for me failed. This provides complete coverage of deletion failure states.


161-165: LGTM: Scope-based soft deleting failed logic is correct.

The implementation correctly identifies soft deleting failures by checking for DeleteForAll scope with !deletingScope.hard, maintaining consistency with other soft deletion state checks.


175-179: LGTM: Scope-based hard deleting failed logic is correct.

The implementation correctly identifies hard deleting failures by checking for DeleteForAll scope with deletingScope.hard set to true, maintaining consistency with other hard deletion state checks.


181-191: LGTM: New isDeletingForMeFailed getter implementation.

The new getter correctly identifies when deleting a message for the current user has failed by checking for the DeleteForMe scope type. The implementation follows the established pattern and maintains consistency.


221-290: LGTM: Factory constructors updated to use MessageDeleteScope.

The factory constructors have been properly updated to require MessageDeleteScope instead of a boolean hard parameter. This provides better type safety and clearer intent. The constructors correctly pass the scope to the underlying state objects.


292-372: LGTM: Comprehensive static instances for all deletion scopes.

The static instances have been properly expanded to cover all deletion scenarios:

  • Hard deletion: hardDeleting, hardDeleted, hardDeletingFailed
  • Delete for me: deletingForMe, deletedForMe, deletingForMeFailed

The implementations correctly use the appropriate MessageDeleteScope values and maintain consistency across all deletion types.


385-387: LGTM: OutgoingState.deleting updated with proper defaults.

The deleting factory constructor now uses MessageDeleteScope with a sensible default of MessageDeleteScope.softDeleteForAll. This maintains backward compatibility while enabling the new scope-based functionality.


404-406: LGTM: CompletedState.deleted updated with proper defaults.

The deleted factory constructor now uses MessageDeleteScope with a sensible default of MessageDeleteScope.softDeleteForAll, maintaining consistency with the outgoing state.


435-437: LGTM: FailedState.deletingFailed updated with proper defaults.

The deletingFailed factory constructor now uses MessageDeleteScope with a sensible default of MessageDeleteScope.softDeleteForAll, maintaining consistency across all deletion-related states.


564-564: LGTM: OutgoingStatePatternMatching updated for scope parameter.

The pattern matching methods for OutgoingState have been correctly updated to pass the MessageDeleteScope scope parameter instead of a boolean hard parameter. This maintains the pattern matching API while supporting the new scope-based deletion model.

Also applies to: 579-579, 594-594


663-663: LGTM: CompletedStatePatternMatching updated for scope parameter.

The pattern matching methods for CompletedState have been correctly updated to pass the MessageDeleteScope scope parameter, maintaining consistency with the OutgoingState pattern matching updates.

Also applies to: 678-678, 693-693


765-765: LGTM: FailedStatePatternMatching updated for scope parameter.

The pattern matching methods for FailedState have been correctly updated to pass the MessageDeleteScope scope parameter, completing the consistent update of all pattern matching interfaces to support the new scope-based deletion model.

Also applies to: 787-787, 809-809


36-36: Approve deletion state logic. isDeleting now covers soft, hard, and “for me” deletion states, and all related getters and factory methods are implemented and fully tested.


570-570: No pattern matching calls to update
Searches for .when(deleting…), .whenOrNull(deleting…) and .maybeWhen(deleting…) returned no occurrences; all pattern-matching methods now use MessageDeleteScope and no caller updates are needed.

packages/stream_chat/lib/src/core/models/message_state.freezed.dart (2)

476-517: Scope-based deleting state looks correct; verify JSON wiring.

Adding scope with default MessageDeleteScope.softDeleteForAll and updating equality/hash is consistent. Please re-run build and confirm message_state.g.dart includes scope in (de)serialization for Deleting.

Also applies to: 521-561


672-714: Deleted state migrated to scope — OK; double-check server payload compatibility.

CompletedState.deleted(scope: ...) reads well. Ensure downstream consumers expect scope instead of the previous boolean (if any) to avoid deserialization errors.

Also applies to: 717-757

packages/stream_chat/lib/stream_chat.dart (1)

50-50: Publicly exporting MessageDeleteScope — OK.

This aligns the API surface with the new scope-based delete flow.

packages/stream_chat/lib/src/core/models/member.g.dart (2)

40-44: Deserialization of deleted_messages — OK.

Defaults to empty list; types are correct.


62-62: Serialization includes deleted_messages — OK.

Matches the new Member field and top-level schema.

packages/stream_chat/lib/src/core/models/message.g.dart (1)

57-57: New Message field deletedOnlyForMe (from deleted_for_me) — OK.

Not serializing it back is appropriate if server controls this flag.

Please confirm message.dart declares @JsonKey(name: 'deleted_for_me') final bool deletedOnlyForMe; (default false).

sample_app/lib/pages/channel_page.dart (3)

1-1: Ignore rules update — OK.

No concerns.


215-219: Permission gating variables — OK.

Clear separation of author check and permissions.


232-232: Icon updates — OK.

Consistent use of StreamSvgIcon/Icon variants.

Also applies to: 238-238, 244-244, 249-249

This commit renames the `DeleteForMe` class and its usages to `DeleteMessageForMe` for clarity.
The documentation for MessageDeleteScope was updated to reflect the new names for the enum values. `forMe` was changed to `deleteForMe` and `forAll` was changed to `deleteForAll`.
@xsahil03x
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

✅ Actions performed

Review triggered.

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

Copy link
Contributor

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

🧹 Nitpick comments (1)
sample_app/lib/pages/channel_page.dart (1)

441-443: Make the action class final like the others.

The other custom action types in this file are declared final class. Marking DeleteMessageForMe as final keeps the API consistent and prevents accidental extension.

Apply this diff:

-class DeleteMessageForMe extends CustomMessageAction {
+final class DeleteMessageForMe extends CustomMessageAction {
   const DeleteMessageForMe({required super.message});
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4c762d5 and 2c6dd69.

📒 Files selected for processing (3)
  • packages/stream_chat/lib/src/core/models/message_delete_scope.dart (1 hunks)
  • packages/stream_chat/test/src/client/client_test.dart (1 hunks)
  • sample_app/lib/pages/channel_page.dart (5 hunks)
🔇 Additional comments (1)
packages/stream_chat/test/src/client/client_test.dart (1)

3539-3551: Nice coverage for delete-for-me.

This test cleanly stubs the API, exercises the new client method, and asserts that we invoke deleteMessage with deleteForMe: true exactly once—perfect for guarding the new flow.

@xsahil03x
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

✅ Actions performed

Review triggered.

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

@xsahil03x xsahil03x self-assigned this Sep 25, 2025
@xsahil03x
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

✅ Actions performed

Review triggered.

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

This commit introduces a new message action that allows users to delete a message that failed to send.

The new action is added to the `MessageActionsBuilder` and is only displayed when the message state is `isSendingFailed`.
@xsahil03x
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

✅ Actions performed

Review triggered.

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

Copy link
Contributor

@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

🧹 Nitpick comments (4)
packages/stream_chat/lib/src/core/api/message_api.dart (1)

188-191: Send delete_for_me only when true (avoid sending false).

Avoid emitting delete_for_me: false; include the flag only when true to reduce ambiguity and align with common API patterns.

Apply this diff:

-      queryParameters: {
-        if (hard != null) 'hard': hard,
-        if (deleteForMe != null) 'delete_for_me': deleteForMe,
-      },
+      queryParameters: {
+        if (hard != null) 'hard': hard,
+        if (deleteForMe == true) 'delete_for_me': true,
+      },

Also confirm the backend expects boolean query params as true/false strings (not 1/0).

packages/stream_chat/lib/src/core/models/message.dart (2)

86-101: Reconsider forcing type to deleted for delete‑for‑me (optional).

Overriding type to MessageType.deleted will make isDeleted true for per‑user deletions, which may ripple into UI/logic that key off type instead of state. Since you already set state = MessageState.deletedForMe, consider leaving type unchanged and letting state drive visibility, at least once backend enriches types.

If you want to minimize behavior changes now, keep this as a temporary shim but audit any isDeleted usages to ensure they behave as intended for per‑user deletes.


323-326: Confirm JSON key mapping for deletedForMe.

@jsonkey(includeToJson: false) assumes global snake_case mapping is enabled. If fieldRename: snake isn’t configured, add an explicit name to avoid deserialization gaps.

Optionally enforce explicitly:

-  @JsonKey(includeToJson: false)
+  @JsonKey(name: 'deleted_for_me', includeToJson: false)
   final bool? deletedForMe;
packages/stream_chat/test/src/core/api/message_api_test.dart (1)

208-219: Test covers delete_for_me flag; add guard and regression tests.

Add:

  • A test asserting deleteMessage throws ArgumentError when hard and deleteForMe are both true.
  • A regression test that still verifies the hard delete path works (query hard: true).

Apply this diff to extend coverage:

   test('deleteMessage', () async {
     const messageId = 'test-message-id';

     const path = '/messages/$messageId';
     const params = {'delete_for_me': true};

     when(() => client.delete(path, queryParameters: params)).thenAnswer(
       (_) async => successResponse(path, data: <String, dynamic>{}),
     );

     final res = await messageApi.deleteMessage(messageId, deleteForMe: true);

     expect(res, isNotNull);

     verify(() => client.delete(path, queryParameters: params)).called(1);
     verifyNoMoreInteractions(client);
   });
+
+  test('deleteMessage throws when both hard and deleteForMe are true', () async {
+    const messageId = 'test-message-id';
+    expect(
+      () => messageApi.deleteMessage(
+        messageId,
+        hard: true,
+        deleteForMe: true,
+      ),
+      throwsArgumentError,
+    );
+  });
+
+  test('deleteMessage hard delete still works', () async {
+    const messageId = 'test-message-id';
+    const path = '/messages/$messageId';
+    const params = {'hard': true};
+
+    when(() => client.delete(path, queryParameters: params)).thenAnswer(
+      (_) async => successResponse(path, data: <String, dynamic>{}),
+    );
+
+    final res = await messageApi.deleteMessage(messageId, hard: true);
+    expect(res, isNotNull);
+    verify(() => client.delete(path, queryParameters: params)).called(1);
+    verifyNoMoreInteractions(client);
+  });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3f7ee96 and d7a4c06.

📒 Files selected for processing (6)
  • packages/stream_chat/lib/src/client/channel.dart (6 hunks)
  • packages/stream_chat/lib/src/core/api/message_api.dart (1 hunks)
  • packages/stream_chat/lib/src/core/models/message.dart (8 hunks)
  • packages/stream_chat/lib/src/core/models/message.g.dart (1 hunks)
  • packages/stream_chat/test/src/core/api/message_api_test.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/stream_chat/lib/src/core/models/message.g.dart
🔇 Additional comments (8)
packages/stream_chat/lib/src/client/channel.dart (1)

1022-1030: Fix _deleteLocalMessage argument ordering

ChannelClientState.deleteMessage takes the positional Message first and the named hardDelete flag second. Passing the named argument before the positional one is invalid Dart syntax and prevents the code from compiling. Please swap the order so the message comes first.

     state?.deleteMessage(
-      hardDelete: true, // Local messages are always hard deleted.
-      message.copyWith(
+      message.copyWith(
         type: MessageType.deleted,
         localDeletedAt: DateTime.now(),
         state: MessageState.hardDeleted,
       ),
+      hardDelete: true, // Local messages are always hard deleted.
     );
packages/stream_chat/lib/src/core/api/message_api.dart (1)

180-184: Good mutual‑exclusion guard; add test coverage.

The runtime check preventing hard and deleteForMe together is correct. Please add a unit test that asserts ArgumentError is thrown when both are true.

packages/stream_chat/lib/src/core/models/message.dart (6)

54-55: New field wiring looks good.

Constructor now accepts deletedForMe; consistent with the rest of the changes.


376-377: Top‑level field registration is correct.

Including deleted_for_me in topLevelFields keeps extraData handling consistent.


436-437: copyWith API updated appropriately.


515-516: copyWith propagation looks correct.


562-563: merge propagation looks correct.


629-630: Equality surface updated to include deletedForMe.

Good catch to keep Equatable props in sync.

@xsahil03x xsahil03x marked this pull request as ready for review September 25, 2025 08:43
@codecov
Copy link

codecov bot commented Sep 25, 2025

Codecov Report

❌ Patch coverage is 74.24242% with 34 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (v10.0.0@6a52cb0). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...lib/src/message_widget/message_widget_content.dart 0.00% 9 Missing ⚠️
...ib/src/message_action/message_actions_builder.dart 0.00% 5 Missing ⚠️
packages/stream_chat/lib/src/client/channel.dart 88.57% 4 Missing ⚠️
...ib/src/message_widget/giphy_ephemeral_message.dart 0.00% 3 Missing ⚠️
...ages/stream_chat/lib/src/core/api/message_api.dart 60.00% 2 Missing ⚠️
...chat/lib/src/core/models/message_delete_scope.dart 60.00% 2 Missing ⚠️
...stream_chat/lib/src/core/models/message_state.dart 95.65% 2 Missing ⚠️
...src/attachment/builder/url_attachment_builder.dart 0.00% 2 Missing ⚠️
.../stream_chat_flutter/lib/src/utils/extensions.dart 71.42% 2 Missing ⚠️
...ckages/stream_chat/lib/src/core/models/member.dart 50.00% 1 Missing ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##             v10.0.0    #2394   +/-   ##
==========================================
  Coverage           ?   65.04%           
==========================================
  Files              ?      421           
  Lines              ?    26057           
  Branches           ?        0           
==========================================
  Hits               ?    16948           
  Misses             ?     9109           
  Partials           ?        0           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@xsahil03x xsahil03x changed the title feat(llc): add delete message for me feat(llc, ui): add delete message for me Sep 25, 2025
@xsahil03x xsahil03x requested a review from renefloor September 25, 2025 17:28
@xsahil03x xsahil03x merged commit e7576f9 into v10.0.0 Sep 26, 2025
9 checks passed
@xsahil03x xsahil03x deleted the feat/delete-for-me branch September 26, 2025 14:26
@coderabbitai coderabbitai bot mentioned this pull request Nov 18, 2025
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.

3 participants