Files
2026-04-01 14:17:27 +00:00

25 KiB

SimpleX Chat iOS -- Chat View Module

Technical specification for the message rendering, chat item types, and context menu actions in the conversation view.

Related specs: Compose Module | State Management | API Reference | README Related product: Chat View

Source: ChatView.swift | ChatInfoView.swift | GroupChatInfoView.swift | ChannelMembersView.swift | ChannelRelaysView.swift


Table of Contents

  1. Overview
  2. ChatView
  3. ChatItemView -- Message Routing
  4. Message Renderers
  5. Media Views
  6. Metadata & Info
  7. Context Menu Actions
  8. Selection Mode

1. Overview

The chat view module renders individual conversations. It consists of:

  • ChatView -- The main conversation screen with message list, compose bar, and navigation
  • ChatItemView -- Router that dispatches each chat item to the appropriate renderer
  • Specialized renderers -- FramedItemView (standard messages), EmojiItemView (emoji-only), CICallItemView (calls), event views, etc.
  • Media views -- CIImageView, CIVideoView, CIVoiceView, CIFileView for attachments
ChatView
├── Message List (ScrollView / LazyVStack)
│   ├── ChatItemView (per message)
│   │   ├── FramedItemView (text/media bubbles)
│   │   │   ├── MsgContentView (text with markdown)
│   │   │   ├── CIImageView / CIVideoView / CIVoiceView
│   │   │   └── CIMetaView (timestamp, status)
│   │   ├── EmojiItemView (emoji-only messages)
│   │   ├── CICallItemView (call events)
│   │   ├── CIEventView (system events)
│   │   ├── CIGroupInvitationView (group invitations)
│   │   ├── DeletedItemView / MarkedDeletedItemView
│   │   └── CIInvalidJSONView (decode errors)
│   └── ... (more items)
├── ComposeView (message input)
└── Navigation bar (contact/group info)

2. ChatView

File: Shared/Views/Chat/ChatView.swift

The main conversation view. Key responsibilities:

State

  • Uses ItemsModel.shared.reversedChatItems for the primary message list
  • ChatModel.shared.chatId identifies the active conversation
  • Manages compose state, scroll position, keyboard visibility
  • Tracks selection mode for multi-message actions

Message List

  • Renders messages in a ScrollViewReader with LazyVStack
  • Items are in reverse chronological order (newest at bottom)
  • Supports infinite scroll: preloads older messages when scrolling up via ItemsModel.preloadState
  • Handles pagination splits (chatState.splits) for non-contiguous loaded ranges

Navigation Bar

  • Title: contact name / group name with connection status indicator
  • Trailing button: navigates to ChatInfoView (direct) or GroupChatInfoView (group)
  • Search button: toggles in-chat message search

Scroll Behavior

  • Auto-scrolls to bottom on new sent/received messages (if already near bottom)
  • "Scroll to bottom" floating button when scrolled up
  • openAroundItemId support: scrolls to a specific message (e.g., from search or notification)

Key Functions

Function Line Description
body L75 Main view body
initChatView() L660 Initializes chat view state on appear
chatItemsList() L817 Builds the scrollable message list
scrollToItem(_:) L731 Scrolls to a specific message by ID
searchToolbar() L765 In-chat search toolbar UI
searchTextChanged(_:) L1095 Handles search query changes
loadChatItems(_:_:) L1531 Loads chat items with pagination
filtered(_:) L803 Filters items by content type
callButton(_:_:imageName:) L1273 Audio/video call toolbar button
searchButton() L1293 Search toggle toolbar button
addMembersButton() L1361 Group add-members toolbar button
forwardSelectedMessages() L1420 Forwards batch-selected messages
deletedSelectedMessages() L1411 Deletes batch-selected messages
onChatItemsUpdated() L1572 Reacts to chat items model changes
contentFilterMenu(withLabel:) L1301 Content filter dropdown menu

