Skip to content

feat: add multimedia rendering in dispute chat (Phase 4)#514

Merged
grunch merged 3 commits into
mainfrom
phase-4-chat-user-admin
Mar 6, 2026
Merged

feat: add multimedia rendering in dispute chat (Phase 4)#514
grunch merged 3 commits into
mainfrom
phase-4-chat-user-admin

Conversation

@Catrya

@Catrya Catrya commented Mar 6, 2026

Copy link
Copy Markdown
Member
  • Decouple EncryptedImageMessage and EncryptedFileMessage from chatRoomsProvider, now accept generic callbacks
  • Change multimedia widgets from ConsumerStatefulWidget to StatefulWidget
  • Update MessageBubble (P2P) to pass chatRoomsProvider callbacks to decoupled widgets
  • Add media cache and auto-download to DisputeChatNotifier for received multimedia
  • Replace placeholder rendering in DisputeMessageBubble with real inline rendering widgets
  • Both P2P and dispute chat now share the same multimedia rendering code

How to test

Dispute chat multimedia (new)

  • Open a dispute on an active order
  • Send an image from dispute chat → image renders inline (no longer shows placeholder)
  • Send a file from dispute chat → file card renders with download/open button
  • Mixed messages (text + images + files) display in correct order
  • Loading spinner shows while image/file is being downloaded and decrypted
  • Close and reopen app → historical multimedia messages load and render correctly

P2P chat regression (must still work)

  • Send and receive text messages in P2P chat
  • Send and receive images in P2P chat → render inline as before
  • Send and receive files in P2P chat → download/open works as before
  • Historical P2P messages with multimedia load correctly after restart

Summary by CodeRabbit

  • New Features

    • Dispute message bubbles now display encrypted images and files inline within dispute threads, with improved loading and caching.
  • Refactor

    • Encrypted image/file handling and caching rewritten for more reliable decryption, download, and metadata management across chat and dispute views.
    • Message processing updated to proactively detect and handle encrypted media with better error handling.

  - Decouple EncryptedImageMessage and EncryptedFileMessage from chatRoomsProvider, accepting generic callbacks
  instead
  - Change multimedia widgets from ConsumerStatefulWidget to StatefulWidget (no longer need Riverpod)
  - Update MessageBubble (P2P) to pass chatRoomsProvider callbacks to decoupled widgets
  - Add media cache to DisputeChatNotifier (image/file cache maps + getter/setter methods)
  - Add _processMessageContent to DisputeChatNotifier for auto-downloading images on receive
  - Replace placeholder rendering in DisputeMessageBubble with real EncryptedImageMessage/EncryptedFileMessage widgets  - Change DisputeMessageBubble from StatelessWidget to ConsumerWidget, add disputeId parameter
  - Pass disputeId from DisputeMessagesList to DisputeMessageBubble
  - Both P2P and dispute chat now share the same multimedia rendering widgets
  - Zero regression on existing P2P chat functionality
@coderabbitai

coderabbitai Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Replaces Riverpod provider access in encrypted media widgets with explicit callback props; message bubbles now supply notifier-backed callbacks; dispute chat notifier adds decrypted-media caches, metadata stores, and background processing for encrypted image/file messages.

Changes

Cohort / File(s) Summary
Encrypted Media Widgets
lib/features/chat/widgets/encrypted_file_message.dart, lib/features/chat/widgets/encrypted_image_message.dart
Converted from ConsumerStatefulWidget to StatefulWidget; removed orderId and provider reads; added required callbacks: getSharedKey, getCached*, get*Metadata, cache*; internal logic updated to use widget callbacks and filename normalization utility.
Message Bubble / Wiring
lib/features/chat/widgets/message_bubble.dart, lib/features/disputes/widgets/dispute_message_bubble.dart, lib/features/disputes/widgets/dispute_messages_list.dart
Wires chat-room/dispute notifier into encrypted media widgets by passing callback implementations; DisputeMessageBubble converted to ConsumerWidget and now requires disputeId; DisputeMessagesList forwards disputeId.
Dispute Chat Notifier
lib/features/disputes/notifiers/dispute_chat_notifier.dart
Adds decrypted media caches and metadata stores (_imageCache, _imageMetadata, _fileCache, _fileMetadata) and private helpers (getCached*, get*Metadata, cacheDecrypted*); introduces _processMessageContent with handlers for encrypted image/file messages that download/decrypt via upload services and cache results.
Minor / Formatting
(implicit small edits across above files)
Whitespace and minor structural adjustments; added imports for notifier wiring and encrypted-media utilities/services.

