Skip to content

phase 3 add multimedia sending to dispute chat#509

Merged
grunch merged 7 commits intomainfrom
phase-3-dispute-multimedia-send
Mar 6, 2026
Merged

phase 3 add multimedia sending to dispute chat#509
grunch merged 7 commits intomainfrom
phase-3-dispute-multimedia-send

Conversation

@Catrya
Copy link
Copy Markdown
Member

@Catrya Catrya commented Mar 3, 2026

  • Add attachment button to DisputeMessageInput matching P2P chat UX
  • Encrypt and upload images/files to Blossom using admin shared key
  • Show placeholder in bubble for sent multimedia (icon + filename + size)
  • Update DisputeMessageBubble to accept DisputeChatMessage and detect type
  • Reuse existing upload services with zero changes to P2P chat code
  • Update documentation

How to test

  1. Open a dispute chat with an admin assigned (in-progress status)
  2. Tap the paper clip icon next to the text input
  3. Select an image or document — confirm in the dialog
  4. Loading spinner shows during upload — then a placeholder bubble appears with icon + filename + size + "Encrypted" badge
  5. Admin should receive and decrypt the file on their end
  6. Verify text messages still work normally

Known limitations (intentional in this phase)

  • Sent multimedia shows as placeholder (icon + filename + size), not inline preview
  • Multimedia received from admin shows as raw JSON — rendering is a future phase

Summary by CodeRabbit

  • Documentation

    • Revised Dispute Chat multimedia roadmap: Phase 3 now focuses on sending with placeholders; Phase 4 on inline admin/media rendering. Updated milestones, test guidance, and new localization keys (imageSent, fileSent).
  • New Features

    • Attach and send encrypted multimedia with upload indicator and confirmation; sent-media placeholders display immediately.
  • UI Improvements

    • Message bubbles show media placeholders, readable file sizes, timestamps preserved, and long-press copy for text.
  • Tests

    • Updated test plans and expectations for phased rollout and placeholder behavior.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Reorders multimedia plan phases and implements Phase 3 sending: message model changed to DisputeChatMessage, bubbles render media placeholders, input adds file/image attachment, encrypted upload and send flow, and message list updated to pass full message objects. (≤50 words)

Changes

Cohort / File(s) Summary
Multimedia roadmap doc
docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md
Reorders Phase 3/4 (Phase 3 = sending + placeholders, Phase 4 = rendering), updates narratives, testing guidance, milestones, and adds localization keys imageSent and fileSent.
Message bubble widget
lib/features/disputes/widgets/dispute_message_bubble.dart
API changed to accept DisputeChatMessage; adds message-type decoding, branches for text / encryptedImage / encryptedFile, _buildTextBubble, _buildMediaPlaceholder, unified decoration/timestamp helpers, and filesize formatting.
Message input (attach/upload/send)
lib/features/disputes/widgets/dispute_message_input.dart
Adds file/image picker, MIME/type detection, size validation, confirmation dialog, encrypted upload services (image/file), admin/shared-key retrieval, _sendEncryptedMessage flow, upload-state UI, and SnackBar error handling.
Messages list call site
lib/features/disputes/widgets/dispute_messages_list.dart
Updates DisputeMessageBubble invocation to pass the full DisputeChatMessage (message: message) and removes separate timestamp/content args.
Imports / notifier usage
lib/features/disputes/...
Introduces imports for message-type utilities and dispute chat notifier usage to obtain admin shared-key and message-type helpers.
Localization
lib/.../intl_*.arb
Adds imageSent and fileSent keys referenced by media-placeholder UI and send confirmations.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as DisputeMessageInput
    participant Picker as FilePicker
    participant Notifier as DisputeChatNotifier
    participant Upload as EncryptedUploadService
    participant Chat as P2P/ChatProvider

    User->>UI: tap attach
    UI->>Picker: open file chooser
    Picker-->>UI: selected file (metadata)
    UI->>User: show confirm dialog (filename/size)
    User-->>UI: confirm
    UI->>Notifier: getAdminSharedKey()
    UI->>Upload: encrypt & upload file/image
    Upload-->>UI: success (media metadata/URL)
    UI->>Chat: send DisputeChatMessage (event with media metadata)
    Chat-->>UI: ack
    UI-->>User: show placeholder bubble (sending/complete)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • grunch
  • AndreaDiazCorreia
  • arkanoider
  • BraCR10

