Skip to content

feat(llc, core, ui): message reminders #2269

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

xsahil03x
Copy link
Member

@xsahil03x xsahil03x commented May 30, 2025

Submit a pull request

Linear: FLU-143

Screenshots / Videos

Screen.Recording.2025-06-03.at.13.21.29.mov

Summary by CodeRabbit

  • New Features

    • Introduced message reminders: users can now create, edit, and delete reminders for chat messages.
    • Added a dedicated Reminders tab in the app, allowing users to view, filter, and manage all their reminders.
    • Messages with reminders are visually highlighted and offer quick actions such as "Remind me" or "Remove from later."
    • Reminders support both scheduled notifications and "saved for later" bookmarks.
    • Reminders can be managed directly from messages or the Reminders tab, with intuitive dialogs for scheduling.
  • Enhancements

    • Reminders list supports filtering (e.g., overdue, upcoming, saved for later), pagination, and real-time updates.
    • Reminder status indicators dynamically update to show time remaining or overdue status.
  • Chores

    • Added the jiffy package dependency for improved date/time handling.

Copy link
Contributor

coderabbitai bot commented May 30, 2025

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.

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

Walkthrough

This update introduces comprehensive support for message reminders across the chat SDK and sample app. It adds new data models, API methods, event types, and UI components for creating, updating, deleting, and listing reminders on messages, including real-time event handling and reminder management interfaces in both the core library and the Flutter sample application.

Changes

File(s) Change Summary
.../client/channel.dart, .../client/client.dart Added methods for creating, updating, deleting, and handling message reminders at the channel and client level.
.../core/api/reminders_api.dart, .../core/api/stream_chat_api.dart Introduced RemindersApi for backend reminder operations and added accessor to StreamChatApi.
.../core/api/responses.dart, .../core/api/responses.g.dart Added response models and deserializers for reminder-related API responses.
.../core/models/message_reminder.dart, .../core/models/message_reminder.g.dart Introduced MessageReminder model with JSON serialization/deserialization.
.../core/models/message.dart, .../core/models/message.g.dart Added optional reminder field to Message model, updated serialization and copy/merge logic.
.../core/models/event.dart, .../core/models/event.g.dart Added userId and reminder fields to Event model, updated serialization and copy logic.
.../core/models/channel_config.dart, .../core/models/channel_config.g.dart Added userMessageReminders boolean to ChannelConfig, updated serialization.
.../event_type.dart Added new event types for reminder creation, update, deletion, and due notifications.
.../stream_chat.dart Exported MessageReminder model.
.../flutter_core/lib/src/stream_message_reminder_list_controller.dart Added controller for paginated, event-driven management of message reminders.
.../flutter_core/lib/src/stream_message_reminder_list_event_handler.dart Added mixin for handling reminder-related events in the controller.
.../flutter_core/lib/stream_chat_flutter_core.dart Exported reminder list controller and event handler.
sample_app/lib/pages/channel_list_page.dart Added "Reminders" tab to the main navigation.
sample_app/lib/pages/channel_page.dart Enhanced message UI with reminder actions and indicators, refactored message builder.
sample_app/lib/pages/reminders_page.dart Added new RemindersPage for listing, filtering, and managing message reminders.
sample_app/lib/widgets/reminder_dialog.dart Added dialogs for creating and editing reminders/bookmarks.
sample_app/pubspec.yaml Added jiffy dependency for date/time handling.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant ReminderController
    participant StreamChatClient
    participant RemindersApi
    participant Backend

    User->>UI: Selects "Remind me" on a message
    UI->>ReminderController: Request to create reminder
    ReminderController->>StreamChatClient: createReminder(messageId, remindAt)
    StreamChatClient->>RemindersApi: createReminder(messageId, remindAt)
    RemindersApi->>Backend: POST /messages/{messageId}/reminders
    Backend-->>RemindersApi: Reminder created response
    RemindersApi-->>StreamChatClient: Reminder response
    StreamChatClient-->>ReminderController: Reminder response
    ReminderController-->>UI: Update reminders list
    UI-->>User: Show reminder indicator

    Backend-->>StreamChatClient: Emits reminder.created event
    StreamChatClient-->>ReminderController: Event stream
    ReminderController-->>UI: Update reminders list
