# Agent J: File Sharing UI ## Phase: 4 (Feature Completion) ## Depends on: Agent F (Chat Screen), Agent C (models with FileInfo/ImageInfo) ## Context File sharing uses chunked encrypted upload/download (AES-256-GCM). The UI handles image/file picking, thumbnail display, download progress, and file type icons. Encryption logic is handled by ChatClient (Claude Code) — this agent only handles UI. ## Task Create file/image picker integration, download UI, file type icons, and thumbnail display. ## Files to Create ### 1. ui/chat/AttachmentSheet.kt Bottom sheet shown when attachment button is tapped: - **"Image"** option with gallery icon - Opens system image picker (ActivityResultContracts.PickVisualMedia) - Shows selected image preview before sending - **"File"** option with document icon - Opens system file picker (ActivityResultContracts.OpenDocument) - Shows selected file name + size before sending - **"Camera"** option with camera icon - Opens camera to take photo (ActivityResultContracts.TakePicture) ### 2. ui/chat/ImageThumbnail.kt Composable for image message display in chat: - Shows base64 JPEG thumbnail from ImageInfo.thumbnail - Max width 200dp, maintain aspect ratio - Rounded corners (8dp) - Loading shimmer while full image loads - On tap: navigate to ImageViewer with full image URL - Download progress overlay (if downloading full resolution) ### 3. ui/chat/FileCard.kt Composable for file attachment display in chat: - Horizontal layout: - File type icon (40dp, left): - PDF: Red document icon - Image: Blue image icon - Video: Purple play icon - Audio: Green music icon - Archive: Yellow archive icon - Default: Gray document icon - Center (weight 1f): - Filename (bold, 1 line, ellipsize end) - File size (formatted: KB, MB) + mime type - Download button/progress (right): - Download icon (if not downloaded) - CircularProgressIndicator (if downloading) - Checkmark (if downloaded) - Background: Surface1 with 1dp Surface2 border, rounded 8dp - On tap: download if not downloaded, open if downloaded ### 4. ui/chat/DownloadProgress.kt Reusable download progress composable: - Circular progress indicator with percentage text - Cancel button - File size downloaded / total ### 5. util/FileUtils.kt ```kotlin package com.kecalek.chat.util import android.content.Context import android.net.Uri import android.webkit.MimeTypeMap object FileUtils { fun getFileName(context: Context, uri: Uri): String { // Query ContentResolver for display name val cursor = context.contentResolver.query(uri, null, null, null, null) cursor?.use { if (it.moveToFirst()) { val nameIndex = it.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME) if (nameIndex >= 0) return it.getString(nameIndex) } } return uri.lastPathSegment ?: "unknown" } fun getFileSize(context: Context, uri: Uri): Long { val cursor = context.contentResolver.query(uri, null, null, null, null) cursor?.use { if (it.moveToFirst()) { val sizeIndex = it.getColumnIndex(android.provider.OpenableColumns.SIZE) if (sizeIndex >= 0) return it.getLong(sizeIndex) } } return 0 } fun getMimeType(context: Context, uri: Uri): String { return context.contentResolver.getType(uri) ?: MimeTypeMap.getSingleton() .getMimeTypeFromExtension(uri.toString().substringAfterLast('.')) ?: "application/octet-stream" } fun formatFileSize(bytes: Long): String = when { bytes < 1024 -> "$bytes B" bytes < 1024 * 1024 -> "${bytes / 1024} KB" bytes < 1024 * 1024 * 1024 -> "${"%.1f".format(bytes / (1024.0 * 1024.0))} MB" else -> "${"%.1f".format(bytes / (1024.0 * 1024.0 * 1024.0))} GB" } fun getFileTypeIcon(mimeType: String): FileTypeIcon = when { mimeType.startsWith("image/") -> FileTypeIcon.IMAGE mimeType.startsWith("video/") -> FileTypeIcon.VIDEO mimeType.startsWith("audio/") -> FileTypeIcon.AUDIO mimeType == "application/pdf" -> FileTypeIcon.PDF mimeType.contains("zip") || mimeType.contains("tar") || mimeType.contains("rar") -> FileTypeIcon.ARCHIVE else -> FileTypeIcon.DOCUMENT } enum class FileTypeIcon { PDF, IMAGE, VIDEO, AUDIO, ARCHIVE, DOCUMENT } } ``` ## Constraints - Use ActivityResultContracts for file/image picking (no deprecated startActivityForResult) - Thumbnail display from base64 data (not URL loading) - Max image display width: 200dp in chat - File cards: consistent height, ellipsize long filenames - Download progress: 0-100% with cancel option ## DO NOT - Implement actual file encryption/decryption - Implement actual chunked upload/download - Handle server communication - Store files on disk (that's ChatClient's job)