Sequence Diagram(s)

sequenceDiagram
    participant Notifier as DisputeChatNotifier
    participant Service as EncryptedUploadService
    participant Cache as LocalCache
    participant Widget as EncryptedMediaWidget

    Note over Notifier,Service: Unwrapping / historical processing of events
    Notifier->>Notifier: _processMessageContent(event)
    alt encrypted image/file detected
        Notifier->>Service: downloadAndDecrypt(payload, sharedKey)
        Service-->>Notifier: decryptedData + metadata
        Notifier->>Cache: cacheDecrypted*(messageId, decryptedData, metadata)
    end

    Note over Widget,Notifier: Widget runtime access (render / user open)
    Widget->>Notifier: getCached*(messageId)
    Notifier-->>Widget: cachedData?
    Widget->>Notifier: get*Metadata(messageId)
    Notifier-->>Widget: metadata
    alt cachedData missing
        Widget->>Notifier: getSharedKey()
        Widget->>Service: requestDecrypt(payload, sharedKey)
        Service-->>Notifier: decryptedData + metadata
        Notifier->>Cache: cacheDecrypted*(messageId, decryptedData, metadata)
        Notifier-->>Widget: decryptedData
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • AndreaDiazCorreia
  • mostronatorcoder
  • grunch
  • arkanoider

Poem

"I'm a rabbit with a tiny hop,
Callbacks replace the provider prop,
Keys and caches tucked away,
Widgets ask — the notifier says 'okay',
A little hare does code and hop!" 🐰✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add multimedia rendering in dispute chat (Phase 4)' directly and clearly describes the main change: adding multimedia (image and file) rendering capability to the dispute chat interface, with phase designation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch phase-4-chat-user-admin

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.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/features/chat/widgets/encrypted_file_message.dart (2)

119-125: ⚠️ Potential issue | 🟡 Minor

Hardcoded user-facing string should be localized.

The string 'Failed to load image' on line 120 should use S.of(context)!.failedToLoadImage or a similar localization key for consistency with the coding guidelines.

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

In `@lib/features/chat/widgets/encrypted_file_message.dart` around lines 119 -
125, Replace the hardcoded user-facing string in the Text widget inside
encrypted_file_message.dart ("Failed to load image") with the localized resource
access (e.g. S.of(context)!.failedToLoadImage) so the message is localized; keep
the existing TextStyle usage but pass the localized string into the Text
constructor in the same spot where the literal currently appears (refer to the
Text widget block that sets color: AppTheme.textSecondary and fontSize: 12).

371-378: ⚠️ Potential issue | 🟡 Minor

Hardcoded user-facing string should be localized.

The string 'Failed to load file' on line 372 should use a localization key (e.g., S.of(context)!.failedToLoadFile) for consistency. As per coding guidelines: "Localize all user-facing strings via ARB files and access them with S.of(context) rather than hard-coded string literals."

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

In `@lib/features/chat/widgets/encrypted_file_message.dart` around lines 371 -
378, Replace the hardcoded Text('Failed to load file') in the widget (in
encrypted_file_message.dart, inside the build method/Widget that renders the
error Text) with the localized string S.of(context)!.failedToLoadFile; ensure
the file imports the generated localization (import
'package:your_app/generated/l10n.dart'; or equivalent) and that the ARB contains
the failedToLoadFile key so S.of(context) exposes it; keep the existing
TextStyle and TextAlign unchanged.
🧹 Nitpick comments (4)
lib/features/disputes/notifiers/dispute_chat_notifier.dart (3)

