9.9 KiB
Chat View (Conversation)
Related spec: spec/client/chat-view.md | spec/client/compose.md
Purpose
Full conversation view for displaying and interacting with messages in a direct contact chat, group chat, or note-to-self. Supports text messaging with markdown, media attachments, voice messages, E2E encrypted calls, message reactions, replies, forwarding, and content search/filtering.
Route / Navigation
- Entry point: Tap a chat row in
ChatListView - Presented by:
NavStackCompatdestination fromChatListView, bound tochatModel.chatId - Back navigation: Dismiss sets
chatModel.chatId = nil, returning to chat list - Sub-navigation: Info button navigates to
ChatInfoView(contact) orGroupChatInfoView(group); member avatars navigate toGroupMemberInfoView
Page Sections
Navigation Bar
Custom toolbar overlaying the chat with themed material background:
| Element | Description |
|---|---|
| Back button | Returns to chat list |
| Contact/Group avatar | Small profile image |
| Chat name | Display name; tappable to open info sheet |
| Encryption badge | Shows PQ (post-quantum) or standard E2E status |
| Call buttons | Audio and video call icons (direct chats only) |
| Search button | Toggles in-chat message search |
| Info button | Opens ChatInfoView or GroupChatInfoView |
Message List
Rendered by EndlessScrollView<MergedItem> with lazy loading and pagination:
| Feature | Description |
|---|---|
| Scroll direction | Bottom-to-top (newest messages at bottom) |
| Pagination | Loads more items on scroll to top (loadingTopItems) and bottom (loadingBottomItems) |
| Merged items | Adjacent messages from the same sender are visually merged via MergedItems |
| Floating buttons | Scroll-to-bottom button with unread count; scroll-to-first-unread button |
| Date separators | Sticky date headers between messages from different days |
| Wallpaper | Themed background image with tint and opacity from theme.wallpaper |
| Content filter | Filter messages by type: .images, .files, .links |
Message Types
Each type has a dedicated view in Shared/Views/Chat/ChatItem/:
| Type | View | Description |
|---|---|---|
| Text | MsgContentView |
Rendered with markdown (bold, italic, code, links, mentions) |
| Image | CIImageView |
Thumbnail with tap-to-fullscreen via FullScreenMediaView |
| Video | CIVideoView |
Video thumbnail with play button; inline playback |
| Voice | CIVoiceView / FramedCIVoiceView |
Waveform visualization with playback controls and duration |
| File | CIFileView |
File icon, name, size; download/open actions |
| Link preview | CILinkView |
URL preview card with title, description, image |
| Emoji-only | EmojiItemView |
Large emoji rendering without message bubble |
| Call event | CICallItemView |
Call status (missed, ended, duration) |
| Group event | CIEventView |
Member joined/left, role changes, group updates |
| E2EE info | CIChatFeatureView |
Encryption status and feature change notifications |
| Group invitation | CIGroupInvitationView |
Inline group join invitation card |
| Deleted | DeletedItemView / MarkedDeletedItemView |
Placeholder for deleted messages |
| Decryption error | CIRcvDecryptionError |
Error with ratchet sync suggestion |
| Invalid JSON | CIInvalidJSONView |
Developer fallback for malformed items |
| Integrity error | IntegrityErrorItemView |
Message integrity/gap warnings |
Message Interactions
Long-press context menu on any message:
| Action | Description |
|---|---|
| Reply | Sets compose bar to reply mode with quoted message |
| Forward | Opens forwardedChatItems sheet to pick destination chat |
| Copy | Copies message text to clipboard |
| Edit | Enters edit mode in compose bar (own messages, within edit window) |
| Delete | Delete for self or delete for everyone (with confirmation) |
| React | Opens emoji reaction picker |
| Select multiple | Enters multi-select mode (selectedChatItems) with bulk delete/forward |
| Info | Shows delivery status and timestamps |
Emoji reactions bar displayed below messages with reaction counts.
Compose Bar (ComposeView)
| Element | Description |
|---|---|
| Text input | NativeTextEditor with markdown support and auto-growing height |
| Attachment button | Opens picker for images, videos, files, camera |
| Send button | Sends composed message; changes to voice record button when empty |
| Voice record | Hold-to-record with waveform preview; swipe-to-cancel |
| Reply quote | Shows quoted message above input when replying |
| Edit indicator | Shows "editing" label when editing a previous message |
| Link preview | Auto-generated preview card for detected URLs (ComposeLinkView) |
| Image/Video preview | Thumbnail strip for selected media (ComposeImageView) |
| File preview | File name and size for attached file (ComposeFileView) |
| Voice preview | Waveform of recorded voice message (ComposeVoiceView) |
| Live message | Real-time typing broadcast (optional, with alert on first use) |
| Context actions | ContextContactRequestActionsView for accepting/rejecting contact requests; ContextPendingMemberActionsView for pending group member actions |
| Commands menu | CommandsMenuView for bot/menu commands in chats with menuCommands |
| Group mentions | GroupMentionsView autocomplete popup when typing @ in groups |
| Profile picker | ContextProfilePickerView for choosing incognito/main profile |
Channel Messages
In channel conversations (groupInfo.useRelays == true), received messages (.channelRcv direction) display with:
- The channel icon (
antenna.radiowaves.left.and.right) instead of the standard group icon - The channel name as sender, with "channel" as the role label
- The group profile image as the avatar (tapping opens group info, not member info)
- Consecutive channel messages are grouped without repeating the avatar
- Channel messages cannot be moderated per-member (no member identity)
Member Support Chat (Groups)
For groups with member support enabled:
MemberSupportViewandMemberSupportChatToolbarshown as secondary chat within groupSecondaryChatViewfor scoped group chat views (reports, member support)- User knocking state:
userMemberKnockingTitleBar()shown when user is pending admission
Loading / Error States
| State | Behavior |
|---|---|
| Initial load | Messages load from ItemsModel with merged items; allowLoadMoreItems throttles pagination |
| Loading more (top) | loadingTopItems spinner at top of scroll view |
| Loading more (bottom) | loadingBottomItems spinner at bottom |
| Connection in progress | ConnectProgressManager shows connecting text below compose bar |
| Connecting text | "connecting..." label shown below message list when chat not yet ready |
| Send disabled | Compose bar shows disabledText reason when userCantSendReason is set |
| Empty chat | No messages placeholder (implicit -- empty scroll view) |
Related Specs
spec/client/chat-view.md-- Chat view feature specificationspec/client/compose.md-- Compose bar specification- Chat List -- Parent navigation
- Contact Info -- Info sheet for direct chats
- Group Info -- Info sheet for group chats
- Call -- Audio/video calls initiated from toolbar
Source Files
Shared/Views/Chat/ChatView.swift-- Main chat view, message list, navigation, state managementShared/Views/Chat/ChatItemView.swift-- Individual message item rendering dispatcherShared/Views/Chat/ComposeMessage/ComposeView.swift-- Compose bar containerShared/Views/Chat/ComposeMessage/SendMessageView.swift-- Send button and voice recordShared/Views/Chat/ComposeMessage/NativeTextEditor.swift-- Text input with markdownShared/Views/Chat/ComposeMessage/ComposeImageView.swift-- Image attachment previewShared/Views/Chat/ComposeMessage/ComposeFileView.swift-- File attachment previewShared/Views/Chat/ComposeMessage/ComposeVoiceView.swift-- Voice recording previewShared/Views/Chat/ComposeMessage/ComposeLinkView.swift-- Link preview generationShared/Views/Chat/ComposeMessage/ContextItemView.swift-- Reply/edit context displayShared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift-- Contact request accept/rejectShared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift-- Pending member actionsShared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift-- Profile picker for incognitoShared/Views/Chat/ChatItem/FramedItemView.swift-- Framed message bubble renderingShared/Views/Chat/ChatItem/MsgContentView.swift-- Text message content with markdownShared/Views/Chat/ChatItem/CIImageView.swift-- Image message viewShared/Views/Chat/ChatItem/CIVideoView.swift-- Video message viewShared/Views/Chat/ChatItem/CIVoiceView.swift-- Voice message viewShared/Views/Chat/ChatItem/CIFileView.swift-- File message viewShared/Views/Chat/ChatItem/CILinkView.swift-- Link preview viewShared/Views/Chat/ChatItem/EmojiItemView.swift-- Large emoji viewShared/Views/Chat/ChatItem/CICallItemView.swift-- Call event viewShared/Views/Chat/ChatItem/CIEventView.swift-- Group/system event viewShared/Views/Chat/ChatItem/CIChatFeatureView.swift-- Feature change notificationShared/Views/Chat/ChatItem/CIMetaView.swift-- Timestamp and delivery statusShared/Views/Chat/ChatItem/FullScreenMediaView.swift-- Fullscreen image/video viewerShared/Views/Chat/ChatItem/AnimatedImageView.swift-- Animated GIF renderingShared/Views/Chat/Group/GroupMentions.swift-- @mention autocompleteShared/Views/Chat/Group/MemberSupportView.swift-- Member support scoped chatShared/Views/Chat/Group/MemberSupportChatToolbar.swift-- Support chat toolbarShared/Views/Chat/Group/SecondaryChatView.swift-- Secondary scoped chat view