Supporting Types

Type Line Description
ChatItemWithMenu L1600 Wraps each chat item with context menu
FloatingButtonModel L2787 Manages scroll-to-bottom button state
ReactionContextMenu L2974 Reaction picker context menu
ToggleNtfsButton L3072 Mute/unmute notifications button
ContentFilter L3124 Enum for message content filter types
deleteMessages() L2870 Deletes messages with confirmation
archiveReports() L2917 Archives report messages

3. ChatItemView

File: Shared/Views/Chat/ChatItemView.swift

Routes each ChatItem to the appropriate renderer based on its CIContent type:

Content Types (CIContent enum)

Content Type Renderer Line Description
sndMsgContent / rcvMsgContent FramedItemView L14 Standard sent/received text+media message
sndDeleted / rcvDeleted DeletedItemView L14 Locally deleted message placeholder
sndCall / rcvCall CICallItemView L13 Call event (missed, ended, duration)
rcvIntegrityError IntegrityErrorItemView L14 Message integrity error
rcvDecryptionError CIRcvDecryptionError L16 Decryption failure
sndGroupInvitation / rcvGroupInvitation CIGroupInvitationView L14 Group invite
sndGroupEvent / rcvGroupEvent CIEventView L14 Group system event
rcvConnEvent / sndConnEvent CIEventView L14 Connection event
rcvChatFeature / sndChatFeature CIChatFeatureView L14 Feature toggle event
rcvChatPreference / sndChatPreference CIFeaturePreferenceView L14 Preference change
invalidJSON CIInvalidJSONView L14 Failed to decode

Bubble Direction

  • Sent messages: aligned right, sender-colored bubble
  • Received messages: aligned left, receiver-colored bubble
  • Events/system messages: centered, no bubble

Appearance Dependencies

Each ChatItemWithMenu may depend on the previous and next items for visual decisions:

  • Whether to show the sender name (group messages, different sender than previous)
  • Whether to show the tail on the bubble (last consecutive message from same sender)
  • Date separator between messages on different days

ChatItemDummyModel.shared.sendUpdate() forces a re-render of all items when global appearance changes.

Channel Message Rendering (.channelRcv)

Channel messages (CIDirection.channelRcv) are rendered with the group avatar and group name as sender, with "channel" as the role label. This mirrors the .groupRcv path's showGroupAsSender visual but uses a dedicated code branch in chatItemListView().

Key differences from .groupRcv:

  • No prevMember/memCount logic — channels have no per-member identity
  • Always shows group avatar (via ProfileImage with groupInfo.image / groupInfo.chatIconName)
  • Tapping avatar opens showChatInfoSheet (not member info)
  • shouldShowAvatar() treats consecutive .channelRcv items as same sender
  • getItemSeparation() treats consecutive .channelRcv items as sameMemberAndDirection
  • showMemberImage() returns true when previous item is .channelRcv (different sender type)
  • memberToModerate() returns nil for .channelRcv (no per-member moderation)

4. Message Renderers

FramedItemView

File: Shared/Views/Chat/ChatItem/FramedItemView.swift

The standard message bubble. Renders:

  • Quote/reply preview (if replying to another message)
  • Forwarded indicator
  • Sender name (in groups)
  • Message content (MsgContentView with markdown)
  • Attached media (image, video, voice, file, link preview)
  • Reaction summary bar
  • Metadata line (CIMetaView)

EmojiItemView

File: Shared/Views/Chat/ChatItem/EmojiItemView.swift

Renders emoji-only messages (messages containing only emoji characters) in a larger font without a bubble background.

MsgContentView

File: Shared/Views/Chat/ChatItem/MsgContentView.swift

Renders message text with SimpleX markdown formatting (bold, italic, code, links, mentions).

DeletedItemView / MarkedDeletedItemView

Files: Shared/Views/Chat/ChatItem/DeletedItemView.swift | Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift

CIEventView

File: Shared/Views/Chat/ChatItem/CIEventView.swift

Centered system event text for group events (member joined, left, role changed) and connection events.

CIGroupInvitationView

File: Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift

Renders group invitation with accept/reject buttons.


5. Media Views

CIImageView

File: Shared/Views/Chat/ChatItem/CIImageView.swift

Renders inline images. Tapping opens FullScreenMediaView for zooming/panning. Images are compressed to MAX_IMAGE_SIZE (255KB) before sending.

CIVideoView

File: Shared/Views/Chat/ChatItem/CIVideoView.swift

Renders video thumbnails with play button. Tapping opens video player. Videos above auto-receive threshold require manual download.

CIVoiceView / FramedCIVoiceView

Files: Shared/Views/Chat/ChatItem/CIVoiceView.swift | Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift

Renders voice messages with waveform visualization, play/pause control, and duration. FramedCIVoiceView is the version inside a message bubble with additional context.

CIFileView

File: Shared/Views/Chat/ChatItem/CIFileView.swift

Renders file attachments with filename, size, and download/open actions. Shows transfer progress during upload/download.

CILinkView

File: Shared/Views/Chat/ChatItem/CILinkView.swift

Renders link preview cards with OpenGraph metadata (title, description, image).

AnimatedImageView

File: Shared/Views/Chat/ChatItem/AnimatedImageView.swift

Renders animated GIF images.

FullScreenMediaView

File: Shared/Views/Chat/ChatItem/FullScreenMediaView.swift

Full-screen media viewer with zoom, pan, and share actions. Supports images and videos.


6. Metadata & Info

CIMetaView

File: Shared/Views/Chat/ChatItem/CIMetaView.swift

Displays message metadata inline at the bottom of the bubble:

  • Timestamp (sent time)
  • Delivery status icon (sending, sent, delivered, read, error)
  • Edit indicator (pencil icon if message was edited)
  • Disappearing message timer (if timed message)

ChatItemInfoView

File: Shared/Views/Chat/ChatItemInfoView.swift

Detailed message information sheet (accessed via long-press menu "Info"):

  • Full delivery history (per-member delivery status in groups)
  • Edit history (all previous versions of edited messages)
  • Forward chain info
  • Message timestamps (created, updated, deleted)

7. Context Menu Actions

Long-pressing a message shows a context menu with actions based on message type and ownership:

Action Available For API Command
Reply All messages Sets compose state to .replying
Forward Sent/received content messages apiForwardChatItems
Copy Text messages Copies to clipboard
Edit Own sent messages (within edit window) apiUpdateChatItem
Delete for me All messages apiDeleteChatItem(mode: .cidmInternal)
Delete for everyone Own sent messages apiDeleteChatItem(mode: .cidmBroadcast)
Moderate Group admin/owner for others' messages apiDeleteMemberChatItem
React Content messages (if reactions enabled) apiChatItemReaction
Select All messages Enters multi-select mode
Info All messages Opens ChatItemInfoView
Save Media messages Saves to photo library / files
Share Content messages iOS share sheet

8. Selection Mode

Multi-selection mode allows batch operations on messages:

  • Enter via long-press "Select" action
  • Toggle individual messages with tap
  • Toolbar appears with batch actions: Delete, Forward
  • Exit via cancel button or completing batch action

GroupChatInfoView — Channel Adaptations

When groupInfo.useRelays == true, GroupChatInfoView adapts its sections:

Section Structure (Channel)

Section Owner Subscriber
1. Links & Members Channel link (manage via GroupLinkView), Owners & subscribers Channel link (read-only QR from groupProfile.publicGroup?.groupLink), Owners
2. Profile & Welcome Edit channel profile, Welcome message Welcome message (if exists)
3. Theme & TTL Chat theme, Delete messages after Chat theme, Delete messages after
4. Actions Chat relays, Clear chat, Delete channel Chat relays, Clear chat, Leave channel