253-291: Historical messages don't pre-process encrypted media.

When loading historical messages in _loadHistoricalMessages, the _processMessageContent method is not called on unwrapped events. This means encrypted images/files won't be auto-downloaded and cached when the user opens an existing dispute chat, unlike new incoming messages in _onChatEvent.

Consider adding media processing after unwrapping historical messages for consistency:

           // Decrypt and unwrap the message
           final unwrappedEvent = await storedEvent.p2pUnwrap(session.adminSharedKey!);
+          await _processMessageContent(unwrappedEvent);
           messages.add(DisputeChatMessage(event: unwrappedEvent));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/disputes/notifiers/dispute_chat_notifier.dart` around lines 253
- 291, The historical loader is unwrapping events but not pre-processing
encrypted media, so after reconstructing and unwrapping each storedEvent in the
loop (the variable unwrappedEvent returned by storedEvent.p2pUnwrap in the
method that loads historical messages) call the existing media processing
routine used for incoming events (invoke _processMessageContent on the
unwrappedEvent and await it if async) before creating/adding the
DisputeChatMessage; this ensures encrypted images/files are downloaded/cached
the same way as in _onChatEvent and keeps media handling consistent.

478-491: Service instantiation on every call.

EncryptedImageUploadService() is instantiated per image processed. If the service is stateless, consider creating it once as a field or obtaining it via a provider for consistency with the codebase's dependency injection patterns.

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

In `@lib/features/disputes/notifiers/dispute_chat_notifier.dart` around lines 478
- 491, The _processEncryptedImageMessage is creating a new
EncryptedImageUploadService() for every call; instead, make
EncryptedImageUploadService a single instance (either a private final field on
the notifier class or injected via the class constructor/provider) and use that
instance inside _processEncryptedImageMessage (keep using getAdminSharedKey(),
EncryptedImageUploadResult.fromJson, downloadAndDecryptImage and
cacheDecryptedImage as before) to avoid repeated instantiation and align with DI
patterns.

424-448: In-memory caches grow unbounded.

The _imageCache and _fileCache maps store decrypted media indefinitely. In a long dispute conversation with many media attachments, this could lead to memory pressure. Consider adding an eviction strategy (LRU cache, max entry count, or clearing on dispose).

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

In `@lib/features/disputes/notifiers/dispute_chat_notifier.dart` around lines 424
- 448, The in-memory caches _imageCache and _fileCache (and their metadata maps
_imageMetadata/_fileMetadata) grow unbounded; implement an eviction strategy by
capping entries and evicting least-recently-used items (or oldest) inside
cacheDecryptedImage and cacheDecryptedFile, or replace the plain Map with an LRU
cache implementation (or a package) and clear caches in a dispose/close method;
update getCachedImage/getCachedFile to refresh recency on access and ensure
cache size/config (max entries) is configurable.
lib/features/chat/widgets/encrypted_file_message.dart (1)

526-544: Inconsistent diacritic handling compared to EncryptedImageMessage.

This file manually replaces diacritics (lines 533-537) while encrypted_image_message.dart uses the diacritic package's removeDiacritics() function. Consider using the same approach for consistency and more comprehensive Unicode support.

♻️ Suggested refactor
+import 'package:diacritic/diacritic.dart';
 ...
   String _sanitizeFilename(String filename) {
     // 1. Get basename only (remove any directory components)
     final basename = filename.split(RegExp(r'[/\\]')).last;

-    // 2. Normalize accented characters to prevent encoding issues
-    String normalized = basename
-        .replaceAll('á', 'a').replaceAll('é', 'e').replaceAll('í', 'i')
-        .replaceAll('ó', 'o').replaceAll('ú', 'u').replaceAll('ñ', 'n')
-        .replaceAll('ü', 'u').replaceAll('Á', 'A').replaceAll('É', 'E')
-        .replaceAll('Í', 'I').replaceAll('Ó', 'O').replaceAll('Ú', 'U')
-        .replaceAll('Ñ', 'N').replaceAll('Ü', 'U');
+    // 2. Normalize using diacritic package for comprehensive Unicode support
+    String normalized = removeDiacritics(basename);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/chat/widgets/encrypted_file_message.dart` around lines 526 -
544, The _sanitizeFilename function currently does manual diacritic
replacements; replace that manual block with the diacritic package's
removeDiacritics() call for consistent and comprehensive normalization (apply
removeDiacritics to the basename before replacing spaces/unsafe chars), remove
the chain of replaceAll(...) for accented chars, add the diacritic import
(import 'package:diacritic/diacritic.dart';) and ensure the package is in
pubspec.yaml if not already; keep the remaining steps (basename extraction,
whitespace->underscore, dangerous-char regex, '..' replacement) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@lib/features/chat/widgets/encrypted_file_message.dart`:
- Around line 119-125: Replace the hardcoded user-facing string in the Text
widget inside encrypted_file_message.dart ("Failed to load image") with the
localized resource access (e.g. S.of(context)!.failedToLoadImage) so the message
is localized; keep the existing TextStyle usage but pass the localized string
into the Text constructor in the same spot where the literal currently appears
(refer to the Text widget block that sets color: AppTheme.textSecondary and
fontSize: 12).
- Around line 371-378: Replace the hardcoded Text('Failed to load file') in the
widget (in encrypted_file_message.dart, inside the build method/Widget that
renders the error Text) with the localized string
S.of(context)!.failedToLoadFile; ensure the file imports the generated
localization (import 'package:your_app/generated/l10n.dart'; or equivalent) and
that the ARB contains the failedToLoadFile key so S.of(context) exposes it; keep
the existing TextStyle and TextAlign unchanged.

---

Nitpick comments:
In `@lib/features/chat/widgets/encrypted_file_message.dart`:
- Around line 526-544: The _sanitizeFilename function currently does manual
diacritic replacements; replace that manual block with the diacritic package's
removeDiacritics() call for consistent and comprehensive normalization (apply
removeDiacritics to the basename before replacing spaces/unsafe chars), remove
the chain of replaceAll(...) for accented chars, add the diacritic import
(import 'package:diacritic/diacritic.dart';) and ensure the package is in
pubspec.yaml if not already; keep the remaining steps (basename extraction,
whitespace->underscore, dangerous-char regex, '..' replacement) unchanged.

In `@lib/features/disputes/notifiers/dispute_chat_notifier.dart`:
- Around line 253-291: The historical loader is unwrapping events but not
pre-processing encrypted media, so after reconstructing and unwrapping each
storedEvent in the loop (the variable unwrappedEvent returned by
storedEvent.p2pUnwrap in the method that loads historical messages) call the
existing media processing routine used for incoming events (invoke
_processMessageContent on the unwrappedEvent and await it if async) before
creating/adding the DisputeChatMessage; this ensures encrypted images/files are
downloaded/cached the same way as in _onChatEvent and keeps media handling
consistent.
- Around line 478-491: The _processEncryptedImageMessage is creating a new
EncryptedImageUploadService() for every call; instead, make
EncryptedImageUploadService a single instance (either a private final field on
the notifier class or injected via the class constructor/provider) and use that
instance inside _processEncryptedImageMessage (keep using getAdminSharedKey(),
EncryptedImageUploadResult.fromJson, downloadAndDecryptImage and
cacheDecryptedImage as before) to avoid repeated instantiation and align with DI
patterns.
- Around line 424-448: The in-memory caches _imageCache and _fileCache (and
their metadata maps _imageMetadata/_fileMetadata) grow unbounded; implement an
eviction strategy by capping entries and evicting least-recently-used items (or
oldest) inside cacheDecryptedImage and cacheDecryptedFile, or replace the plain
Map with an LRU cache implementation (or a package) and clear caches in a
dispose/close method; update getCachedImage/getCachedFile to refresh recency on
access and ensure cache size/config (max entries) is configurable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2cef150b-d966-4721-914a-bc42f9fdf69d

📥 Commits

Reviewing files that changed from the base of the PR and between 4870ca2 and ae3a00a.

📒 Files selected for processing (6)
  • lib/features/chat/widgets/encrypted_file_message.dart
  • lib/features/chat/widgets/encrypted_image_message.dart
  • lib/features/chat/widgets/message_bubble.dart
  • lib/features/disputes/notifiers/dispute_chat_notifier.dart
  • lib/features/disputes/widgets/dispute_message_bubble.dart
  • lib/features/disputes/widgets/dispute_messages_list.dart

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/features/chat/widgets/encrypted_file_message.dart (1)

119-126: ⚠️ Potential issue | 🟡 Minor

Hardcoded user-facing strings should be localized.

Lines 121 and 373 contain hardcoded English text that should use S.of(context)! for localization:

  • Line 121: 'Failed to load image'
  • Line 373: 'Failed to load file'
🌐 Proposed fix for localization
-                      Text(
-                        'Failed to load image',
-                        style: TextStyle(
+                      Text(
+                        S.of(context)!.failedToLoadImage,
+                        style: TextStyle(
-          Text(
-            'Failed to load file',
-            style: TextStyle(
+          Text(
+            S.of(context)!.failedToLoadFile,
+            style: TextStyle(

Add the corresponding keys to the ARB files.

As per coding guidelines: "Replace all hardcoded user-facing text with localized strings using S.of(context)!.keyName."

Also applies to: 372-378

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

In `@lib/features/chat/widgets/encrypted_file_message.dart` around lines 119 -
126, Replace the hardcoded user-facing strings in EncryptedFileMessage widget:
change the Text('Failed to load image') and Text('Failed to load file') usages
to use localized resources via S.of(context)!, e.g.
S.of(context)!.failedToLoadImage and S.of(context)!.failedToLoadFile; update the
ARB files with those new keys and run the localization codegen so the S class
contains the new accessors.
🧹 Nitpick comments (1)
lib/features/disputes/notifiers/dispute_chat_notifier.dart (1)

425-431: Unbounded in-memory caches may cause memory pressure in long sessions.

The four cache maps (_imageCache, _imageMetadata, _fileCache, _fileMetadata) grow without bound as media messages are processed, and dispose() does not clear them. For disputes with many media messages or long-running sessions, this could lead to significant memory consumption.

Consider clearing the caches in dispose():

♻️ Proposed fix to clear caches on dispose
 `@override`
 void dispose() {
   _subscription?.cancel();
   _sessionListener?.close();
+  _imageCache.clear();
+  _imageMetadata.clear();
+  _fileCache.clear();
+  _fileMetadata.clear();
   super.dispose();
 }

Also applies to: 551-556

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

In `@lib/features/disputes/notifiers/dispute_chat_notifier.dart` around lines 425
- 431, The maps _imageCache, _imageMetadata, _fileCache, and _fileMetadata are
unbounded and not cleared, causing memory pressure; update the
DisputeChatNotifier.dispose() method to clear (call .clear() on) each of those
maps (and any other similar caches around lines referenced like the other cache
pair at 551-556) to free memory on teardown, and optionally replace or wrap
these maps with a bounded/LRU cache implementation if long-lived sessions need
retention rather than full clear.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@lib/features/chat/widgets/encrypted_file_message.dart`:
- Around line 119-126: Replace the hardcoded user-facing strings in
EncryptedFileMessage widget: change the Text('Failed to load image') and
Text('Failed to load file') usages to use localized resources via
S.of(context)!, e.g. S.of(context)!.failedToLoadImage and
S.of(context)!.failedToLoadFile; update the ARB files with those new keys and
run the localization codegen so the S class contains the new accessors.