Loading

Assessment against linked issues

Objective (Issue) Addressed Explanation
Implement message reminders: create, update, delete, list, and real-time updates (FLU-143)
Add reminder UI/UX in sample app: reminders tab, dialog, message actions (FLU-143)
Add backend and model support for reminders (FLU-143)
Add reminder event types and handle event-driven updates (FLU-143)

Poem

In the warren of code, a new feature appears,
Reminders for messages—no more missed cheers!
With bookmarks and snoozes, and actions galore,
Rabbits can nap, but miss nothing anymore.
From backend to frontend, the changes are neat—
🐇 Now every message reminder is a treat!


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@xsahil03x
Copy link
Member Author

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented May 30, 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: 8

🧹 Nitpick comments (8)
packages/stream_chat/lib/src/core/api/responses.dart (1)

769-773: Fix the comment to accurately describe the reminder field.

The comment incorrectly refers to "Draft returned by the api call" but should reference reminder since this is the base class for reminder responses.

/// Base Model response for draft based api calls.
class MessageReminderResponse extends _BaseResponse {
-  /// Draft returned by the api call
+  /// Reminder returned by the api call
  late MessageReminder reminder;
}
packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_event_handler.dart (1)

22-24: Fix documentation inconsistency in onMessageReminderCreated.

The documentation comment incorrectly refers to messageReminderUpdated event type but should refer to the creation event.

  /// Function which gets called for the event
-  /// [EventType.messageReminderUpdated].
+  /// [EventType.messageReminderCreated].
  ///
-  /// This event is fired when a message reminder is updated.
+  /// This event is fired when a message reminder is created.
  ///
-  /// By default, this updates the reminder in the list.
+  /// By default, this adds the reminder to the list.

Also applies to: 28-36

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

1311-1319: updateReminder(): documentation/argument mismatch

The doc-string mentions reminderId but the parameter is messageId.
If the endpoint expects a message id (which matches the usage), please update the comment; otherwise the implementation is wrong.

packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_controller.dart (5)

96-107: value override re-sorts on every assignment – avoid redundant work

Every time value is set, the list is cloned and resorted, even when the incoming PagedValue is already ordered or contains only the delta from loadMore().
For large lists this can be expensive.

  1. Short-circuit when the incoming list is already sorted:
-            items: success.items.sorted(reminderSort.compare),
+            items: success.items.isSorted(reminderSort.compare)
+                ? success.items
+                : success.items.sorted(reminderSort.compare),

isSorted can be a tiny extension that performs an O(n) check.

  1. Consider sorting only once right before exposing items to the UI rather than on every assignment.
    [performance]

176-199: Filter the event stream to save bandwidth & CPU

client.on().listen delivers all WS events, most of which are irrelevant here.
You can substantially reduce GC pressure and wake-ups by pre-filtering:

