13 KiB
SimpleX Chat iOS -- Chat List Module
Technical specification for the conversation list, filtering, search, swipe actions, and user picker.
Related specs: Chat View | Navigation | State Management | README Related product: Chat List View
Source: ChatListView.swift
Table of Contents
- Overview
- ChatListView
- ChatPreviewView
- ChatListNavLink
- Filtering & Tags
- Search
- Swipe Actions
- UserPicker
- Floating Action Button
1. Overview
The chat list is the main screen of the app, displaying all conversations for the current user. It provides:
- Conversation previews with unread badges
- Filter tabs (All, Unread, Favorites, Groups, Contacts, Business, user-defined tags)
- Search across chat names and message content
- Swipe actions for quick operations
- User profile switcher
- Floating action button for new conversations
ChatListView
├── Navigation Bar
│ ├── User avatar (tap → UserPicker)
│ └── Filter tabs (TagListView)
├── Search bar (on pull-down or tap)
├── Chat List (List/LazyVStack)
│ └── ChatListNavLink (per conversation)
│ └── ChatPreviewView
│ ├── Avatar
│ ├── Chat name + last message preview
│ ├── Timestamp
│ └── Unread badge
├── FAB (New Chat button)
└── Pending connection cards
2. ChatListView
File: Shared/Views/ChatList/ChatListView.swift
The root list view. Key responsibilities:
Data Source
- Reads
ChatModel.shared.chats(all conversations) - Applies active filter from
ChatTagsModel.shared.activeFilter - Applies search query filtering via
filteredChats() - Sorts by last activity (most recent first), with pinned chats at top
Layout
- Uses SwiftUI
ListwithForEachover filtered chats - Each row is a
ChatListNavLinkwrapping aChatPreviewView - Pull-to-refresh triggers
updateChats()API call - Empty state:
ChatHelpview with getting-started guidance
Connection Cards
- Pending contact connections (
ChatInfo.contactConnection) shown as cards - Contact requests (
ChatInfo.contactRequest) shown with accept/reject UI viaContactRequestView
Key Functions
| Function | Line | Description |
|---|---|---|
body |
163 | Main view body |
filteredChats() |
472 | Applies active filter and search to chat list |
searchString() |
514 | Normalizes search text for comparison |
unreadBadge() |
448 | Renders unread count circle badge |
stopAudioPlayer() |
467 | Stops any playing voice message |
3. ChatPreviewView
File: Shared/Views/ChatList/ChatPreviewView.swift
Renders a single row in the chat list. Shows:
| Element | Source | Description |
|---|---|---|
| Avatar | chatInfo.image |
Profile image or default icon |
| Chat name | chatInfo.displayName |
Contact name, group name, or connection label |
| Last message | chat.chatItems.last |
Preview text of most recent message |
| Timestamp | chat.chatItems.last?.timestampText |
Relative time of last message |
| Unread badge | chat.chatStats.unreadCount |
Circular badge with unread count |
| Mute icon | chatInfo.chatSettings?.enableNtfs |
Bell-slash icon if notifications muted |
| Pin icon | -- | Pin indicator for pinned chats |
| Incognito icon | Contact.contactConnIncognito | Incognito mode indicator |
| Delivery status | Last sent item's meta.itemStatus |
Check marks for delivery confirmation |
Preview Text Rendering
- Text messages: first line of message content
- Images: camera icon + caption (if any)
- Files: paperclip icon + filename
- Voice: microphone icon + duration
- Calls: phone icon + call status
- Group events: system event description
- Encrypted/deleted: placeholder text
4. ChatListNavLink
File: Shared/Views/ChatList/ChatListNavLink.swift
Wraps ChatPreviewView in a navigation link with tap and swipe behavior:
Tap Behavior
- Direct chat: navigates to
ChatViewviaItemsModel.loadOpenChat(chatId)--contactNavLink()L93 - Group chat: navigates to
ChatView--groupNavLink()L214 - Contact request: shows
ContactRequestViewwith accept/reject --contactRequestNavLink()L486 - Contact connection: shows
ContactConnectionInfo--contactConnectionNavLink()L520 - Notes folder: navigates to
ChatView--noteFolderNavLink()L298
Navigation
- Uses
NavigationLink(iOS 15) or programmatic navigation (iOS 16+) - Sets
ChatModel.chatIdto trigger navigation ItemsModel.loadOpenChat()loads messages with a 250ms navigation delay for smooth animation
5. Filtering & Tags
Filter Tabs (TagListView)
File: Shared/Views/ChatList/TagListView.swift
Horizontal scrolling tab bar below the navigation bar. Tabs:
| Tab | Filter | Shows |
|---|---|---|
| All | nil |
All conversations |
| Unread | .unread |
Conversations with unread messages |
| Favorites | .presetTag(.favorites) |
Favorited conversations |
| Groups | .presetTag(.groups) |
Group conversations |
| Contacts | .presetTag(.contacts) |
Direct conversations |
| Business | .presetTag(.business) |
Business conversations |
| Group Reports | .presetTag(.groupReports) |
Groups with pending reports |
| User tags | .userTag(ChatTag) |
User-defined custom tags |
Filter matching is handled by presetTagMatchesChat() (L910) and the in-view TagsView struct (L705).
ChatTagsModel State
Filtering state is managed by ChatTagsModel (ChatModel.swift L183):
class ChatTagsModel: ObservableObject {
@Published var userTags: [ChatTag] = []
@Published var activeFilter: ActiveFilter? = nil
@Published var presetTags: [PresetTag: Int] = [:] // count per preset tag
@Published var unreadTags: [Int64: Int] = [:] // unread count per user tag
}
presetTagscounts are updated wheneverchatschanges viaupdateChatTags()(L197)- Tags with zero matching chats are auto-hidden
- Active filter is auto-cleared when its tag has no matching chats
Supporting Types
| Type | File | Line | Description |
|---|---|---|---|
PresetTag |
ChatListView.swift | 34 | Enum of built-in filter categories |
ActiveFilter |
ChatListView.swift | 49 | Enum wrapping preset, user-tag, or unread filter |
setActiveFilter() |
ChatListView.swift | 878 | Applies a filter and persists selection |
Tag Management Commands
apiCreateChatTag(tag: ChatTagData)-- create tagapiSetChatTags(type:, id:, tagIds:)-- assign tags to a chatapiDeleteChatTag(tagId:)-- delete tagapiUpdateChatTag(tagId:, tagData:)-- rename tagapiReorderChatTags(tagIds:)-- reorder tags
6. Search
Search is available via pull-down gesture or search button in the navigation bar.
Search bar UI: ChatListSearchBar (ChatListView.swift L578)
Filtering Logic
- Filters
ChatModel.chatsby matching search text against:chatInfo.displayName(contact/group name)chatInfo.localAlias(local alias)chatInfo.fullName(full name)
- For deeper message content search, uses
apiGetChat(chatId:, search:)parameter - Core logic in
filteredChats()(L480) andsearchString()(L523)
Search Results
- Matching chats are displayed in the same list format
- Results update as the user types (debounced)
- Clearing search restores the full filtered list
7. Swipe Actions
ChatListNavLink provides swipe actions on each row:
Leading Swipe (left-to-right)
| Action | Icon | Handler | Line | API | Condition |
|---|---|---|---|---|---|
| Pin / Unpin | pin | toggleFavoriteButton() |
347 | apiSetChatSettings (favorite) |
Always |
| Read / Unread | envelope | markReadButton() |
328 | apiChatRead / apiChatUnread |
Always |
Trailing Swipe (right-to-left)
| Action | Icon | Handler | Line | API | Condition |
|---|---|---|---|---|---|
| Mute / Unmute | bell.slash | toggleNtfsButton() |
365 | apiSetChatSettings (enableNtfs) |
Always |
| Clear | trash | clearChatButton() |
385 | apiClearChat |
Has messages |
| Delete | trash.fill | -- | -- | apiDeleteChat |
Not active chat |
| Tag | tag | -- | -- | apiSetChatTags |
Always |
8. UserPicker
File: Shared/Views/ChatList/UserPicker.swift
Triggered by tapping the user avatar in the navigation bar. Presented as a sheet with:
| Section | Contents |
|---|---|
| User list | All non-hidden users with unread counts |
| Active user | Highlighted with checkmark |
| Actions | Settings, Your SimpleX address, User profiles |
User Switching
- Tapping a different user calls
apiSetActiveUser(userId:) - Triggers
apiGetChatsfor the new user ChatModel.currentUserupdates, causing full UI refresh- Hidden users are not shown (require password entry via settings)
9. Floating Action Button
The FAB (floating action button) in the bottom-right corner opens the new chat flow:
- Tap: opens
NewChatViewsheet for creating a new contact connection or group - Shows options: Create link, Scan QR code, Paste link, Create group
Source Files
| File | Path | Key struct | Line |
|---|---|---|---|
| Chat list view | ChatListView.swift |
ChatListView |
138 |
| Chat preview row | ChatPreviewView.swift |
ChatPreviewView |
12 |
| Navigation link wrapper | ChatListNavLink.swift |
ChatListNavLink |
43 |
| Tag filter tabs | TagListView.swift |
TagListView |
19 |
| User picker sheet | UserPicker.swift |
UserPicker |
9 |
| Getting started help | ChatHelp.swift |
||
| Contact request view | ContactRequestView.swift |
||
| Contact connection info | ContactConnectionInfo.swift |
||
| Contact connection view | ContactConnectionView.swift |
||
| Server summary | ServersSummaryView.swift |
||
| One-hand UI card | OneHandUICard.swift |