---

Nitpick comments:
In `@lib/features/disputes/notifiers/dispute_chat_notifier.dart`:
- Around line 425-431: The maps _imageCache, _imageMetadata, _fileCache, and
_fileMetadata are unbounded and not cleared, causing memory pressure; update the
DisputeChatNotifier.dispose() method to clear (call .clear() on) each of those
maps (and any other similar caches around lines referenced like the other cache
pair at 551-556) to free memory on teardown, and optionally replace or wrap
these maps with a bounded/LRU cache implementation if long-lived sessions need
retention rather than full clear.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 70ed68f1-3c0d-4592-a8c8-16f2a1e2904b

📥 Commits

Reviewing files that changed from the base of the PR and between ae3a00a and e464f4f.

📒 Files selected for processing (2)
  • lib/features/chat/widgets/encrypted_file_message.dart
  • lib/features/disputes/notifiers/dispute_chat_notifier.dart

@mostronatorcoder mostronatorcoder 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.

Review — Phase 4: Multimedia in Dispute Chat

✅ What works well

  • Decoupling pattern is solid. Moving EncryptedImageMessage and EncryptedFileMessage from ConsumerStatefulWidgetStatefulWidget with callback injection is the right call. Makes both widgets reusable across P2P and dispute contexts without being coupled to a specific provider.
  • CI passes, no merge conflicts, mergeable_state: clean.
  • diacritic package adoption in encrypted_file_message.dart — the old manual replaceAll chain was incomplete (missed characters like ä, ö, ç, etc.). Good fix.
  • Historical messages now call _processMessageContent (line 284) — this was missing in an earlier commit and CodeRabbit flagged it. Fixed correctly.
  • Auto-download strategy is sensible: images auto-download, non-image files cache metadata only and require manual trigger.

