Files
Kecalek/specs/agent-f-chat-screen.md
filip fe861cfafa Initial commit: Kecalek Android client
Complete Android client for encrypted chat platform.
78+ Kotlin files: crypto (X3DH, Double Ratchet, AES-GCM, Ed25519, X25519,
RSA-PSS), network (TCP/TLS, 50 endpoints), Hilt DI, Room+SQLCipher DB,
Jetpack Compose UI with Catppuccin Mocha theme.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 01:19:17 +01:00

231 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<Message> = emptyList(),
val conversation: Conversation? = null,
val members: List<ConversationMember> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null,
val replyingTo: Message? = null,
val isSearchActive: Boolean = false,
val searchQuery: String = "",
val searchResults: List<Int> = 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<ChatUiState> = _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)