Feature: Chat Image Upload

Branch: feat/chat-image-upload Date: 2026-03-18 Impact: Users can now send photos in chat (previously silently dropped)


Root Cause

_pickImage() in chat_screen.dart selected an image and then discarded it — showing a “Upload foto in arrivo! 📸” snackbar but never uploading or sending anything.


Implementation

Upload Flow

User taps photo icon
  → _pickImage(source) picks XFile via image_picker
  → _sendImageMessage(XFile) called
  → Progress snackbar shown
  → XFile.readAsBytes() → Uint8List (works on web + native)
  → Supabase Storage upload to chat-attachments/{chatId}/{uuid}.{ext}
  → getPublicUrl() retrieves the public URL
  → MessageAttachment created with id, url, type=image, name, size, mimeType
  → chatNotifier.sendMessage(type: image, attachments: [attachment])
     → optimistic Message added to chat immediately (status: sending)
     → confirmed from server response
  → _scrollToBottom() called

Storage Path

chat-attachments/
  {chatId}/
    {uuid}.jpg
    {uuid}.png

Key Technical Choices

  • XFile.readAsBytes() instead of File(path) — works on both web and native without kIsWeb guards
  • uploadBinary() instead of upload() — accepts Uint8List, bypasses file path issues on web
  • Progress feedback — shows a spinner snackbar during upload, hides it on completion or error

Required Infrastructure

Create the Supabase Storage bucket chat-attachments with public access policy:

-- Create bucket (via Supabase dashboard or SQL)
INSERT INTO storage.buckets (id, name, public)
VALUES ('chat-attachments', 'chat-attachments', true);
 
-- RLS: authenticated users can upload to their own chat paths
CREATE POLICY "Users can upload chat images"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (bucket_id = 'chat-attachments');
 
-- RLS: anyone can read (public bucket)
CREATE POLICY "Public read chat images"
ON storage.objects FOR SELECT
USING (bucket_id = 'chat-attachments');

Files Changed

FileChange
lib/features/messaging/screens/chat_screen.dart_pickImage() fixed, _sendImageMessage() added