🚨 Issues (must fix)

1. _processMessageContent blocks message rendering (UX regression)

In _onChatEvent (~line 200), await _processMessageContent(unwrappedEvent) is called before the message is added to state. This means:

  • If the image download takes 5-10 seconds (or times out), the message won't appear in the chat until the download completes or fails.
  • For the P2P chat, images load lazily (widget auto-loads on build). The dispute chat should follow the same pattern.

Fix: Don't await the processing. Fire-and-forget with unawaited() or move it after adding the message to state, then trigger a state update when the cache is populated:

messages.add(DisputeChatMessage(event: unwrappedEvent));
// Fire-and-forget — widget will pick up cached data on next build
unawaited(_processMessageContent(unwrappedEvent));

Same applies to the historical messages loop at line 284.

2. Unbounded in-memory caches — memory leak

The four maps (_imageCache, _imageMetadata, _fileCache, _fileMetadata) grow without bounds and are never cleared, not even in dispose().

A dispute with 20 high-res images could consume 100+ MB of RAM. On low-end Android devices this will cause OOM kills.

Minimum fix: Clear caches in dispose():

@override
void dispose() {
  _subscription?.cancel();
  _sessionListener?.close();
  _imageCache.clear();
  _imageMetadata.clear();
  _fileCache.clear();
  _fileMetadata.clear();
  super.dispose();
}