Poem

🐇 I hopped a little upload trail,
I wrapped your files in secret veil,
A tiny placeholder takes the stage,
Phase three sends — the next page will engage,
Soon admin media will bloom on the next page ✨

🚥 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 'phase 3 add multimedia sending to dispute chat' directly reflects the main change: adding multimedia sending functionality to the dispute chat in Phase 3, which is the primary objective of this PR.
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-3-dispute-multimedia-send

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md`:
- Around line 295-302: Three fenced code blocks are missing language specifiers
(MD040); add the language label "text" to each fenced block that contains the
listed lines so the linter accepts them — specifically update the block
containing the service signatures (e.g.,
EncryptedImageUploadService.uploadEncryptedImage(imageFile, sharedKey) and
EncryptedFileUploadService.uploadEncryptedFile(file, sharedKey)), and the two UI
placeholder mockup blocks that start with the ASCII box/emoji lines (the block
showing "┌─────────────────────────────┐" and the one showing "│  📄 
document.pdf           │"); simply change the opening ``` to ```text for those
three blocks.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e5d189 and 03d4c2c.

📒 Files selected for processing (1)
  • docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md

Comment thread docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md Outdated
  - Add attachment button to DisputeMessageInput matching P2P chat UX
  - Encrypt and upload images/files to Blossom using admin shared key
  - Show placeholder in bubble for sent multimedia (icon + filename + size)
  - Update DisputeMessageBubble to accept DisputeChatMessage and detect type
  - Reuse existing upload services with zero changes to P2P chat code
Copy link
Copy Markdown
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

🧹 Nitpick comments (1)
lib/features/disputes/widgets/dispute_message_input.dart (1)

89-90: Avoid reading entire files just to detect MIME type.

readAsBytes() loads the full attachment into memory before upload. For large files, this can cause avoidable memory spikes; reading only header bytes is enough for MIME sniffing.

Suggested refactor
-        final fileData = await file.readAsBytes();
-        final isImage = await _isImageFile(fileData, filename);
+        final isImage = await _isImageFile(file, filename);

-  Future<bool> _isImageFile(Uint8List fileData, String filename) async {
+  Future<bool> _isImageFile(File file, String filename) async {
     try {
-      final mimeType = await _getMimeType(fileData, filename);
+      final mimeType = await _getMimeType(file, filename);
       return MediaValidationService.isImageTypeSupported(mimeType ?? '');
     } catch (e) {
       return false;
     }
   }

-  Future<String?> _getMimeType(Uint8List fileData, String filename) async {
-    final mimeFromBytes = lookupMimeType('', headerBytes: fileData);
+  Future<String?> _getMimeType(File file, String filename) async {
+    final headerBytes = await file.openRead(0, 64).expand((chunk) => chunk).take(64).toList();
+    final mimeFromBytes = lookupMimeType(filename, headerBytes: headerBytes);
     if (mimeFromBytes != null) {
       return mimeFromBytes;
     }
     return lookupMimeType(filename);
   }

Also applies to: 133-147

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

In `@lib/features/disputes/widgets/dispute_message_input.dart` around lines 89 -
90, The code currently calls file.readAsBytes() before calling _isImageFile,
which reads the entire file into memory; change this to read only the minimal
header bytes for MIME sniffing (e.g., open a stream and read the first N bytes
or use file.openRead(0, N)) and pass that slice to _isImageFile instead of the
full payload; update both places where file.readAsBytes() is used (the call
before _isImageFile and the similar block around lines 133–147) and ensure
_isImageFile accepts/handles the header byte buffer only or add a small helper
that extracts header bytes before invoking _isImageFile.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/disputes/widgets/dispute_message_bubble.dart`:
- Around line 178-181: The timestamp text in _buildTimestamp() uses
_formatTime(...) which returns hardcoded English strings (e.g., "now", "m ago");
update localization by routing all user-facing timestamp fragments through ARB
keys and S.of(context)!.keyName. Modify _formatTime to accept a BuildContext (or
add a wrapper that maps its output) and replace hardcoded literals inside
_formatTime with calls to S.of(context)!.timestampNow,
S.of(context)!.minutesAgo, S.of(context)!.hoursAgo, etc.; update
_buildTimestamp() to pass its context into _formatTime (or call the new wrapper)
so the rendered Text uses localized strings. Ensure new ARB keys are added and
used consistently for singular/plural forms where needed.
- Around line 92-94: The code unsafely casts metadata entries causing CastError;
replace the direct casts for original_size (and optionally filename) with
defensive parsing: read metadata['original_size'] into a local var, handle if
it's an int, num (call toInt()), or String (use int.tryParse) and default to 0
if parsing fails, and for filename ensure you coerce non-null values to String
safely (e.g., using toString() or check type) before assigning to filename;
update the variables originalSize and filename in dispute_message_bubble.dart
accordingly so they never throw on unexpected backend types.

