# Agent F: Chat Screen ## Phase: 2 (UI Shells) ## Depends on: Agent A, Agent B (theme + navigation), Agent C (models) ## Context The chat screen is the core messaging interface. It shows encrypted messages in bubbles, supports replies, file attachments, reactions, pins, and real-time updates. ## Task Create ChatScreen, MessageBubble, MessageInput, ImageViewer, and ChatViewModel skeleton. ## Files to Create ### 1. ui/chat/ChatScreen.kt Jetpack Compose screen with: - **Top bar (custom)**: - Back arrow (left) - Circular avatar (32dp) - Conversation name + "Encrypted" / "Verified" label below (small, Green or Overlay1) - Action buttons (right): Search icon, Info/Group icon - **Message list** (LazyColumn, reverseLayout = true): - Messages grouped by date (date separator headers) - Each message is a MessageBubble - Scroll to bottom FAB (shown when not at bottom) - **Reply preview bar** (shown when replying to a message): - Vertical Lavender bar + replied message text preview + close button - **Search overlay** (shown when search active): - Search text field with prev/next buttons + match count - Highlighted matches in messages - **Message input** at bottom (MessageInput composable) ### 2. ui/chat/MessageBubble.kt Composable for a single message: - **Own messages**: Right-aligned, Lavender background (alpha 0.15) - **Other's messages**: Left-aligned, Surface0 background - **Content layout** (vertical): - Sender name (only in groups, for other's messages, bold, Lavender color) - Forwarded header (if forwarded): "Forwarded from Username" with blue left border - Reply reference (if reply): small gray box with replied message text (1 line) - Text content with: - Link detection (Blue, underlined) - @mention highlighting (Blue, bold) - Image thumbnail (if image): clickable, shows full-size on tap - File card (if file): icon + filename + size, clickable to download - **Bottom row** (horizontal): - Timestamp (Overlay1, small) - Pin icon (if pinned) - Read receipt indicators: - 1 checkmark = sent - 2 checkmarks = delivered - 2 blue checkmarks = read - Reaction badges (if reactions): row of emoji+count chips below message - **Deleted message**: "This message was deleted" in italic, Overlay1 color - **Long-press context menu**: - Reply - React (submenu with 6 emoji) - Copy text - Forward - Pin / Unpin - Delete (own messages only, Red color) - **Message shape**: Rounded rectangle (12dp), with tail on sender side ### 3. ui/chat/MessageInput.kt Composable for message input area: - **Layout** (horizontal): - Attachment button (left, paperclip icon) - On click: shows bottom sheet with "Image" and "File" options - Text field (weight 1f, pill-shaped, Surface1 background) - Placeholder: "Message..." - Auto-grow up to 4 lines - @mention detection triggers autocomplete popup - Send button (right, Lavender, circular, arrow icon) - Shown only when text is non-empty or attachment selected - **@mention autocomplete**: Popup above input showing matching member names - **Attachment preview**: Shows selected image/file name before sending ### 4. ui/chat/ImageViewer.kt Full-screen image viewer: - **Black background** with translucent system bars - **Zoomable image** (pinch-to-zoom, double-tap zoom, pan) - **Top bar**: Back button + filename (transparent background) - **Bottom bar**: Share button + Save button - **Gesture**: Swipe down to dismiss ### 5. ui/chat/ChatViewModel.kt ```kotlin package com.kecalek.chat.ui.chat import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import com.kecalek.chat.data.model.Conversation import com.kecalek.chat.data.model.ConversationMember import com.kecalek.chat.data.model.Message import javax.inject.Inject data class ChatUiState( val messages: List = emptyList(), val conversation: Conversation? = null, val members: List = emptyList(), val isLoading: Boolean = false, val error: String? = null, val replyingTo: Message? = null, val isSearchActive: Boolean = false, val searchQuery: String = "", val searchResults: List = emptyList(), // indices into messages val currentSearchIndex: Int = -1, val currentUserId: String = "", val verificationStatus: String = "encrypted", // "encrypted" or "verified" ) @HiltViewModel class ChatViewModel @Inject constructor( savedStateHandle: SavedStateHandle, // TODO: Inject repositories ) : ViewModel() { val conversationId: String = savedStateHandle["conversationId"] ?: "" private val _uiState = MutableStateFlow(ChatUiState()) val uiState: StateFlow = _uiState.asStateFlow() fun loadMessages() { // TODO: Load from cache + incremental sync from server } fun sendMessage(text: String) { // TODO: Encrypt and send message } fun sendImage(uri: String) { // TODO: Encrypt and upload image } fun sendFile(uri: String) { // TODO: Encrypt and upload file } fun deleteMessage(messageId: String) { // TODO: Soft-delete message } fun reactToMessage(messageId: String, reaction: String) { // TODO: Add/remove reaction } fun pinMessage(messageId: String) { // TODO: Pin/unpin message } fun forwardMessage(messageId: String, targetConversationId: String) { // TODO: Forward message to another conversation } fun setReplyTo(message: Message?) { _uiState.value = _uiState.value.copy(replyingTo = message) } fun toggleSearch() { val current = _uiState.value _uiState.value = current.copy( isSearchActive = !current.isSearchActive, searchQuery = "", searchResults = emptyList(), currentSearchIndex = -1, ) } fun search(query: String) { // TODO: Search through local message cache } fun nextSearchResult() { // TODO: Navigate to next search result } fun prevSearchResult() { // TODO: Navigate to previous search result } fun markAsRead() { // TODO: Mark visible messages as read } fun downloadFile(fileId: String) { // TODO: Download and decrypt file } } ``` ## Message Bubble Visual Spec ``` ┌──────────────────────────────────────────┐ │ Forwarded from Alice (fwd) │ │ ┌────────────────────────────────────┐ │ │ │ Replying to: original message... │ │ │ └────────────────────────────────────┘ │ │ Bob (sender name, groups only) │ │ │ │ Message text content here with │ │ @mentions highlighted in blue │ │ │ │ ┌────────────────────────────────────┐ │ │ │ [Image Thumbnail] │ │ │ └────────────────────────────────────┘ │ │ │ │ 12:34 📌 ✓✓ │ │ 👍2 ❤️1 │ └──────────────────────────────────────────┘ ``` ## Constraints - Use Material 3 components - LazyColumn with reverseLayout for chat (newest at bottom) - Message bubbles must be efficient for long conversations - Use `remember` for expensive computations in bubbles - Maximum bubble width: 80% of screen width - Image thumbnails: max 200dp width, maintain aspect ratio - Context menu via `DropdownMenu` on long-press ## DO NOT - Implement actual encryption/decryption - Implement actual file upload/download - Handle server communication - Implement QR code scanning (that's Agent G)