Better fix: Cap cache size (e.g., 50 entries, evict oldest).

⚠️ Minor issues

3. Hardcoded strings not localized (pre-existing, but touched by this PR)

encrypted_file_message.dart lines 121 and 373 have 'Failed to load image' and 'Failed to load file' hardcoded. These should use S.of(context)! as per project guidelines. Since this PR touches these files and adds the diacritic import, it's a good time to fix them.

4. Service instantiation per call

_processEncryptedImageMessage and _processEncryptedFileMessage create new instances of EncryptedImageUploadService() and EncryptedFileUploadService() on every call. If these are stateless, make them late final fields on the notifier.

5. DisputeMessageBubble re-couples to provider

The multimedia widgets were decoupled from providers (good), but DisputeMessageBubble was changed from StatelessWidgetConsumerWidget to read the dispute notifier directly. This is the pragmatic choice for now, but ideally the callbacks would be passed down from DisputeMessagesList to keep the bubble provider-free too. Not a blocker, just noting the architecture direction.

Summary

The refactoring direction is correct and the code sharing between P2P/dispute chat is a real improvement. But issue #1 (blocking message rendering) is a UX regression that needs fixing before merge, and issue #2 (memory leak) is a must-fix that's trivial to address.

Request changes for #1 and #2. The rest are nice-to-haves for this PR.