Hidden for channels: Member support, group reports, user support chat, send receipts, inline members list, group preferences.

Label Replacements

All "group" labels are replaced with "channel" equivalents via groupInfo.useRelays ? "Channel..." : ternary prepended before existing businessChat ternary. Affected: delete/leave buttons, delete/leave alerts, remove member alert, edit profile button, group link nav title. Channel link button uses a separate channelLinkButton() with hardcoded "Channel link" label.

channelMembersButton()ChannelMembersView

Navigates to a dedicated members view with two sections:

  • Owners: current user (if owner) + members with memberRole >= .owner
  • Subscribers (admin+ only): members with memberRole < .owner

Member rows show profile image, display name (with verified shield), connection status, and role badge. Non-user rows link to GroupMemberInfoView.

Owner sees channelLinkButton() (navigates to GroupLinkView for full link management), guarded by groupInfo.isOwner && groupLink != nil — channel links can only be created during channel creation, not from the info view. A TODO marks the need for protocol changes to allow other owners to manage the same channel link. Non-owner sees read-only QR code displaying groupProfile.publicGroup?.groupLink via SimpleXLinkQRCode. apiGetGroupLink is skipped in onAppear for non-owner channels.

Groups use separate groupLinkButton() which supports both "Create group link" and "Group link" labels.

channelRelaysButton()ChannelRelaysView

Navigates to relay list view with role-based branches:

  • Owner: loads [GroupRelay] via apiGetGroupRelays (owner-only API, guarded by assertUserGroupRole GROwner on backend). Joins with chatModel.groupMembers by groupMemberId for display names. Shows status indicators (colored circle + RelayStatus.text).
  • Member: filters chatModel.groupMembers by .memberRole == .relay. Shows relay member display names only (no status data).

Leave Button Logic

Sole channel owner cannot leave (only delete). Guard: members.filter({ $0.wrapped.memberRole == .owner && $0.wrapped.groupMemberId != groupInfo.membership.groupMemberId }).count > 0.


Source Files

File Path Line
Chat view Shared/Views/Chat/ChatView.swift L18
Item router Shared/Views/Chat/ChatItemView.swift L42
Framed bubble Shared/Views/Chat/ChatItem/FramedItemView.swift L14
Emoji message Shared/Views/Chat/ChatItem/EmojiItemView.swift L14
Image view Shared/Views/Chat/ChatItem/CIImageView.swift L14
Video view Shared/Views/Chat/ChatItem/CIVideoView.swift L16
Voice view Shared/Views/Chat/ChatItem/CIVoiceView.swift L14
File view Shared/Views/Chat/ChatItem/CIFileView.swift L14
Link preview Shared/Views/Chat/ChatItem/CILinkView.swift L14
Call event Shared/Views/Chat/ChatItem/CICallItemView.swift L13
Metadata Shared/Views/Chat/ChatItem/CIMetaView.swift L14
Message info Shared/Views/Chat/ChatItemInfoView.swift L13
System event Shared/Views/Chat/ChatItem/CIEventView.swift L14
Deleted placeholder Shared/Views/Chat/ChatItem/DeletedItemView.swift L14
Moderated placeholder Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift L14
Text content Shared/Views/Chat/ChatItem/MsgContentView.swift L28
Group invitation Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift L14
Feature event Shared/Views/Chat/ChatItem/CIChatFeatureView.swift L14
Decryption error Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift L16
Integrity error Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift L14
Full-screen media Shared/Views/Chat/ChatItem/FullScreenMediaView.swift L16
Animated image Shared/Views/Chat/ChatItem/AnimatedImageView.swift L11
Framed voice Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift L16
Member contact Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift L14
Channel members Shared/Views/Chat/Group/ChannelMembersView.swift L12
Channel relays Shared/Views/Chat/Group/ChannelRelaysView.swift L12