In `@lib/features/disputes/widgets/dispute_message_input.dart`:
- Around line 65-79: After the await FilePicker.platform.pickFiles(...) call,
ensure you check the widget's mounted flag before using context: if (!mounted)
return; then call _showFileConfirmationDialog(filename). Also add another
mounted check immediately after awaiting _showFileConfirmationDialog(filename)
(and before any subsequent async operations or usage of context); if the widget
is no longer mounted, abort the upload flow. This ensures
FilePicker.platform.pickFiles, _showFileConfirmationDialog, and any following
context usage are guarded by mounted checks.

---

Nitpick comments:
In `@lib/features/disputes/widgets/dispute_message_input.dart`:
- Around line 89-90: The code currently calls file.readAsBytes() before calling
_isImageFile, which reads the entire file into memory; change this to read only
the minimal header bytes for MIME sniffing (e.g., open a stream and read the
first N bytes or use file.openRead(0, N)) and pass that slice to _isImageFile
instead of the full payload; update both places where file.readAsBytes() is used
(the call before _isImageFile and the similar block around lines 133–147) and
ensure _isImageFile accepts/handles the header byte buffer only or add a small
helper that extracts header bytes before invoking _isImageFile.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03d4c2c and ed00056.

📒 Files selected for processing (3)
  • lib/features/disputes/widgets/dispute_message_bubble.dart
  • lib/features/disputes/widgets/dispute_message_input.dart
  • lib/features/disputes/widgets/dispute_messages_list.dart

Comment thread lib/features/disputes/widgets/dispute_message_bubble.dart
Comment thread lib/features/disputes/widgets/dispute_message_bubble.dart
Comment thread lib/features/disputes/widgets/dispute_message_input.dart
Copy link
Copy Markdown
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 (1)
docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md (1)

37-47: Use consistent English terminology for “decoupling.”

The plan mixes English with desacoplamiento. Consider replacing with decoupling in these headings/body sections for consistency and easier team-wide readability.

Also applies to: 558-561

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