@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.

🧹 Nitpick comments (1)
lib/features/disputes/notifiers/dispute_chat_notifier.dart (1)

10-10: Avoid decoding the same payload twice.

_processMessageContent already has jsonContent, but the helpers in lib/features/chat/utils/message_type_helpers.dart:6-30 decode message.content again. Switching on the parsed type here removes the extra decode on every media message/history replay and lets this notifier drop the helper import.

♻️ Example simplification
-import 'package:mostro_mobile/features/chat/utils/message_type_helpers.dart';
...
-      if (jsonContent != null) {
-        if (MessageTypeUtils.isEncryptedImageMessage(message)) {
-          await _processEncryptedImageMessage(message, jsonContent);
-        } else if (MessageTypeUtils.isEncryptedFileMessage(message)) {
-          await _processEncryptedFileMessage(message, jsonContent);
-        }
-      }
+      switch (jsonContent?['type']) {
+        case 'image_encrypted':
+          await _processEncryptedImageMessage(message, jsonContent!);
+          break;
+        case 'file_encrypted':
+          await _processEncryptedFileMessage(message, jsonContent!);
+          break;
+      }

Also applies to: 458-473

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

In `@lib/features/disputes/notifiers/dispute_chat_notifier.dart` at line 10, The
notifier decodes message.content twice: _processMessageContent already parses
jsonContent but code and the helpers in message_type_helpers.dart re-decode
message.content; update the logic inside _processMessageContent (and the similar
block at the other occurrence) to switch on the already-parsed
jsonContent['type'] (or its parsed enum) and call the appropriate handling
directly instead of calling the helpers that re-decode message.content, then
remove the now-unused import of
package:mostro_mobile/features/chat/utils/message_type_helpers.dart; ensure you
update both the main occurrence and the other block referenced (lines ~458-473)
to avoid duplicate decoding.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/features/disputes/notifiers/dispute_chat_notifier.dart`:
- Line 10: The notifier decodes message.content twice: _processMessageContent
already parses jsonContent but code and the helpers in message_type_helpers.dart
re-decode message.content; update the logic inside _processMessageContent (and
the similar block at the other occurrence) to switch on the already-parsed
jsonContent['type'] (or its parsed enum) and call the appropriate handling
directly instead of calling the helpers that re-decode message.content, then
remove the now-unused import of
package:mostro_mobile/features/chat/utils/message_type_helpers.dart; ensure you
update both the main occurrence and the other block referenced (lines ~458-473)
to avoid duplicate decoding.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bcd614ea-9dfd-4798-9729-8b3ecdac6511

📥 Commits

Reviewing files that changed from the base of the PR and between e464f4f and 497cce8.

📒 Files selected for processing (1)
  • lib/features/disputes/notifiers/dispute_chat_notifier.dart

@mostronatorcoder mostronatorcoder 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.

Both critical issues from my previous review are properly addressed in 497cce80:

Issue #1 (blocking rendering): _processMessageContent is now fire-and-forget with unawaited() — both for incoming messages (after adding to state) and historical messages. Messages appear immediately, media loads in background.

Issue #2 (memory leak): All four cache maps are cleared in dispose().

CI passes, no conflicts. LGTM 👍

@grunch grunch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

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.

2 participants