-    _reminderEventSubscription = client.on().listen((event) {
+    _reminderEventSubscription = client
+        .on(
+          EventType.reminderCreated,
+          EventType.reminderUpdated,
+          EventType.reminderDeleted,
+          EventType.notificationReminderDue,
+          EventType.connectionRecovered,
+        )
+        .listen((event) {

This also makes intent explicit.
[performance]


221-235: merge extension is non-standard – ensure null-safety & O(n²) avoidance

Nice use of merge, but:

  1. If messageId or userId can be null, (reminder.messageId, reminder.userId) forms a tuple containing null, causing MapKey collisions. Guard with ?? fallbacks or assert non-null.

  2. merge must be O(n) internally; otherwise repeated updates could degrade to O(n²). Verify the extension’s implementation or switch to a Map<(String,String),MessageReminder> internally.


240-255: deleteReminder could use the key-based approach used in updateReminder

You already compute a composite key in updateReminder; replicating that logic here would simplify the code and avoid an O(n) scan:

-    final removeIndex = currentReminders.indexWhere(
-      (it) {
-        var predicate = it.userId == reminder.userId;
-        predicate &= it.messageId == reminder.messageId;
-        return predicate;
-      },
-    );
+    final removeIndex = currentReminders.indexWhere(
+      (it) => it.userId == reminder.userId &&
+              it.messageId == reminder.messageId,
+    );

(Or keep a Set/Map keyed by the tuple for O(1) removals.)


275-279: Race-free disposal

_unsubscribeFromReminderListEvents() cancels the subscription but does not set a disposed flag.
If a pending loadMore() future completes after disposal, it can re-establish state.
Set a _isDisposed boolean and early-return in loadMore / doInitialLoad when disposed.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b3241a and 22be4e3.

📒 Files selected for processing (24)
  • packages/stream_chat/lib/src/client/channel.dart (3 hunks)
  • packages/stream_chat/lib/src/client/client.dart (2 hunks)
  • packages/stream_chat/lib/src/core/api/reminders_api.dart (1 hunks)
  • packages/stream_chat/lib/src/core/api/responses.dart (2 hunks)
  • packages/stream_chat/lib/src/core/api/responses.g.dart (1 hunks)
  • packages/stream_chat/lib/src/core/api/stream_chat_api.dart (2 hunks)
  • packages/stream_chat/lib/src/core/models/channel_config.dart (2 hunks)
  • packages/stream_chat/lib/src/core/models/channel_config.g.dart (2 hunks)
  • packages/stream_chat/lib/src/core/models/event.dart (8 hunks)
  • packages/stream_chat/lib/src/core/models/event.g.dart (3 hunks)
  • packages/stream_chat/lib/src/core/models/message.dart (8 hunks)
  • packages/stream_chat/lib/src/core/models/message.g.dart (2 hunks)
  • packages/stream_chat/lib/src/core/models/message_reminder.dart (1 hunks)
  • packages/stream_chat/lib/src/core/models/message_reminder.g.dart (1 hunks)
  • packages/stream_chat/lib/src/event_type.dart (1 hunks)
  • packages/stream_chat/lib/stream_chat.dart (1 hunks)
  • packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_controller.dart (1 hunks)
  • packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_event_handler.dart (1 hunks)
  • packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart (1 hunks)
  • sample_app/lib/pages/channel_list_page.dart (3 hunks)
  • sample_app/lib/pages/channel_page.dart (5 hunks)
  • sample_app/lib/pages/reminders_page.dart (1 hunks)
  • sample_app/lib/widgets/reminder_dialog.dart (1 hunks)
  • sample_app/pubspec.yaml (1 hunks)
🔇 Additional comments (46)
sample_app/pubspec.yaml (1)

34-34:

✅ Verification successful

Verify the jiffy package version for currency and security.

The jiffy ^6.2.1 dependency addition is appropriate for the message reminders feature, as it provides excellent DateTime manipulation and formatting capabilities. However, ensure this version is current and free from security vulnerabilities.


🌐 Web query:

What is the latest stable version of the jiffy Dart package and are there any known security vulnerabilities in version 6.2.1?

💡 Result:

Latest Stable Version of the Jiffy Dart Package

The latest stable version of the Jiffy package for Dart and Flutter is 6.4.3 as of May 2025[1][3]. This version is compatible with Dart 3, supports all major platforms, and includes several improvements over previous versions.

Known Security Vulnerabilities in Jiffy 6.2.1

No security advisories or known vulnerabilities have been published for Jiffy version 6.2.1. Searches of CVE databases and Dart's official security advisory listings return no results for vulnerabilities associated specifically with the jiffy package, including version 6.2.1[6][7][9]. The Dart security advisories are integrated with GitHub's advisory database, and any major or recent issues would be surfaced via tools like dart pub get; as of now, there are no alerts or advisories listed for this version.

Summary Table

Package Latest Stable Version Security Issues in 6.2.1
Jiffy (Dart/Flutter) 6.4.3 None reported as of May 2025

If you are starting a new project or maintaining an existing one, upgrading to the latest version (6.4.3) is recommended to benefit from continued bug fixes, improvements, and any future security patches[1][3][2].

Citations:


Audit of jiffy version – no security risks; consider upgrade

  • Verified that Jiffy 6.2.1 has no known security advisories or vulnerabilities.
  • The latest stable release is 6.4.3 (May 2025), which includes additional bug fixes and improvements.
  • You may continue with 6.2.1 safely, but consider updating to ^6.4.3 to benefit from the latest enhancements.
packages/stream_chat/lib/stream_chat.dart (1)

47-47: LGTM! Export follows consistent patterns.

The export for message_reminder.dart is correctly placed in alphabetical order and follows the established pattern for model exports in this library file.

packages/stream_chat/lib/src/core/api/stream_chat_api.dart (2)

12-12: LGTM! Import follows consistent patterns.

The import for RemindersApi is correctly placed in alphabetical order with other API imports.


91-93: LGTM! API integration follows established patterns.

The RemindersApi integration perfectly follows the established pattern used by other APIs in this class:

  • Proper documentation comment
  • Lazy initialization using the null-aware assignment operator
  • Private nullable field for caching
  • Consistent naming convention
packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart (1)

25-26: LGTM! Exports follow consistent architectural patterns.

The two new exports for stream_message_reminder_list_controller.dart and stream_message_reminder_list_event_handler.dart are:

  • Correctly placed in alphabetical order
  • Following the established naming convention
  • Consistent with the controller + event handler pattern used by other list management features (channels, drafts, threads)
packages/stream_chat/lib/src/core/models/channel_config.dart (1)

28-28: LGTM! Clean implementation following established patterns.

The userMessageReminders field addition follows the existing code patterns perfectly:

  • Appropriate default value of false for new feature
  • Consistent constructor parameter ordering
  • Clear documentation explaining the field's purpose
  • Follows naming conventions and field declaration patterns

Also applies to: 91-92

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

36-36: Auto-generated serialization code looks correct.

The JSON serialization/deserialization logic properly handles the new userMessageReminders field:

  • Correct snake_case JSON key mapping (user_message_reminders)
  • Appropriate null safety with ?? false default
  • Follows consistent patterns with other boolean configuration fields

Also applies to: 59-59

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

13-13: Clean import addition.

The import statement follows the existing pattern and is properly organized with other page imports.


96-104: Excellent tab implementation following established patterns.

The new "Reminders" tab is well-implemented:

  • Semantic bookmark icon choice is appropriate for reminders functionality
  • Consistent color theming using _isSelected(4) logic
  • Follows the exact same structure as existing navigation items
  • Proper index handling for the 5th tab position

149-149: Proper IndexedStack integration.

The RemindersPage is correctly added to the IndexedStack at the appropriate index position matching the bottom navigation tab order.

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

95-97: Auto-generated serialization correctly handles the new reminder field.

The serialization logic properly implements the optional reminder field:

  • Appropriate null checking during deserialization
  • Correct type mapping to MessageReminder.fromJson
  • Follows the same pattern as other optional complex fields (poll, draft)
  • Proper null-safe serialization with ?.toJson()

Also applies to: 116-116

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

162-172: LGTM! Reminder event types follow established conventions.

The new reminder-related event types are properly implemented with consistent naming conventions and appropriate documentation. They follow the existing pattern of camelCase constants with dot-separated lowercase string values.

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

39-41:

❌ Incorrect review comment

Verify null safety consistency for DateTime fields.

The toJson method calls toIso8601String() on createdAt and updatedAt without null checks, but the fromJson method treats these fields as nullable. This suggests potential inconsistency in the MessageReminder model definition.


🏁 Script executed:

#!/bin/bash
# Check the MessageReminder model definition to verify DateTime field nullability
cat packages/stream_chat/lib/src/core/models/message_reminder.dart | grep -A 10 -B 5 "createdAt\|updatedAt"

Length of output: 3336


Ignore null-safety concerns for createdAt/updatedAt

The MessageReminder constructor accepts DateTime? createdAt and updatedAt but immediately defaults them to DateTime.now(), making the final fields non-nullable. Therefore, calling toIso8601String() in toJson is safe and consistent with the model.

No changes are required.

Likely an incorrect or invalid review comment.

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

11-11: LGTM! Event JSON serialization properly handles new fields.

The additions for userId and reminder fields are correctly implemented with:

  • Proper null safety handling in both serialization and deserialization
  • Appropriate JSON key mapping (user_id for userId)
  • Consistent use of conditional serialization patterns

Also applies to: 68-70, 77-77, 114-114

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

13-13: LGTM! Constructor properly includes new parameters.

The userId and reminder parameters are correctly added to the constructor following the existing parameter order and nullable patterns.

Also applies to: 43-43


59-60: LGTM! Field declarations are well-documented and consistent.

The new fields follow the established documentation pattern and proper type declarations. The userId field provides clear context about event ownership, and the reminder field enables events to carry reminder data.

Also applies to: 154-155


164-164: LGTM! Serialization fields properly registered.

The user_id and reminder keys are correctly added to topLevelFields, ensuring proper serialization handling by the Serializer utility.

Also applies to: 195-195


206-206: LGTM! copyWith method comprehensively updated.

The copyWith method properly includes the new fields in both the parameter list and the constructor call, maintaining consistency with the existing implementation pattern.

Also applies to: 236-236, 241-241, 271-271

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

447-469: LGTM! Generated code follows established patterns.

The new JSON deserialization functions for reminder responses are correctly generated and follow the same patterns as existing response deserializers. The handling of nullable fields, default values, and nested object deserialization is consistent with the rest of the codebase.

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

7-7: LGTM! Proper import added for MessageReminder.

The import statement correctly adds the MessageReminder model which is needed for the new reminder field.


71-71: LGTM! Reminder field properly added to constructor.

The reminder parameter is correctly added to the Message constructor with the appropriate nullable type.


312-315: LGTM! Reminder field properly declared with good documentation.

The reminder field is correctly declared as optional with clear documentation explaining its purpose.


364-364: LGTM! Reminder field added to topLevelFields.

The reminder field is correctly included in the topLevelFields array, which is essential for proper serialization handling.


426-426: LGTM! Reminder field properly integrated into copyWith method.

The reminder parameter is correctly added to the copyWith method signature and implementation using the null-const sentinel pattern, consistent with the existing draft field handling.

Also applies to: 507-508


553-553: LGTM! Reminder field included in merge method.

The reminder field is properly included in the merge method to ensure it's copied when combining messages.


618-618: LGTM! Reminder field added to props for equality comparison.

The reminder field is correctly included in the props getter, ensuring it's considered in equality comparisons and hash code generation.

packages/stream_chat/lib/src/core/api/responses.dart (2)

14-14: LGTM! Proper import added for MessageReminder.

The import statement correctly adds the MessageReminder model which is needed for the new response classes.


775-804: LGTM! Reminder response classes follow established patterns.

The new response classes (CreateReminderResponse, UpdateReminderResponse, and QueryRemindersResponse) are well-structured and follow the same patterns as existing response classes in the codebase. They properly:

  • Use @JsonSerializable(createToJson: false) annotation
  • Extend appropriate base classes
  • Include static fromJson factory methods
  • Handle lists with default values for QueryRemindersResponse
  • Include pagination support with the next field
packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_event_handler.dart (5)

8-8: LGTM! Well-designed mixin class for event handling.

The mixin class approach allows flexible composition and provides good default implementations for reminder event handling.


15-20: LGTM! Proper no-op implementation for connection recovery.

The connection recovery handler is appropriately implemented as a no-op with clear documentation explaining its purpose and potential use cases.


44-52: LGTM! Proper implementation for reminder updates.

The update handler correctly extracts the reminder from the event and calls the controller's update method.


60-68: LGTM! Proper implementation for reminder deletion.

The deletion handler correctly extracts the reminder from the event and calls the controller's delete method.


76-81: LGTM! Logical delegation for reminder due events.

The reminder due handler appropriately delegates to the update handler, as a due reminder should be updated in the list to reflect its new status.

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

10-91: LGTM! Well-structured API class.

The RemindersApi class follows consistent patterns with other API classes in the codebase. The use of POST for querying is appropriate given the complexity of filters and pagination parameters that may be included.

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

29-29: LGTM! Correct import addition.

The import for the new MessageReminder model is properly added.


1958-2003: LGTM! Clean integration of reminder functionality.

The reminder methods are well-implemented with:

  • Consistent delegation to the API layer
  • Clear documentation
  • Appropriate parameter handling
  • Follows established patterns in the codebase
sample_app/lib/widgets/reminder_dialog.dart (3)

4-15: LGTM! Excellent use of sealed classes for type safety.

The ReminderOption hierarchy with ScheduledReminder and BookmarkReminder provides clear type safety and makes the intent explicit. This is a well-designed pattern for distinguishing between scheduled reminders and bookmarks.


17-53: LGTM! Well-implemented create reminder dialog.

The dialog provides a clean user experience with:

  • Predefined durations that cover common use cases
  • Human-readable relative time display using Jiffy
  • Consistent Cupertino theming
  • Proper navigation handling

55-102: LGTM! Edit dialog with appropriate functionality.

The edit dialog appropriately extends the create dialog with the ability to clear due dates for non-bookmark reminders. The conditional logic for showing the "Clear due date" option is correct.

packages/stream_chat/lib/src/core/models/message_reminder.dart (5)

10-14: LGTM! Excellent use of sentinel pattern.

The _NullConst pattern is a sophisticated approach to distinguish between explicitly setting a nullable field to null versus leaving it unchanged in the copyWith method. This is a best practice for optional nullable parameters.


28-42: LGTM! Well-designed constructor with sensible defaults.

The constructor properly handles:

  • Required core identifiers
  • Optional related objects that may be populated later
  • Automatic timestamp initialization
  • Clear parameter organization

48-78: LGTM! Appropriate JSON serialization configuration.

The use of @JsonKey(includeToJson: false) for related objects (channel, message, user) is correct since these are typically populated from separate API calls and shouldn't be serialized back to the server.


84-106: LGTM! Correct implementation of copyWith with sentinel pattern.

The copyWith method correctly uses the sentinel value to distinguish between:

  • Not changing the remindAt field (when remindAt == _nullConst)
  • Explicitly setting it to null (when remindAt is null)
  • Setting it to a specific DateTime value

This allows for precise control over nullable field updates.


151-164: LGTM! Type-safe sort keys with extension types.

The MessageReminderSortKey extension type provides compile-time safety for sort field names while maintaining string compatibility with the API. This is a modern Dart pattern that prevents typos in sort field names.

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

220-224: client.createReminder(messageId) call no longer matches the new signature

After making remindAt optional (see previous comment) the call is fine; if you keep it required, this line won’t compile. Keep the two in sync.

packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_controller.dart (1)

202-208: refresh(resetValue: true) unexpectedly reverts active filters/sorts

Because resetValue: true assigns _activeFilter = filter and filter is the constructor value (see first comment), calling refresh() after dynamically changing filters reverts the controller to its initial query, surprising callers.

Consider either:

  • Removing the automatic reset when resetValue is true, or
  • Passing explicit parameters to refresh().

At minimum, document this gotcha in the dart-doc.

return QueryRemindersResponse.fromJson(response.data);
}

/// Creates a new reminder for the specified [messageId.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix documentation typo.

There's a syntax error in the documentation comment.

Apply this diff to fix the typo:

-  /// Creates a new reminder for the specified [messageId.
+  /// Creates a new reminder for the specified [messageId].
🤖 Prompt for AI Agents
In packages/stream_chat/lib/src/core/api/reminders_api.dart at line 41, the
documentation comment has a syntax error with an unclosed bracket in
"[messageId.". Fix the typo by properly closing the bracket and ensuring the
comment is syntactically correct.

Comment on lines 1295 to 1309
/// Create a reminder for the given [messageId].
///
/// Optionally, provide a [remindAt] date to set when the reminder should
/// be triggered. If not provided, the reminder will be created as a
/// bookmark type instead.
Future<CreateReminderResponse> createReminder(
String messageId, {
required DateTime remindAt,
}) {
_checkInitialized();
return _client.createReminder(
messageId,
remindAt: remindAt,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

createReminder(): Required remindAt parameter contradicts the API contract

The doc-string says remindAt is optional (bookmark vs timed reminder) but the method signature makes it required.
Down-stream code (ChannelPage, RemindersPage, etc.) already calls client.createReminder(messageId) without a 2nd argument, so this will not compile.

-Future<CreateReminderResponse> createReminder(
-  String messageId, {
-    required DateTime remindAt,
-}) {
+Future<CreateReminderResponse> createReminder(
+  String messageId, {
+  DateTime? remindAt,
+}) {

Also consider validating that an explicit remindAt is not in the past.

🤖 Prompt for AI Agents
In packages/stream_chat/lib/src/client/channel.dart around lines 1295 to 1309,
the createReminder method incorrectly marks the remindAt parameter as required,
contradicting the doc-string that states it is optional. Update the method
signature to make remindAt optional by removing the required keyword and
allowing null values. Additionally, add validation inside the method to check if
remindAt is provided and ensure it is not set to a past date, throwing an error
or handling it appropriately if it is. This will align the method with the API
contract and prevent compilation errors in downstream code.

Comment on lines +2629 to +2686
void _listenReminderCreated() {
_subscriptions.add(
_channel.on(EventType.reminderCreated).listen((event) {
final reminder = event.reminder;
if (reminder == null) return;

updateReminder(reminder);
}),
);
}

void _listenReminderUpdated() {
_subscriptions.add(
_channel.on(EventType.reminderUpdated).listen((event) {
final reminder = event.reminder;
if (reminder == null) return;

updateReminder(reminder);
}),
);
}

void _listenReminderDeleted() {
_subscriptions.add(
_channel.on(EventType.reminderDeleted).listen((event) {
final reminder = event.reminder;
if (reminder == null) return;

deleteReminder(reminder);
}),
);
}

/// Updates the [reminder] of the message if it exists.
void updateReminder(MessageReminder reminder) {
final messageId = reminder.messageId;
for (final message in messages) {
if (message.id == messageId) {
return updateMessage(
message.copyWith(reminder: reminder),
);
}
}
}

/// Deletes the [reminder] of the message if it exists.
void deleteReminder(MessageReminder reminder) {
// TODO: Delete the reminder from the persistence client.

final messageId = reminder.messageId;
for (final message in messages) {
if (message.id == messageId) {
return updateMessage(
message.copyWith(reminder: null),
);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Thread-messages are ignored when updating/deleting a reminder

updateReminder() / deleteReminder() iterate only over messages, so reminders attached to a thread reply that is not shown in channel will never be updated locally.

-for (final message in messages) {
+for (final message in [...messages, ...threads.values.expand((m) => m)]) {

Without this, the UI can display stale data.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void _listenReminderCreated() {
_subscriptions.add(
_channel.on(EventType.reminderCreated).listen((event) {
final reminder = event.reminder;
if (reminder == null) return;
updateReminder(reminder);
}),
);
}
void _listenReminderUpdated() {
_subscriptions.add(
_channel.on(EventType.reminderUpdated).listen((event) {
final reminder = event.reminder;
if (reminder == null) return;
updateReminder(reminder);
}),
);
}
void _listenReminderDeleted() {
_subscriptions.add(
_channel.on(EventType.reminderDeleted).listen((event) {
final reminder = event.reminder;
if (reminder == null) return;
deleteReminder(reminder);
}),
);
}
/// Updates the [reminder] of the message if it exists.
void updateReminder(MessageReminder reminder) {
final messageId = reminder.messageId;
for (final message in messages) {
if (message.id == messageId) {
return updateMessage(
message.copyWith(reminder: reminder),
);
}
}
}
/// Deletes the [reminder] of the message if it exists.
void deleteReminder(MessageReminder reminder) {
// TODO: Delete the reminder from the persistence client.
final messageId = reminder.messageId;
for (final message in messages) {
if (message.id == messageId) {
return updateMessage(
message.copyWith(reminder: null),
);
}
}
}
/// Updates the [reminder] of the message if it exists.
void updateReminder(MessageReminder reminder) {
final messageId = reminder.messageId;
- for (final message in messages) {
+ for (final message in [...messages, ...threads.values.expand((m) => m)]) {
if (message.id == messageId) {
return updateMessage(
message.copyWith(reminder: reminder),
);
}
}
}
/// Deletes the [reminder] of the message if it exists.
void deleteReminder(MessageReminder reminder) {
// TODO: Delete the reminder from the persistence client.
final messageId = reminder.messageId;
- for (final message in messages) {
+ for (final message in [...messages, ...threads.values.expand((m) => m)]) {
if (message.id == messageId) {
return updateMessage(
message.copyWith(reminder: null),
);
}
}
}
🤖 Prompt for AI Agents
In packages/stream_chat/lib/src/client/channel.dart around lines 2629 to 2686,
the updateReminder and deleteReminder methods only iterate over the top-level
messages list, ignoring thread replies that may have reminders. To fix this,
modify these methods to also iterate over thread messages (replies) within each
message, ensuring reminders attached to thread replies are updated or deleted
accordingly. This will prevent stale reminder data in the UI by covering all
messages including those in threads.

Comment on lines +1 to +9
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:go_router/go_router.dart';
import 'package:sample_app/routes/routes.dart';
import 'package:sample_app/widgets/reminder_dialog.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing import for Jiffy leads to unresolved identifier

TimedReminderIndicator uses Jiffy, but the file doesn’t import it.

 import 'package:flutter/material.dart';
+import 'package:jiffy/jiffy.dart';

If you want to avoid the heavy dependency you could switch to timeago.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:go_router/go_router.dart';
import 'package:sample_app/routes/routes.dart';
import 'package:sample_app/widgets/reminder_dialog.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'dart:async';
import 'package:flutter/material.dart';
+import 'package:jiffy/jiffy.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:go_router/go_router.dart';
import 'package:sample_app/routes/routes.dart';
import 'package:sample_app/widgets/reminder_dialog.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
🤖 Prompt for AI Agents
In sample_app/lib/pages/reminders_page.dart at the top import section (lines 1
to 9), the code uses the `Jiffy` class in `TimedReminderIndicator` but does not
import the `jiffy` package, causing an unresolved identifier error. Fix this by
adding the appropriate import statement for `Jiffy` (e.g., `import
'package:jiffy/jiffy.dart';`). Alternatively, if you want to avoid adding the
heavy `jiffy` dependency, refactor the code to use the `timeago` package instead
and import it accordingly.

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