In `@docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md` around lines 37 - 47, Replace the
Spanish word "desacoplamiento" with the English term "decoupling" throughout the
document (e.g., update the heading "### 2. Sending does not require
desacoplamiento" and any occurrences in the body and in the "New Phase 4"
bullet), keeping surrounding context intact; ensure references like
EncryptedImageMessage, EncryptedFileMessage, EncryptedImageUploadService,
EncryptedFileUploadService, BlossomUploadHelper and DisputeChatNotifier remain
unchanged and that capitalization matches the existing text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md`:
- Around line 267-270: Update the Phase 3 status field in the document where it
currently reads "Status: `TODO`" (near the Phase 3 header referencing branch
`phase-3-dispute-multimedia-send`) to reflect the PR work — change it to `IN
PROGRESS` or `DONE` as appropriate so the roadmap matches the implemented
multimedia send UI changes in this PR.

---

Nitpick comments:
In `@docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md`:
- Around line 37-47: Replace the Spanish word "desacoplamiento" with the English
term "decoupling" throughout the document (e.g., update the heading "### 2.
Sending does not require desacoplamiento" and any occurrences in the body and in
the "New Phase 4" bullet), keeping surrounding context intact; ensure references
like EncryptedImageMessage, EncryptedFileMessage, EncryptedImageUploadService,
EncryptedFileUploadService, BlossomUploadHelper and DisputeChatNotifier remain
unchanged and that capitalization matches the existing text.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed00056 and 6d73182.

📒 Files selected for processing (2)
  • docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md
  • lib/features/disputes/widgets/dispute_message_input.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/features/disputes/widgets/dispute_message_input.dart

Comment thread docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md Outdated
Copy link
Copy Markdown
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

♻️ Duplicate comments (2)
lib/features/disputes/widgets/dispute_message_bubble.dart (1)

92-93: ⚠️ Potential issue | 🟠 Major

Defensively parse multimedia metadata to prevent render-time type crashes.

Direct casts here still throw when backend metadata drifts (e.g., original_size: "50000" or non-string filename). That can crash bubble rendering for a message.

🛠️ Proposed fix
-    final filename = metadata['filename'] as String? ?? '';
-    final originalSize = (metadata['original_size'] as num?)?.toInt() ?? 0;
+    final filenameRaw = metadata['filename'];
+    final filename = switch (filenameRaw) {
+      String value when value.trim().isNotEmpty => value,
+      null => isImage ? S.of(context)!.imageSent : S.of(context)!.fileSent,
+      _ => filenameRaw.toString(),
+    };
+
+    final originalSizeRaw = metadata['original_size'];
+    final originalSize = switch (originalSizeRaw) {
+      int value => value,
+      num value => value.toInt(),
+      String value => int.tryParse(value) ?? 0,
+      _ => 0,
+    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/disputes/widgets/dispute_message_bubble.dart` around lines 92 -
93, The current direct casts for metadata in dispute_message_bubble.dart (the
filename and originalSize assignments) can throw if backend types drift; update
the parsing to be defensive: for filename use metadata['filename'] != null ?
metadata['filename'].toString() : '' (or metadata['filename']?.toString() ??
''), and for originalSize read the raw value (var v =
metadata['original_size']), then if v is num use v.toInt(), else if v is String
use int.tryParse(v) ?? 0, else default to 0; replace the existing filename and
originalSize assignments with these safe conversions so the widget (in the
DisputeMessageBubble code) never crashes on unexpected metadata types.
docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md (1)

266-270: ⚠️ Potential issue | 🟡 Minor

Phase 3 status is stale for a Phase 3 implementation PR.

This section still marks Phase 3 as TODO. Please update it to IN PROGRESS or DONE so the roadmap matches the current PR scope.

📝 Proposed fix
-**Status:** `TODO`
+**Status:** `IN PROGRESS`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md` around lines 266 - 270, Update the
Phase 3 status under the "## Phase 3: Multimedia Sending in Dispute Chat"
section by replacing the current `Status: TODO` with the appropriate current
state (`IN PROGRESS` or `DONE`) so the doc reflects the PR scope; specifically
edit the "Status:" line in DISPUTE_CHAT_MULTIMEDIA_PLAN.md to the chosen state
while leaving the rest of the header and branch/PR scope text intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/disputes/widgets/dispute_message_bubble.dart`:
- Around line 204-210: The _formatFileSize function emits hardcoded unit
strings; change it to use localized strings by accepting a BuildContext (e.g.,
rename to _formatFileSize(BuildContext context, int bytes) or otherwise obtain
context) and replace 'B', 'KB', 'MB' with S.of(context)!.fileSizeBytes,
S.of(context)!.fileSizeKB, S.of(context)!.fileSizeMB (or the correct keys in
your S class), formatting the numeric portion the same way but concatenating
with the localized unit strings so all user-facing text comes from localization.

---

Duplicate comments:
In `@docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md`:
- Around line 266-270: Update the Phase 3 status under the "## Phase 3:
Multimedia Sending in Dispute Chat" section by replacing the current `Status:
TODO` with the appropriate current state (`IN PROGRESS` or `DONE`) so the doc
reflects the PR scope; specifically edit the "Status:" line in
DISPUTE_CHAT_MULTIMEDIA_PLAN.md to the chosen state while leaving the rest of
the header and branch/PR scope text intact.

In `@lib/features/disputes/widgets/dispute_message_bubble.dart`:
- Around line 92-93: The current direct casts for metadata in
dispute_message_bubble.dart (the filename and originalSize assignments) can
throw if backend types drift; update the parsing to be defensive: for filename
use metadata['filename'] != null ? metadata['filename'].toString() : '' (or
metadata['filename']?.toString() ?? ''), and for originalSize read the raw value
(var v = metadata['original_size']), then if v is num use v.toInt(), else if v
is String use int.tryParse(v) ?? 0, else default to 0; replace the existing
filename and originalSize assignments with these safe conversions so the widget
(in the DisputeMessageBubble code) never crashes on unexpected metadata types.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d73182 and a84605c.

📒 Files selected for processing (2)
  • docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md
  • lib/features/disputes/widgets/dispute_message_bubble.dart

Comment thread lib/features/disputes/widgets/dispute_message_bubble.dart
Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

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

Review: Phase 3 — Dispute Multimedia Sending

CI passes ✅, the architecture decision to reverse phases 3/4 is well-reasoned, and reusing the existing upload services is clean. However, there are several issues that need to be addressed before merging.

Summary

  • 3 blocking issues (resource leak, memory safety, missing validation)
  • 3 non-blocking suggestions

Comment thread lib/features/disputes/widgets/dispute_message_input.dart
Comment thread lib/features/disputes/widgets/dispute_message_input.dart
Comment thread lib/features/disputes/widgets/dispute_message_input.dart
Comment thread lib/features/disputes/widgets/dispute_message_bubble.dart
Comment thread lib/features/disputes/widgets/dispute_message_bubble.dart
Comment thread lib/features/disputes/widgets/dispute_message_input.dart
…tion

  - Make upload service instances static final (stateless, no resource leak)
  - Add file.length() pre-check before readAsBytes() to prevent OOM on large files
  - Replace readAsBytes() MIME sniffing with lookupMimeType(filename)
  - Remove unused _isImageFile(), _getMimeType() helpers and dart:typed_data import
Copy link
Copy Markdown
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

♻️ Duplicate comments (1)
lib/features/disputes/widgets/dispute_message_input.dart (1)

125-131: ⚠️ Potential issue | 🟠 Major

Don’t expose raw exception text in the snackbar.

Line 129 passes e.toString() directly to UI. This can leak internal details and creates inconsistent user messaging. Show a safe localized message and log full details with the shared logger.

💡 Suggested fix
+import 'package:mostro_mobile/services/logger_service.dart';
...
-    } catch (e) {
+    } catch (e, st) {
+      logger.e('Dispute file upload failed', error: e, stackTrace: st);
       if (mounted) {
         SnackBarHelper.showTopSnackBar(
           context,
-          S.of(context)!.errorUploadingFile(e.toString()),
+          S.of(context)!.errorUploadingFile(S.of(context)!.uploadFailedTryAgain),
           backgroundColor: Colors.red,
         );
       }
     }

Based on learnings: "do not expose raw exception messages (e.g., e.toString()) in the UI... Full error information is logged via logger.e()."

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

In `@lib/features/disputes/widgets/dispute_message_input.dart` around lines 125 -
131, The catch block in dispute_message_input.dart currently shows raw exception
text via SnackBarHelper.showTopSnackBar(context,
S.of(context)!.errorUploadingFile(e.toString())), which can leak internals;
instead log the full error with the shared logger (e.g., call logger.e(e) or
logger.e('Upload error', e)) and pass a safe localized message to the UI (use
S.of(context)!.errorUploadingFile() or a new generic localization key) so the
snackbar displays user-friendly text; update the code around the catch (e) { if
(mounted) { ... } } block to remove e.toString() from the UI and add the
logger.e(...) call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md`:
- Around line 403-408: The doc snippet shows instance fields (_isUploadingFile,
EncryptedImageUploadService, EncryptedFileUploadService) but the widget uses
static final services; update the snippet to declare the upload services as
static final (e.g., make the EncryptedImageUploadService and
EncryptedFileUploadService fields static final) while keeping _isUploadingFile
as an instance bool, so the snippet matches the implemented lifecycle of the
services and avoids reverting to the old instance-field pattern.
- Around line 426-427: Update the MIME detection docs to match the shipped
implementation: replace references to byte/header-based helpers (_isImageFile,
_getMimeType) with the actual flow that uses lookupMimeType(filename) for MIME
detection and falls back to extension-based logic; also ensure the step
numbering and examples in the same section (including the repeated content at
440-454) mention the current call pattern and not the old byte helpers, and keep
the existing note about obtaining the shared key via await
ref.read(disputeChatNotifierProvider(disputeId).notifier).getAdminSharedKey().

In `@lib/features/disputes/widgets/dispute_message_input.dart`:
- Around line 84-87: The hardcoded English string in the
S.of(context)!.errorUploadingFile call should be moved into the ARB/localization
system; create a new localized key (e.g. fileTooLargeWithSize) in your .arb(s)
that accepts two parameters (actualSizeMb and maxSizeMb), update the generated S
class, and replace the inline concatenation in dispute_message_input.dart so you
call S.of(context)!.fileTooLargeWithSize(actualSizeMb, maxSizeMb) using the
computed values from FileValidationService.maxFileSize and (fileSize / (1024 *
1024)).toStringAsFixed(1); ensure plural/formatting rules in the ARB entry as
needed.

---

Duplicate comments:
In `@lib/features/disputes/widgets/dispute_message_input.dart`:
- Around line 125-131: The catch block in dispute_message_input.dart currently
shows raw exception text via SnackBarHelper.showTopSnackBar(context,
S.of(context)!.errorUploadingFile(e.toString())), which can leak internals;
instead log the full error with the shared logger (e.g., call logger.e(e) or
logger.e('Upload error', e)) and pass a safe localized message to the UI (use
S.of(context)!.errorUploadingFile() or a new generic localization key) so the
snackbar displays user-friendly text; update the code around the catch (e) { if
(mounted) { ... } } block to remove e.toString() from the UI and add the
logger.e(...) call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cbc8904a-6c4c-42a9-8af4-28860513cdca

📥 Commits

Reviewing files that changed from the base of the PR and between a84605c and ca4ca72.

📒 Files selected for processing (2)
  • docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md
  • lib/features/disputes/widgets/dispute_message_input.dart

Comment thread docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md
Comment thread docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md Outdated
Comment thread lib/features/disputes/widgets/dispute_message_input.dart Outdated
@Catrya
Copy link
Copy Markdown
Member Author

Catrya commented Mar 5, 2026

@mostronatorcoder
Here's a summary of what was addressed and what was intentionally deferred:

Addressed (with adjustments):

  • Service instances as instance fields, Fixed with static final instead of Riverpod providers. Both services are fully stateless (empty constructors, zero fields, all methods delegate to static helpers). A provider would add complexity with no benefit here. The P2P MessageInput uses the same pattern, both will be unified in Phase 5.

  • No file size validation before upload , Fixed with file.length() (filesystem metadata, no memory allocation) before any readAsBytes(). The suggested FileValidationService.validateFile(file, filename) doesn't match the actual API, validateFile(File) takes one parameter and internally calls readAsBytes() itself, which wouldn't prevent the OOM. The file.length() approach is the correct fix.

  • readAsBytes() for MIME detection Fixed exactly as suggested. Replaced with lookupMimeType(filename), removed dead _isImageFile/_getMimeType helpers and unused dart:typed_data import.

    Intentionally not addressed:

  • Double JSON parsing, MessageTypeUtils.getMessageType() is shared code used by P2P chat. Changing its return type to include parsed metadata would require updating all P2P call sites (MessageBubble, etc.), breaking the Phase 3 contract of zero P2P file changes. The cost of parsing ~200 bytes twice per multimedia bubble is negligible. If MessageTypeUtils is refactored in Phase 5 for other reasons, this can be done as a side improvement.

  • Hardcoded English time strings, This is pre-existing code, not introduced by this PR. It's already documented in the phase plan (Section 5.6: "Localize timestamps in DisputeMessageBubble") with a concrete implementation approach (replace with timeago package + timeAgoWithLocale()). A // TODO comment would be redundant with actively-tracked plan documentation.

  • Separate catch for getAdminSharedKey() failure, This scenario cannot occur by UI design. The dispute message input widget is only rendered inside DisputeContent when an admin is already assigned. No admin = no input widget = no attach button. Adding a dedicated catch + a new localization key in 3 languages for an unreachable code path is unnecessary. If it somehow did occur, the generic error with the actual exception message is more useful for debugging.

Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

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

All three blocking issues resolved in ca4ca72:

  • ✅ Services are now static final — no resource leak
  • file.length() pre-check prevents OOM on large files
  • ✅ MIME detection via lookupMimeType(filename) — no more full-file readAsBytes()
  • ✅ Removed unused _isImageFile(), _getMimeType() helpers

The three non-blocking suggestions (double JSON parse, timeago TODO, admin key error UX) are fine to address in a future phase.

LGTM 👍

@grunch grunch merged commit 5526210 into main Mar 6, 2026
2 checks passed
@grunch grunch deleted the phase-3-dispute-multimedia-send branch March 6, 2026 00:36
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