diff --git a/apps/ios/product/glossary.md b/apps/ios/product/glossary.md index 7b2e227ffa..33c28eb1a2 100644 --- a/apps/ios/product/glossary.md +++ b/apps/ios/product/glossary.md @@ -112,7 +112,7 @@ A struct representing a relay instance for a group. Contains the relay's databas A struct representing a user's chat relay configuration. Contains the relay's database ID (`chatRelayId`), SMP server address, name, domains, and flags for preset/tested/enabled/deleted status. *See: `SimpleXChat/ChatTypes.swift` L2513* ### GroupShortLinkInfo -Information about a group's short link including whether it's a direct link, associated relay hostnames, and shared group identifier. Transient data returned by `APIConnectPreparedGroup` — not persisted on GroupInfo. *See: `Shared/Model/AppAPITypes.swift` L1352* +Information about a group's short link including whether it's a direct link, associated relay hostnames, and shared group identifier. Transient data returned by `APIConnectPreparedGroup` — not persisted on GroupInfo. *See: `Shared/Model/AppAPITypes.swift` L1494* ### CIDirection.channelRcv A chat item direction case for messages received via a channel relay, as opposed to `.groupRcv` for standard group messages. *See: `SimpleXChat/ChatTypes.swift` L3529* diff --git a/apps/ios/spec/api.md b/apps/ios/spec/api.md index e7ac2f66ad..c9daf6112e 100644 --- a/apps/ios/spec/api.md +++ b/apps/ios/spec/api.md @@ -33,9 +33,9 @@ The iOS app communicates with the Haskell core exclusively through a command/res 5. Async events arrive separately via `chat_recv_msg_wait`, decoded as `ChatEvent` **Source files**: -- [`Shared/Model/AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift) -- `ChatCommand` ([L15](../Shared/Model/AppAPITypes.swift#L15)), `ChatResponse0` ([L657](../Shared/Model/AppAPITypes.swift#L657)), `ChatResponse1` ([L779](../Shared/Model/AppAPITypes.swift#L779)), `ChatResponse2` ([L919](../Shared/Model/AppAPITypes.swift#L919)), `ChatEvent` ([L1069](../Shared/Model/AppAPITypes.swift#L1069)) enums -- [`SimpleXChat/APITypes.swift`](../SimpleXChat/APITypes.swift) -- `APIResult` ([L27](../SimpleXChat/APITypes.swift#L27)), `ChatAPIResult` ([L65](../SimpleXChat/APITypes.swift#L65)), `ChatError` ([L699](../SimpleXChat/APITypes.swift#L699)) -- [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift) -- FFI bridge functions (`chatSendCmd` [L121](../Shared/Model/SimpleXAPI.swift#L121), `chatRecvMsg` [L237](../Shared/Model/SimpleXAPI.swift#L237)) +- [`Shared/Model/AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift) -- `ChatCommand` ([L15](../Shared/Model/AppAPITypes.swift#L15)), `ChatResponse0` ([L729](../Shared/Model/AppAPITypes.swift#L729)), `ChatResponse1` ([L857](../Shared/Model/AppAPITypes.swift#L857)), `ChatResponse2` ([L1003](../Shared/Model/AppAPITypes.swift#L1003)), `ChatEvent` ([L1179](../Shared/Model/AppAPITypes.swift#L1179)) enums +- [`SimpleXChat/APITypes.swift`](../SimpleXChat/APITypes.swift) -- `APIResult` ([L27](../SimpleXChat/APITypes.swift#L27)), `ChatAPIResult` ([L65](../SimpleXChat/APITypes.swift#L65)), `ChatError` ([L765](../SimpleXChat/APITypes.swift#L765)) +- [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift) -- FFI bridge functions (`chatSendCmd` [L121](../Shared/Model/SimpleXAPI.swift#L121), `chatRecvMsg` [L241](../Shared/Model/SimpleXAPI.swift#L241)) - [`SimpleXChat/API.swift`](../SimpleXChat/API.swift) -- Low-level FFI (`sendSimpleXCmd` [L115](../SimpleXChat/API.swift#L115), `recvSimpleXMsg` [L137](../SimpleXChat/API.swift#L137)) - `SimpleXChat/ChatTypes.swift` -- Data types used in commands/responses (User, Contact, GroupInfo, ChatItem, etc.) - `../../src/Simplex/Chat/Controller.hs` -- Haskell controller (function `chat_send_cmd_retry`, `chat_recv_msg_wait`) @@ -54,12 +54,12 @@ The `ChatCommand` enum ([`AppAPITypes.swift` L15](../Shared/Model/AppAPITypes.sw | `createActiveUser` | `profile: Profile?, pastTimestamp: Bool` | Create new user profile | [L17](../Shared/Model/AppAPITypes.swift#L17) | | `listUsers` | -- | List all user profiles | [L18](../Shared/Model/AppAPITypes.swift#L18) | | `apiSetActiveUser` | `userId: Int64, viewPwd: String?` | Switch active user | [L19](../Shared/Model/AppAPITypes.swift#L19) | -| `apiHideUser` | `userId: Int64, viewPwd: String` | Hide user behind password | [L24](../Shared/Model/AppAPITypes.swift#L24) | -| `apiUnhideUser` | `userId: Int64, viewPwd: String` | Unhide hidden user | [L25](../Shared/Model/AppAPITypes.swift#L25) | -| `apiMuteUser` | `userId: Int64` | Mute notifications for user | [L26](../Shared/Model/AppAPITypes.swift#L26) | -| `apiUnmuteUser` | `userId: Int64` | Unmute notifications for user | [L27](../Shared/Model/AppAPITypes.swift#L27) | -| `apiDeleteUser` | `userId: Int64, delSMPQueues: Bool, viewPwd: String?` | Delete user profile | [L28](../Shared/Model/AppAPITypes.swift#L28) | -| `apiUpdateProfile` | `userId: Int64, profile: Profile` | Update user display name/image | [L141](../Shared/Model/AppAPITypes.swift#L141) | +| `apiHideUser` | `userId: Int64, viewPwd: String` | Hide user behind password | [L25](../Shared/Model/AppAPITypes.swift#L25) | +| `apiUnhideUser` | `userId: Int64, viewPwd: String` | Unhide hidden user | [L26](../Shared/Model/AppAPITypes.swift#L26) | +| `apiMuteUser` | `userId: Int64` | Mute notifications for user | [L27](../Shared/Model/AppAPITypes.swift#L27) | +| `apiUnmuteUser` | `userId: Int64` | Unmute notifications for user | [L28](../Shared/Model/AppAPITypes.swift#L28) | +| `apiDeleteUser` | `userId: Int64, delSMPQueues: Bool, viewPwd: String?` | Delete user profile | [L29](../Shared/Model/AppAPITypes.swift#L29) | +| `apiUpdateProfile` | `userId: Int64, profile: Profile` | Update user display name/image | [L149](../Shared/Model/AppAPITypes.swift#L149) | | `setAllContactReceipts` | `enable: Bool` | Set delivery receipts for all contacts | [L20](../Shared/Model/AppAPITypes.swift#L20) | | `apiSetUserContactReceipts` | `userId: Int64, userMsgReceiptSettings` | Per-user contact receipt settings | [L21](../Shared/Model/AppAPITypes.swift#L21) | | `apiSetUserGroupReceipts` | `userId: Int64, userMsgReceiptSettings` | Per-user group receipt settings | [L22](../Shared/Model/AppAPITypes.swift#L22) | @@ -69,227 +69,227 @@ The `ChatCommand` enum ([`AppAPITypes.swift` L15](../Shared/Model/AppAPITypes.sw | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `startChat` | `mainApp: Bool, enableSndFiles: Bool` | Start chat engine | [L29](../Shared/Model/AppAPITypes.swift#L29) | -| `checkChatRunning` | -- | Check if chat is running | [L30](../Shared/Model/AppAPITypes.swift#L30) | -| `apiStopChat` | -- | Stop chat engine | [L31](../Shared/Model/AppAPITypes.swift#L31) | -| `apiActivateChat` | `restoreChat: Bool` | Resume from background | [L32](../Shared/Model/AppAPITypes.swift#L32) | -| `apiSuspendChat` | `timeoutMicroseconds: Int` | Suspend for background | [L33](../Shared/Model/AppAPITypes.swift#L33) | -| `apiSetAppFilePaths` | `filesFolder, tempFolder, assetsFolder` | Set file storage paths | [L34](../Shared/Model/AppAPITypes.swift#L34) | -| `apiSetEncryptLocalFiles` | `enable: Bool` | Toggle local file encryption | [L35](../Shared/Model/AppAPITypes.swift#L35) | +| `startChat` | `mainApp: Bool, enableSndFiles: Bool` | Start chat engine | [L30](../Shared/Model/AppAPITypes.swift#L30) | +| `checkChatRunning` | -- | Check if chat is running | [L31](../Shared/Model/AppAPITypes.swift#L31) | +| `apiStopChat` | -- | Stop chat engine | [L32](../Shared/Model/AppAPITypes.swift#L32) | +| `apiActivateChat` | `restoreChat: Bool` | Resume from background | [L33](../Shared/Model/AppAPITypes.swift#L33) | +| `apiSuspendChat` | `timeoutMicroseconds: Int` | Suspend for background | [L34](../Shared/Model/AppAPITypes.swift#L34) | +| `apiSetAppFilePaths` | `filesFolder, tempFolder, assetsFolder` | Set file storage paths | [L35](../Shared/Model/AppAPITypes.swift#L35) | +| `apiSetEncryptLocalFiles` | `enable: Bool` | Toggle local file encryption | [L36](../Shared/Model/AppAPITypes.swift#L36) | ### 2.3 Chat & Message Operations | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiGetChats` | `userId: Int64` | Get all chat previews for user | [L44](../Shared/Model/AppAPITypes.swift#L44) | -| `apiGetChat` | `chatId, scope, contentTag, pagination, search` | Get messages for a chat | [L45](../Shared/Model/AppAPITypes.swift#L45) | -| `apiGetChatContentTypes` | `chatId, scope` | Get content type counts for a chat | [L46](../Shared/Model/AppAPITypes.swift#L46) | -| `apiGetChatItemInfo` | `type, id, scope, itemId` | Get detailed info for a message | [L47](../Shared/Model/AppAPITypes.swift#L47) | -| `apiSendMessages` | `type, id, scope, sendAsGroup, live, ttl, composedMessages` | Send one or more messages; `sendAsGroup` sends as channel owner | [L48](../Shared/Model/AppAPITypes.swift#L48) | -| `apiCreateChatItems` | `noteFolderId, composedMessages` | Create items in notes folder | [L54](../Shared/Model/AppAPITypes.swift#L54) | -| `apiUpdateChatItem` | `type, id, scope, itemId, updatedMessage, live` | Edit a sent message | [L56](../Shared/Model/AppAPITypes.swift#L56) | -| `apiDeleteChatItem` | `type, id, scope, itemIds, mode` | Delete messages | [L57](../Shared/Model/AppAPITypes.swift#L57) | -| `apiDeleteMemberChatItem` | `groupId, itemIds` | Moderate group messages | [L58](../Shared/Model/AppAPITypes.swift#L58) | -| `apiChatItemReaction` | `type, id, scope, itemId, add, reaction` | Add/remove emoji reaction | [L61](../Shared/Model/AppAPITypes.swift#L61) | -| `apiGetReactionMembers` | `userId, groupId, itemId, reaction` | Get who reacted | [L62](../Shared/Model/AppAPITypes.swift#L62) | -| `apiPlanForwardChatItems` | `fromChatType, fromChatId, fromScope, itemIds` | Plan message forwarding | [L63](../Shared/Model/AppAPITypes.swift#L63) | -| `apiForwardChatItems` | `toChatType, toChatId, toScope, sendAsGroup, from..., itemIds, ttl` | Forward messages; `sendAsGroup` forwards as channel owner | [L64](../Shared/Model/AppAPITypes.swift#L64) | -| `apiReportMessage` | `groupId, chatItemId, reportReason, reportText` | Report group message | [L55](../Shared/Model/AppAPITypes.swift#L55) | -| `apiChatRead` | `type, id, scope` | Mark entire chat as read | [L166](../Shared/Model/AppAPITypes.swift#L166) | -| `apiChatItemsRead` | `type, id, scope, itemIds` | Mark specific items as read | [L167](../Shared/Model/AppAPITypes.swift#L167) | -| `apiChatUnread` | `type, id, unreadChat` | Toggle unread badge | [L168](../Shared/Model/AppAPITypes.swift#L168) | +| `apiGetChats` | `userId: Int64` | Get all chat previews for user | [L45](../Shared/Model/AppAPITypes.swift#L45) | +| `apiGetChat` | `chatId, scope, contentTag, pagination, search` | Get messages for a chat | [L46](../Shared/Model/AppAPITypes.swift#L46) | +| `apiGetChatContentTypes` | `chatId, scope` | Get content type counts for a chat | [L47](../Shared/Model/AppAPITypes.swift#L47) | +| `apiGetChatItemInfo` | `type, id, scope, itemId` | Get detailed info for a message | [L48](../Shared/Model/AppAPITypes.swift#L48) | +| `apiSendMessages` | `type, id, scope, sendAsGroup, live, ttl, composedMessages` | Send one or more messages; `sendAsGroup` sends as channel owner | [L49](../Shared/Model/AppAPITypes.swift#L49) | +| `apiCreateChatItems` | `noteFolderId, composedMessages` | Create items in notes folder | [L55](../Shared/Model/AppAPITypes.swift#L55) | +| `apiUpdateChatItem` | `type, id, scope, itemId, updatedMessage, live` | Edit a sent message | [L57](../Shared/Model/AppAPITypes.swift#L57) | +| `apiDeleteChatItem` | `type, id, scope, itemIds, mode` | Delete messages | [L58](../Shared/Model/AppAPITypes.swift#L58) | +| `apiDeleteMemberChatItem` | `groupId, itemIds` | Moderate group messages | [L59](../Shared/Model/AppAPITypes.swift#L59) | +| `apiChatItemReaction` | `type, id, scope, itemId, add, reaction` | Add/remove emoji reaction | [L62](../Shared/Model/AppAPITypes.swift#L62) | +| `apiGetReactionMembers` | `userId, groupId, itemId, reaction` | Get who reacted | [L63](../Shared/Model/AppAPITypes.swift#L63) | +| `apiPlanForwardChatItems` | `fromChatType, fromChatId, fromScope, itemIds` | Plan message forwarding | [L64](../Shared/Model/AppAPITypes.swift#L64) | +| `apiForwardChatItems` | `toChatType, toChatId, toScope, sendAsGroup, from..., itemIds, ttl` | Forward messages; `sendAsGroup` forwards as channel owner | [L65](../Shared/Model/AppAPITypes.swift#L65) | +| `apiReportMessage` | `groupId, chatItemId, reportReason, reportText` | Report group message | [L56](../Shared/Model/AppAPITypes.swift#L56) | +| `apiChatRead` | `type, id, scope` | Mark entire chat as read | [L177](../Shared/Model/AppAPITypes.swift#L177) | +| `apiChatItemsRead` | `type, id, scope, itemIds` | Mark specific items as read | [L178](../Shared/Model/AppAPITypes.swift#L178) | +| `apiChatUnread` | `type, id, unreadChat` | Toggle unread badge | [L179](../Shared/Model/AppAPITypes.swift#L179) | ### 2.4 Contact Management | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiAddContact` | `userId, incognito` | Create invitation link | [L126](../Shared/Model/AppAPITypes.swift#L126) | -| `apiConnect` | `userId, incognito, connLink` | Connect via link | [L136](../Shared/Model/AppAPITypes.swift#L136) | -| `apiConnectPlan` | `userId, connLink` | Plan connection (preview) | [L129](../Shared/Model/AppAPITypes.swift#L129) | -| `apiPrepareContact` | `userId, connLink, contactShortLinkData` | Prepare contact from link | [L130](../Shared/Model/AppAPITypes.swift#L130) | -| `apiPrepareGroup` | `userId, connLink, directLink, groupShortLinkData` | Prepare group from link; `directLink` (required, no default) indicates whether link is a direct (non-relay) group link | [L131](../Shared/Model/AppAPITypes.swift#L131) | -| `apiConnectPreparedContact` | `contactId, incognito, msg` | Connect prepared contact | [L134](../Shared/Model/AppAPITypes.swift#L134) | -| `apiConnectPreparedGroup` | `groupId, incognito, msg` | Connect to a prepared group/channel; returns `(GroupInfo, [RelayConnectionResult])?` | [L135](../Shared/Model/AppAPITypes.swift#L135) | -| `apiConnectContactViaAddress` | `userId, incognito, contactId` | Connect via address | [L137](../Shared/Model/AppAPITypes.swift#L137) | -| `apiAcceptContact` | `incognito, contactReqId` | Accept contact request | [L154](../Shared/Model/AppAPITypes.swift#L154) | -| `apiRejectContact` | `contactReqId` | Reject contact request | [L155](../Shared/Model/AppAPITypes.swift#L155) | -| `apiDeleteChat` | `type, id, chatDeleteMode` | Delete conversation | [L138](../Shared/Model/AppAPITypes.swift#L138) | -| `apiClearChat` | `type, id` | Clear conversation history | [L139](../Shared/Model/AppAPITypes.swift#L139) | -| `apiListContacts` | `userId` | List all contacts | [L140](../Shared/Model/AppAPITypes.swift#L140) | -| `apiSetContactPrefs` | `contactId, preferences` | Set contact preferences | [L142](../Shared/Model/AppAPITypes.swift#L142) | -| `apiSetContactAlias` | `contactId, localAlias` | Set local alias | [L143](../Shared/Model/AppAPITypes.swift#L143) | -| `apiSetConnectionAlias` | `connId, localAlias` | Set pending connection alias | [L145](../Shared/Model/AppAPITypes.swift#L145) | -| `apiContactInfo` | `contactId` | Get contact info + connection stats | [L112](../Shared/Model/AppAPITypes.swift#L112) | -| `apiSetConnectionIncognito` | `connId, incognito` | Toggle incognito on pending connection | [L127](../Shared/Model/AppAPITypes.swift#L127) | +| `apiAddContact` | `userId, incognito` | Create invitation link | [L133](../Shared/Model/AppAPITypes.swift#L133) | +| `apiConnect` | `userId, incognito, connLink` | Connect via link | [L144](../Shared/Model/AppAPITypes.swift#L144) | +| `apiConnectPlan` | `userId, connLink` | Plan connection (preview) | [L136](../Shared/Model/AppAPITypes.swift#L136) | +| `apiPrepareContact` | `userId, connLink, contactShortLinkData` | Prepare contact from link | [L138](../Shared/Model/AppAPITypes.swift#L138) | +| `apiPrepareGroup` | `userId, connLink, directLink, groupShortLinkData` | Prepare group from link; `directLink` (required, no default) indicates whether link is a direct (non-relay) group link | [L139](../Shared/Model/AppAPITypes.swift#L139) | +| `apiConnectPreparedContact` | `contactId, incognito, msg` | Connect prepared contact | [L142](../Shared/Model/AppAPITypes.swift#L142) | +| `apiConnectPreparedGroup` | `groupId, incognito, msg` | Connect to a prepared group/channel; returns `(GroupInfo, [RelayConnectionResult])?` | [L143](../Shared/Model/AppAPITypes.swift#L143) | +| `apiConnectContactViaAddress` | `userId, incognito, contactId` | Connect via address | [L145](../Shared/Model/AppAPITypes.swift#L145) | +| `apiAcceptContact` | `incognito, contactReqId` | Accept contact request | [L165](../Shared/Model/AppAPITypes.swift#L165) | +| `apiRejectContact` | `contactReqId` | Reject contact request | [L166](../Shared/Model/AppAPITypes.swift#L166) | +| `apiDeleteChat` | `type, id, chatDeleteMode` | Delete conversation | [L146](../Shared/Model/AppAPITypes.swift#L146) | +| `apiClearChat` | `type, id` | Clear conversation history | [L147](../Shared/Model/AppAPITypes.swift#L147) | +| `apiListContacts` | `userId` | List all contacts | [L148](../Shared/Model/AppAPITypes.swift#L148) | +| `apiSetContactPrefs` | `contactId, preferences` | Set contact preferences | [L150](../Shared/Model/AppAPITypes.swift#L150) | +| `apiSetContactAlias` | `contactId, localAlias` | Set local alias | [L151](../Shared/Model/AppAPITypes.swift#L151) | +| `apiSetConnectionAlias` | `connId, localAlias` | Set pending connection alias | [L153](../Shared/Model/AppAPITypes.swift#L153) | +| `apiContactInfo` | `contactId` | Get contact info + connection stats | [L119](../Shared/Model/AppAPITypes.swift#L119) | +| `apiSetConnectionIncognito` | `connId, incognito` | Toggle incognito on pending connection | [L134](../Shared/Model/AppAPITypes.swift#L134) | ### 2.5 Group Management | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiNewGroup` | `userId, incognito, groupProfile` | Create new group | [L72](../Shared/Model/AppAPITypes.swift#L72) | -| `apiNewPublicGroup` | `userId, incognito, relayIds, groupProfile` | Create new public group (channel) with chat relays | [L73](../Shared/Model/AppAPITypes.swift#L73) | -| `apiGetGroupRelays` | `groupId` | Get group relay list with status (owner only) | [L74](../Shared/Model/AppAPITypes.swift#L74) | -| `apiAddMember` | `groupId, contactId, memberRole` | Invite contact to group | [L75](../Shared/Model/AppAPITypes.swift#L75) | -| `apiJoinGroup` | `groupId` | Accept group invitation | [L76](../Shared/Model/AppAPITypes.swift#L76) | -| `apiAcceptMember` | `groupId, groupMemberId, memberRole` | Accept member (knocking) | [L77](../Shared/Model/AppAPITypes.swift#L77) | -| `apiRemoveMembers` | `groupId, memberIds, withMessages` | Remove members | [L81](../Shared/Model/AppAPITypes.swift#L81) | -| `apiLeaveGroup` | `groupId` | Leave group | [L82](../Shared/Model/AppAPITypes.swift#L82) | -| `apiListMembers` | `groupId` | List group members | [L83](../Shared/Model/AppAPITypes.swift#L83) | -| `apiUpdateGroupProfile` | `groupId, groupProfile` | Update group name/image/description | [L84](../Shared/Model/AppAPITypes.swift#L84) | -| `apiMembersRole` | `groupId, memberIds, memberRole` | Change member roles | [L79](../Shared/Model/AppAPITypes.swift#L79) | -| `apiBlockMembersForAll` | `groupId, memberIds, blocked` | Block members for all | [L80](../Shared/Model/AppAPITypes.swift#L80) | -| `apiCreateGroupLink` | `groupId, memberRole` | Create shareable group link | [L85](../Shared/Model/AppAPITypes.swift#L85) | -| `apiGroupLinkMemberRole` | `groupId, memberRole` | Change group link default role | [L86](../Shared/Model/AppAPITypes.swift#L86) | -| `apiDeleteGroupLink` | `groupId` | Delete group link | [L87](../Shared/Model/AppAPITypes.swift#L87) | -| `apiGetGroupLink` | `groupId` | Get existing group link | [L88](../Shared/Model/AppAPITypes.swift#L88) | -| `apiAddGroupShortLink` | `groupId` | Add short link to group | [L89](../Shared/Model/AppAPITypes.swift#L89) | -| `apiCreateMemberContact` | `groupId, groupMemberId` | Create direct contact from group member | [L90](../Shared/Model/AppAPITypes.swift#L90) | -| `apiSendMemberContactInvitation` | `contactId, msg` | Send contact invitation to member | [L91](../Shared/Model/AppAPITypes.swift#L91) | -| `apiGroupMemberInfo` | `groupId, groupMemberId` | Get member info + connection stats | [L113](../Shared/Model/AppAPITypes.swift#L113) | -| `apiDeleteMemberSupportChat` | `groupId, groupMemberId` | Delete member support chat | [L78](../Shared/Model/AppAPITypes.swift#L78) | -| `apiSetMemberSettings` | `groupId, groupMemberId, memberSettings` | Set per-member settings | [L111](../Shared/Model/AppAPITypes.swift#L111) | -| `apiSetGroupAlias` | `groupId, localAlias` | Set local group alias | [L144](../Shared/Model/AppAPITypes.swift#L144) | +| `apiNewGroup` | `userId, incognito, groupProfile` | Create new group | [L75](../Shared/Model/AppAPITypes.swift#L75) | +| `apiNewPublicGroup` | `userId, incognito, relayIds, groupProfile` | Create new public group (channel) with chat relays | [L76](../Shared/Model/AppAPITypes.swift#L76) | +| `apiGetGroupRelays` | `groupId` | Get group relay list with status (owner only) | [L77](../Shared/Model/AppAPITypes.swift#L77) | +| `apiAddMember` | `groupId, contactId, memberRole` | Invite contact to group | [L79](../Shared/Model/AppAPITypes.swift#L79) | +| `apiJoinGroup` | `groupId` | Accept group invitation | [L80](../Shared/Model/AppAPITypes.swift#L80) | +| `apiAcceptMember` | `groupId, groupMemberId, memberRole` | Accept member (knocking) | [L81](../Shared/Model/AppAPITypes.swift#L81) | +| `apiRemoveMembers` | `groupId, memberIds, withMessages` | Remove members | [L85](../Shared/Model/AppAPITypes.swift#L85) | +| `apiLeaveGroup` | `groupId` | Leave group | [L86](../Shared/Model/AppAPITypes.swift#L86) | +| `apiListMembers` | `groupId` | List group members | [L87](../Shared/Model/AppAPITypes.swift#L87) | +| `apiUpdateGroupProfile` | `groupId, groupProfile` | Update group name/image/description | [L88](../Shared/Model/AppAPITypes.swift#L88) | +| `apiMembersRole` | `groupId, memberIds, memberRole` | Change member roles | [L83](../Shared/Model/AppAPITypes.swift#L83) | +| `apiBlockMembersForAll` | `groupId, memberIds, blocked` | Block members for all | [L84](../Shared/Model/AppAPITypes.swift#L84) | +| `apiCreateGroupLink` | `groupId, memberRole` | Create shareable group link | [L90](../Shared/Model/AppAPITypes.swift#L90) | +| `apiGroupLinkMemberRole` | `groupId, memberRole` | Change group link default role | [L91](../Shared/Model/AppAPITypes.swift#L91) | +| `apiDeleteGroupLink` | `groupId` | Delete group link | [L92](../Shared/Model/AppAPITypes.swift#L92) | +| `apiGetGroupLink` | `groupId` | Get existing group link | [L93](../Shared/Model/AppAPITypes.swift#L93) | +| `apiAddGroupShortLink` | `groupId` | Add short link to group | [L94](../Shared/Model/AppAPITypes.swift#L94) | +| `apiCreateMemberContact` | `groupId, groupMemberId` | Create direct contact from group member | [L95](../Shared/Model/AppAPITypes.swift#L95) | +| `apiSendMemberContactInvitation` | `contactId, msg` | Send contact invitation to member | [L96](../Shared/Model/AppAPITypes.swift#L96) | +| `apiGroupMemberInfo` | `groupId, groupMemberId` | Get member info + connection stats | [L120](../Shared/Model/AppAPITypes.swift#L120) | +| `apiDeleteMemberSupportChat` | `groupId, groupMemberId` | Delete member support chat | [L82](../Shared/Model/AppAPITypes.swift#L82) | +| `apiSetMemberSettings` | `groupId, groupMemberId, memberSettings` | Set per-member settings | [L117](../Shared/Model/AppAPITypes.swift#L117) | +| `apiSetGroupAlias` | `groupId, localAlias` | Set local group alias | [L152](../Shared/Model/AppAPITypes.swift#L152) | ### 2.6 Chat Tags | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiGetChatTags` | `userId` | Get all user tags | [L43](../Shared/Model/AppAPITypes.swift#L43) | -| `apiCreateChatTag` | `tag: ChatTagData` | Create a new tag | [L49](../Shared/Model/AppAPITypes.swift#L49) | -| `apiSetChatTags` | `type, id, tagIds` | Assign tags to a chat | [L50](../Shared/Model/AppAPITypes.swift#L50) | -| `apiDeleteChatTag` | `tagId` | Delete a tag | [L51](../Shared/Model/AppAPITypes.swift#L51) | -| `apiUpdateChatTag` | `tagId, tagData` | Update tag name/emoji | [L52](../Shared/Model/AppAPITypes.swift#L52) | -| `apiReorderChatTags` | `tagIds` | Reorder tags | [L53](../Shared/Model/AppAPITypes.swift#L53) | +| `apiGetChatTags` | `userId` | Get all user tags | [L44](../Shared/Model/AppAPITypes.swift#L44) | +| `apiCreateChatTag` | `tag: ChatTagData` | Create a new tag | [L50](../Shared/Model/AppAPITypes.swift#L50) | +| `apiSetChatTags` | `type, id, tagIds` | Assign tags to a chat | [L51](../Shared/Model/AppAPITypes.swift#L51) | +| `apiDeleteChatTag` | `tagId` | Delete a tag | [L52](../Shared/Model/AppAPITypes.swift#L52) | +| `apiUpdateChatTag` | `tagId, tagData` | Update tag name/emoji | [L53](../Shared/Model/AppAPITypes.swift#L53) | +| `apiReorderChatTags` | `tagIds` | Reorder tags | [L54](../Shared/Model/AppAPITypes.swift#L54) | ### 2.7 File Operations | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `receiveFile` | `fileId, userApprovedRelays, encrypted, inline` | Accept and download file | [L169](../Shared/Model/AppAPITypes.swift#L169) | -| `setFileToReceive` | `fileId, userApprovedRelays, encrypted` | Mark file for auto-receive | [L170](../Shared/Model/AppAPITypes.swift#L170) | -| `cancelFile` | `fileId` | Cancel file transfer | [L171](../Shared/Model/AppAPITypes.swift#L171) | -| `apiUploadStandaloneFile` | `userId, file: CryptoFile` | Upload file to XFTP (no chat) | [L181](../Shared/Model/AppAPITypes.swift#L181) | -| `apiDownloadStandaloneFile` | `userId, url, file: CryptoFile` | Download from XFTP URL | [L182](../Shared/Model/AppAPITypes.swift#L182) | -| `apiStandaloneFileInfo` | `url` | Get file metadata from XFTP URL | [L183](../Shared/Model/AppAPITypes.swift#L183) | +| `receiveFile` | `fileId, userApprovedRelays, encrypted, inline` | Accept and download file | [L180](../Shared/Model/AppAPITypes.swift#L180) | +| `setFileToReceive` | `fileId, userApprovedRelays, encrypted` | Mark file for auto-receive | [L181](../Shared/Model/AppAPITypes.swift#L181) | +| `cancelFile` | `fileId` | Cancel file transfer | [L182](../Shared/Model/AppAPITypes.swift#L182) | +| `apiUploadStandaloneFile` | `userId, file: CryptoFile` | Upload file to XFTP (no chat) | [L192](../Shared/Model/AppAPITypes.swift#L192) | +| `apiDownloadStandaloneFile` | `userId, url, file: CryptoFile` | Download from XFTP URL | [L193](../Shared/Model/AppAPITypes.swift#L193) | +| `apiStandaloneFileInfo` | `url` | Get file metadata from XFTP URL | [L194](../Shared/Model/AppAPITypes.swift#L194) | ### 2.8 WebRTC Call Operations | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiSendCallInvitation` | `contact, callType` | Initiate call | [L157](../Shared/Model/AppAPITypes.swift#L157) | -| `apiRejectCall` | `contact` | Reject incoming call | [L158](../Shared/Model/AppAPITypes.swift#L158) | -| `apiSendCallOffer` | `contact, callOffer: WebRTCCallOffer` | Send SDP offer | [L159](../Shared/Model/AppAPITypes.swift#L159) | -| `apiSendCallAnswer` | `contact, answer: WebRTCSession` | Send SDP answer | [L160](../Shared/Model/AppAPITypes.swift#L160) | -| `apiSendCallExtraInfo` | `contact, extraInfo: WebRTCExtraInfo` | Send ICE candidates | [L161](../Shared/Model/AppAPITypes.swift#L161) | -| `apiEndCall` | `contact` | End active call | [L162](../Shared/Model/AppAPITypes.swift#L162) | -| `apiGetCallInvitations` | -- | Get pending call invitations | [L163](../Shared/Model/AppAPITypes.swift#L163) | -| `apiCallStatus` | `contact, callStatus` | Report call status change | [L164](../Shared/Model/AppAPITypes.swift#L164) | +| `apiSendCallInvitation` | `contact, callType` | Initiate call | [L168](../Shared/Model/AppAPITypes.swift#L168) | +| `apiRejectCall` | `contact` | Reject incoming call | [L169](../Shared/Model/AppAPITypes.swift#L169) | +| `apiSendCallOffer` | `contact, callOffer: WebRTCCallOffer` | Send SDP offer | [L170](../Shared/Model/AppAPITypes.swift#L170) | +| `apiSendCallAnswer` | `contact, answer: WebRTCSession` | Send SDP answer | [L171](../Shared/Model/AppAPITypes.swift#L171) | +| `apiSendCallExtraInfo` | `contact, extraInfo: WebRTCExtraInfo` | Send ICE candidates | [L172](../Shared/Model/AppAPITypes.swift#L172) | +| `apiEndCall` | `contact` | End active call | [L173](../Shared/Model/AppAPITypes.swift#L173) | +| `apiGetCallInvitations` | -- | Get pending call invitations | [L174](../Shared/Model/AppAPITypes.swift#L174) | +| `apiCallStatus` | `contact, callStatus` | Report call status change | [L175](../Shared/Model/AppAPITypes.swift#L175) | ### 2.9 Push Notifications | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiGetNtfToken` | -- | Get current notification token | [L65](../Shared/Model/AppAPITypes.swift#L65) | -| `apiRegisterToken` | `token, notificationMode` | Register device token with server | [L66](../Shared/Model/AppAPITypes.swift#L66) | -| `apiVerifyToken` | `token, nonce, code` | Verify token registration | [L67](../Shared/Model/AppAPITypes.swift#L67) | -| `apiCheckToken` | `token` | Check token status | [L68](../Shared/Model/AppAPITypes.swift#L68) | -| `apiDeleteToken` | `token` | Unregister token | [L69](../Shared/Model/AppAPITypes.swift#L69) | -| `apiGetNtfConns` | `nonce, encNtfInfo` | Get notification connections (NSE) | [L70](../Shared/Model/AppAPITypes.swift#L70) | -| `apiGetConnNtfMessages` | `connMsgReqs` | Get notification messages (NSE) | [L71](../Shared/Model/AppAPITypes.swift#L71) | +| `apiGetNtfToken` | -- | Get current notification token | [L68](../Shared/Model/AppAPITypes.swift#L68) | +| `apiRegisterToken` | `token, notificationMode` | Register device token with server | [L69](../Shared/Model/AppAPITypes.swift#L69) | +| `apiVerifyToken` | `token, nonce, code` | Verify token registration | [L70](../Shared/Model/AppAPITypes.swift#L70) | +| `apiCheckToken` | `token` | Check token status | [L71](../Shared/Model/AppAPITypes.swift#L71) | +| `apiDeleteToken` | `token` | Unregister token | [L72](../Shared/Model/AppAPITypes.swift#L72) | +| `apiGetNtfConns` | `nonce, encNtfInfo` | Get notification connections (NSE) | [L73](../Shared/Model/AppAPITypes.swift#L73) | +| `apiGetConnNtfMessages` | `connMsgReqs` | Get notification messages (NSE) | [L74](../Shared/Model/AppAPITypes.swift#L74) | ### 2.10 Settings & Configuration | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiSaveSettings` | `settings: AppSettings` | Save app settings to core | [L41](../Shared/Model/AppAPITypes.swift#L41) | -| `apiGetSettings` | `settings: AppSettings` | Get settings from core | [L42](../Shared/Model/AppAPITypes.swift#L42) | -| `apiSetChatSettings` | `type, id, chatSettings` | Per-chat notification settings | [L110](../Shared/Model/AppAPITypes.swift#L110) | -| `apiSetChatItemTTL` | `userId, seconds` | Set global message TTL | [L102](../Shared/Model/AppAPITypes.swift#L102) | -| `apiGetChatItemTTL` | `userId` | Get global message TTL | [L103](../Shared/Model/AppAPITypes.swift#L103) | -| `apiSetChatTTL` | `userId, type, id, seconds` | Per-chat message TTL | [L104](../Shared/Model/AppAPITypes.swift#L104) | -| `apiSetNetworkConfig` | `networkConfig: NetCfg` | Set network configuration | [L105](../Shared/Model/AppAPITypes.swift#L105) | -| `apiGetNetworkConfig` | -- | Get network configuration | [L106](../Shared/Model/AppAPITypes.swift#L106) | -| `apiSetNetworkInfo` | `networkInfo: UserNetworkInfo` | Set network type/status | [L107](../Shared/Model/AppAPITypes.swift#L107) | -| `reconnectAllServers` | -- | Force reconnect all servers | [L108](../Shared/Model/AppAPITypes.swift#L108) | -| `reconnectServer` | `userId, smpServer` | Reconnect specific server | [L109](../Shared/Model/AppAPITypes.swift#L109) | +| `apiSaveSettings` | `settings: AppSettings` | Save app settings to core | [L42](../Shared/Model/AppAPITypes.swift#L42) | +| `apiGetSettings` | `settings: AppSettings` | Get settings from core | [L43](../Shared/Model/AppAPITypes.swift#L43) | +| `apiSetChatSettings` | `type, id, chatSettings` | Per-chat notification settings | [L116](../Shared/Model/AppAPITypes.swift#L116) | +| `apiSetChatItemTTL` | `userId, seconds` | Set global message TTL | [L108](../Shared/Model/AppAPITypes.swift#L108) | +| `apiGetChatItemTTL` | `userId` | Get global message TTL | [L109](../Shared/Model/AppAPITypes.swift#L109) | +| `apiSetChatTTL` | `userId, type, id, seconds` | Per-chat message TTL | [L110](../Shared/Model/AppAPITypes.swift#L110) | +| `apiSetNetworkConfig` | `networkConfig: NetCfg` | Set network configuration | [L111](../Shared/Model/AppAPITypes.swift#L111) | +| `apiGetNetworkConfig` | -- | Get network configuration | [L112](../Shared/Model/AppAPITypes.swift#L112) | +| `apiSetNetworkInfo` | `networkInfo: UserNetworkInfo` | Set network type/status | [L113](../Shared/Model/AppAPITypes.swift#L113) | +| `reconnectAllServers` | -- | Force reconnect all servers | [L114](../Shared/Model/AppAPITypes.swift#L114) | +| `reconnectServer` | `userId, smpServer` | Reconnect specific server | [L115](../Shared/Model/AppAPITypes.swift#L115) | ### 2.11 Database & Storage | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiStorageEncryption` | `config: DBEncryptionConfig` | Set/change database encryption | [L39](../Shared/Model/AppAPITypes.swift#L39) | -| `testStorageEncryption` | `key: String` | Test encryption key | [L40](../Shared/Model/AppAPITypes.swift#L40) | -| `apiExportArchive` | `config: ArchiveConfig` | Export database archive | [L36](../Shared/Model/AppAPITypes.swift#L36) | -| `apiImportArchive` | `config: ArchiveConfig` | Import database archive | [L37](../Shared/Model/AppAPITypes.swift#L37) | -| `apiDeleteStorage` | -- | Delete all storage | [L38](../Shared/Model/AppAPITypes.swift#L38) | +| `apiStorageEncryption` | `config: DBEncryptionConfig` | Set/change database encryption | [L40](../Shared/Model/AppAPITypes.swift#L40) | +| `testStorageEncryption` | `key: String` | Test encryption key | [L41](../Shared/Model/AppAPITypes.swift#L41) | +| `apiExportArchive` | `config: ArchiveConfig` | Export database archive | [L37](../Shared/Model/AppAPITypes.swift#L37) | +| `apiImportArchive` | `config: ArchiveConfig` | Import database archive | [L38](../Shared/Model/AppAPITypes.swift#L38) | +| `apiDeleteStorage` | -- | Delete all storage | [L39](../Shared/Model/AppAPITypes.swift#L39) | ### 2.12 Server Operations | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiGetServerOperators` | -- | Get server operators | [L94](../Shared/Model/AppAPITypes.swift#L94) | -| `apiSetServerOperators` | `operators` | Set server operators | [L95](../Shared/Model/AppAPITypes.swift#L95) | -| `apiGetUserServers` | `userId` | Get user's configured servers | [L96](../Shared/Model/AppAPITypes.swift#L96) | -| `apiSetUserServers` | `userId, userServers` | Set user's servers | [L97](../Shared/Model/AppAPITypes.swift#L97) | -| `apiValidateServers` | `userId, userServers` | Validate server configuration; returns errors and warnings | [L98](../Shared/Model/AppAPITypes.swift#L98) | -| `apiGetUsageConditions` | -- | Get usage conditions | [L99](../Shared/Model/AppAPITypes.swift#L99) | -| `apiAcceptConditions` | `conditionsId, operatorIds` | Accept usage conditions | [L101](../Shared/Model/AppAPITypes.swift#L101) | -| `apiTestProtoServer` | `userId, server` | Test server connectivity | [L93](../Shared/Model/AppAPITypes.swift#L93) | +| `apiGetServerOperators` | -- | Get server operators | [L100](../Shared/Model/AppAPITypes.swift#L100) | +| `apiSetServerOperators` | `operators` | Set server operators | [L101](../Shared/Model/AppAPITypes.swift#L101) | +| `apiGetUserServers` | `userId` | Get user's configured servers | [L102](../Shared/Model/AppAPITypes.swift#L102) | +| `apiSetUserServers` | `userId, userServers` | Set user's servers | [L103](../Shared/Model/AppAPITypes.swift#L103) | +| `apiValidateServers` | `userId, userServers` | Validate server configuration; returns errors and warnings | [L104](../Shared/Model/AppAPITypes.swift#L104) | +| `apiGetUsageConditions` | -- | Get usage conditions | [L105](../Shared/Model/AppAPITypes.swift#L105) | +| `apiAcceptConditions` | `conditionsId, operatorIds` | Accept usage conditions | [L107](../Shared/Model/AppAPITypes.swift#L107) | +| `apiTestProtoServer` | `userId, server` | Test server connectivity | [L98](../Shared/Model/AppAPITypes.swift#L98) | ### 2.13 Theme & UI | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiSetUserUIThemes` | `userId, themes: ThemeModeOverrides?` | Set per-user theme | [L146](../Shared/Model/AppAPITypes.swift#L146) | -| `apiSetChatUIThemes` | `chatId, themes: ThemeModeOverrides?` | Set per-chat theme | [L147](../Shared/Model/AppAPITypes.swift#L147) | +| `apiSetUserUIThemes` | `userId, themes: ThemeModeOverrides?` | Set per-user theme | [L154](../Shared/Model/AppAPITypes.swift#L154) | +| `apiSetChatUIThemes` | `chatId, themes: ThemeModeOverrides?` | Set per-chat theme | [L155](../Shared/Model/AppAPITypes.swift#L155) | ### 2.14 Remote Desktop | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `setLocalDeviceName` | `displayName` | Set device name for pairing | [L173](../Shared/Model/AppAPITypes.swift#L173) | -| `connectRemoteCtrl` | `xrcpInvitation` | Connect to desktop via QR code | [L174](../Shared/Model/AppAPITypes.swift#L174) | -| `findKnownRemoteCtrl` | -- | Find previously paired desktops | [L175](../Shared/Model/AppAPITypes.swift#L175) | -| `confirmRemoteCtrl` | `remoteCtrlId` | Confirm known remote controller | [L176](../Shared/Model/AppAPITypes.swift#L176) | -| `verifyRemoteCtrlSession` | `sessionCode` | Verify session code | [L177](../Shared/Model/AppAPITypes.swift#L177) | -| `listRemoteCtrls` | -- | List known remote controllers | [L178](../Shared/Model/AppAPITypes.swift#L178) | -| `stopRemoteCtrl` | -- | Stop remote session | [L179](../Shared/Model/AppAPITypes.swift#L179) | -| `deleteRemoteCtrl` | `remoteCtrlId` | Delete known controller | [L180](../Shared/Model/AppAPITypes.swift#L180) | +| `setLocalDeviceName` | `displayName` | Set device name for pairing | [L184](../Shared/Model/AppAPITypes.swift#L184) | +| `connectRemoteCtrl` | `xrcpInvitation` | Connect to desktop via QR code | [L185](../Shared/Model/AppAPITypes.swift#L185) | +| `findKnownRemoteCtrl` | -- | Find previously paired desktops | [L186](../Shared/Model/AppAPITypes.swift#L186) | +| `confirmRemoteCtrl` | `remoteCtrlId` | Confirm known remote controller | [L187](../Shared/Model/AppAPITypes.swift#L187) | +| `verifyRemoteCtrlSession` | `sessionCode` | Verify session code | [L188](../Shared/Model/AppAPITypes.swift#L188) | +| `listRemoteCtrls` | -- | List known remote controllers | [L189](../Shared/Model/AppAPITypes.swift#L189) | +| `stopRemoteCtrl` | -- | Stop remote session | [L190](../Shared/Model/AppAPITypes.swift#L190) | +| `deleteRemoteCtrl` | `remoteCtrlId` | Delete known controller | [L191](../Shared/Model/AppAPITypes.swift#L191) | ### 2.15 Diagnostics | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `showVersion` | -- | Get core version info | [L185](../Shared/Model/AppAPITypes.swift#L185) | -| `getAgentSubsTotal` | `userId` | Get total SMP subscriptions | [L186](../Shared/Model/AppAPITypes.swift#L186) | -| `getAgentServersSummary` | `userId` | Get server summary stats | [L187](../Shared/Model/AppAPITypes.swift#L187) | -| `resetAgentServersStats` | -- | Reset server statistics | [L188](../Shared/Model/AppAPITypes.swift#L188) | +| `showVersion` | -- | Get core version info | [L201](../Shared/Model/AppAPITypes.swift#L201) | +| `getAgentSubsTotal` | `userId` | Get total SMP subscriptions | [L202](../Shared/Model/AppAPITypes.swift#L202) | +| `getAgentServersSummary` | `userId` | Get server summary stats | [L203](../Shared/Model/AppAPITypes.swift#L203) | +| `resetAgentServersStats` | -- | Reset server statistics | [L204](../Shared/Model/AppAPITypes.swift#L204) | ### 2.16 Address Management | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiCreateMyAddress` | `userId` | Create SimpleX address | [L148](../Shared/Model/AppAPITypes.swift#L148) | -| `apiDeleteMyAddress` | `userId` | Delete SimpleX address | [L149](../Shared/Model/AppAPITypes.swift#L149) | -| `apiShowMyAddress` | `userId` | Show current address | [L150](../Shared/Model/AppAPITypes.swift#L150) | -| `apiAddMyAddressShortLink` | `userId` | Add short link to address | [L151](../Shared/Model/AppAPITypes.swift#L151) | -| `apiSetProfileAddress` | `userId, on: Bool` | Toggle address in profile | [L152](../Shared/Model/AppAPITypes.swift#L152) | -| `apiSetAddressSettings` | `userId, addressSettings` | Configure address settings | [L153](../Shared/Model/AppAPITypes.swift#L153) | +| `apiCreateMyAddress` | `userId` | Create SimpleX address | [L156](../Shared/Model/AppAPITypes.swift#L156) | +| `apiDeleteMyAddress` | `userId` | Delete SimpleX address | [L157](../Shared/Model/AppAPITypes.swift#L157) | +| `apiShowMyAddress` | `userId` | Show current address | [L158](../Shared/Model/AppAPITypes.swift#L158) | +| `apiAddMyAddressShortLink` | `userId` | Add short link to address | [L159](../Shared/Model/AppAPITypes.swift#L159) | +| `apiSetProfileAddress` | `userId, on: Bool` | Toggle address in profile | [L160](../Shared/Model/AppAPITypes.swift#L160) | +| `apiSetAddressSettings` | `userId, addressSettings` | Configure address settings | [L161](../Shared/Model/AppAPITypes.swift#L161) | ### 2.17 Connection Security | Command | Parameters | Description | Source | |---------|-----------|-------------|--------| -| `apiGetContactCode` | `contactId` | Get verification code | [L122](../Shared/Model/AppAPITypes.swift#L122) | -| `apiGetGroupMemberCode` | `groupId, groupMemberId` | Get member verification code | [L123](../Shared/Model/AppAPITypes.swift#L123) | -| `apiVerifyContact` | `contactId, connectionCode` | Verify contact identity | [L124](../Shared/Model/AppAPITypes.swift#L124) | -| `apiVerifyGroupMember` | `groupId, groupMemberId, connectionCode` | Verify group member identity | [L125](../Shared/Model/AppAPITypes.swift#L125) | -| `apiSwitchContact` | `contactId` | Switch contact connection (key rotation) | [L116](../Shared/Model/AppAPITypes.swift#L116) | -| `apiSwitchGroupMember` | `groupId, groupMemberId` | Switch group member connection | [L117](../Shared/Model/AppAPITypes.swift#L117) | -| `apiAbortSwitchContact` | `contactId` | Abort contact switch | [L118](../Shared/Model/AppAPITypes.swift#L118) | -| `apiAbortSwitchGroupMember` | `groupId, groupMemberId` | Abort member switch | [L119](../Shared/Model/AppAPITypes.swift#L119) | -| `apiSyncContactRatchet` | `contactId, force` | Sync double-ratchet state | [L120](../Shared/Model/AppAPITypes.swift#L120) | -| `apiSyncGroupMemberRatchet` | `groupId, groupMemberId, force` | Sync member ratchet | [L121](../Shared/Model/AppAPITypes.swift#L121) | +| `apiGetContactCode` | `contactId` | Get verification code | [L129](../Shared/Model/AppAPITypes.swift#L129) | +| `apiGetGroupMemberCode` | `groupId, groupMemberId` | Get member verification code | [L130](../Shared/Model/AppAPITypes.swift#L130) | +| `apiVerifyContact` | `contactId, connectionCode` | Verify contact identity | [L131](../Shared/Model/AppAPITypes.swift#L131) | +| `apiVerifyGroupMember` | `groupId, groupMemberId, connectionCode` | Verify group member identity | [L132](../Shared/Model/AppAPITypes.swift#L132) | +| `apiSwitchContact` | `contactId` | Switch contact connection (key rotation) | [L123](../Shared/Model/AppAPITypes.swift#L123) | +| `apiSwitchGroupMember` | `groupId, groupMemberId` | Switch group member connection | [L124](../Shared/Model/AppAPITypes.swift#L124) | +| `apiAbortSwitchContact` | `contactId` | Abort contact switch | [L125](../Shared/Model/AppAPITypes.swift#L125) | +| `apiAbortSwitchGroupMember` | `groupId, groupMemberId` | Abort member switch | [L126](../Shared/Model/AppAPITypes.swift#L126) | +| `apiSyncContactRatchet` | `contactId, force` | Sync double-ratchet state | [L127](../Shared/Model/AppAPITypes.swift#L127) | +| `apiSyncGroupMemberRatchet` | `groupId, groupMemberId, force` | Sync member ratchet | [L128](../Shared/Model/AppAPITypes.swift#L128) | ### 2.18 Service Requests @@ -305,171 +305,171 @@ Responses are split across three enums due to Swift enum size limitations: ### ChatResponse0 -Synchronous query responses ([`AppAPITypes.swift` L657](../Shared/Model/AppAPITypes.swift#L657)): +Synchronous query responses ([`AppAPITypes.swift` L729](../Shared/Model/AppAPITypes.swift#L729)): | Response | Key Fields | Description | Source | |----------|-----------|-------------|--------| -| `activeUser` | `user: User` | Current active user | [L658](../Shared/Model/AppAPITypes.swift#L658) | -| `usersList` | `users: [UserInfo]` | All user profiles | [L659](../Shared/Model/AppAPITypes.swift#L659) | -| `chatStarted` | -- | Chat engine started | [L660](../Shared/Model/AppAPITypes.swift#L660) | -| `chatRunning` | -- | Chat is already running | [L661](../Shared/Model/AppAPITypes.swift#L661) | -| `chatStopped` | -- | Chat engine stopped | [L662](../Shared/Model/AppAPITypes.swift#L662) | -| `apiChats` | `user, chats: [ChatData]` | All chat previews | [L663](../Shared/Model/AppAPITypes.swift#L663) | -| `apiChat` | `user, chat: ChatData, navInfo` | Single chat with messages | [L664](../Shared/Model/AppAPITypes.swift#L664) | -| `chatTags` | `user, userTags: [ChatTag]` | User's chat tags | [L666](../Shared/Model/AppAPITypes.swift#L666) | -| `chatItemInfo` | `user, chatItem, chatItemInfo` | Message detail info | [L667](../Shared/Model/AppAPITypes.swift#L667) | -| `serverTestResult` | `user, testServer, testFailure` | Server test result | [L668](../Shared/Model/AppAPITypes.swift#L668) | -| `userServersValidation` | `user, serverErrors: [UserServersError], serverWarnings: [UserServersWarning]` | Server validation result with errors and warnings | [L671](../Shared/Model/AppAPITypes.swift#L671) | -| `networkConfig` | `networkConfig: NetCfg` | Current network config | [L674](../Shared/Model/AppAPITypes.swift#L674) | -| `contactInfo` | `user, contact, connectionStats, customUserProfile` | Contact details | [L675](../Shared/Model/AppAPITypes.swift#L675) | -| `groupMemberInfo` | `user, groupInfo, member, connectionStats` | Member details | [L676](../Shared/Model/AppAPITypes.swift#L676) | -| `connectionVerified` | `verified, expectedCode` | Verification result | [L686](../Shared/Model/AppAPITypes.swift#L686) | -| `tagsUpdated` | `user, userTags, chatTags` | Tags changed | [L687](../Shared/Model/AppAPITypes.swift#L687) | +| `activeUser` | `user: User` | Current active user | [L730](../Shared/Model/AppAPITypes.swift#L730) | +| `usersList` | `users: [UserInfo]` | All user profiles | [L731](../Shared/Model/AppAPITypes.swift#L731) | +| `chatStarted` | -- | Chat engine started | [L732](../Shared/Model/AppAPITypes.swift#L732) | +| `chatRunning` | -- | Chat is already running | [L733](../Shared/Model/AppAPITypes.swift#L733) | +| `chatStopped` | -- | Chat engine stopped | [L734](../Shared/Model/AppAPITypes.swift#L734) | +| `apiChats` | `user, chats: [ChatData]` | All chat previews | [L735](../Shared/Model/AppAPITypes.swift#L735) | +| `apiChat` | `user, chat: ChatData, navInfo` | Single chat with messages | [L736](../Shared/Model/AppAPITypes.swift#L736) | +| `chatTags` | `user, userTags: [ChatTag]` | User's chat tags | [L738](../Shared/Model/AppAPITypes.swift#L738) | +| `chatItemInfo` | `user, chatItem, chatItemInfo` | Message detail info | [L739](../Shared/Model/AppAPITypes.swift#L739) | +| `serverTestResult` | `user, testServer, testFailure` | Server test result | [L740](../Shared/Model/AppAPITypes.swift#L740) | +| `userServersValidation` | `user, serverErrors: [UserServersError], serverWarnings: [UserServersWarning]` | Server validation result with errors and warnings | [L744](../Shared/Model/AppAPITypes.swift#L744) | +| `networkConfig` | `networkConfig: NetCfg` | Current network config | [L747](../Shared/Model/AppAPITypes.swift#L747) | +| `contactInfo` | `user, contact, connectionStats, customUserProfile` | Contact details | [L748](../Shared/Model/AppAPITypes.swift#L748) | +| `groupMemberInfo` | `user, groupInfo, member, connectionStats` | Member details | [L750](../Shared/Model/AppAPITypes.swift#L750) | +| `connectionVerified` | `verified, expectedCode` | Verification result | [L760](../Shared/Model/AppAPITypes.swift#L760) | +| `tagsUpdated` | `user, userTags, chatTags` | Tags changed | [L761](../Shared/Model/AppAPITypes.swift#L761) | ### ChatResponse1 -Contact, message, and profile responses ([`AppAPITypes.swift` L779](../Shared/Model/AppAPITypes.swift#L779)): +Contact, message, and profile responses ([`AppAPITypes.swift` L857](../Shared/Model/AppAPITypes.swift#L857)): | Response | Key Fields | Description | Source | |----------|-----------|-------------|--------| -| `invitation` | `user, connLinkInvitation, connection` | Created invitation link | [L780](../Shared/Model/AppAPITypes.swift#L780) | -| `connectionPlan` | `user, connLink, connectionPlan` | Connection plan preview | [L783](../Shared/Model/AppAPITypes.swift#L783) | +| `invitation` | `user, connLinkInvitation, connection` | Created invitation link | [L858](../Shared/Model/AppAPITypes.swift#L858) | +| `connectionPlan` | `user, connLink, connectionPlan` | Connection plan preview | [L861](../Shared/Model/AppAPITypes.swift#L861) | | `serviceResponse` | `user, responseData: JSONValue` | A service's response to `apiSendServiceRequest`, left undecoded for the caller | [L862](../Shared/Model/AppAPITypes.swift#L862) | -| `newPreparedChat` | `user, chat: ChatData` | Prepared contact/group | [L784](../Shared/Model/AppAPITypes.swift#L784) | -| `startedConnectionToGroup` | `user, groupInfo, relayResults: [RelayConnectionResult]` | Group/channel join initiated; relay results indicate per-relay connection success/failure | [L790](../Shared/Model/AppAPITypes.swift#L790) | -| `contactDeleted` | `user, contact` | Contact deleted | [L793](../Shared/Model/AppAPITypes.swift#L793) | -| `newChatItems` | `user, chatItems: [AChatItem]` | New messages sent/received | [L811](../Shared/Model/AppAPITypes.swift#L811) | -| `chatItemUpdated` | `user, chatItem: AChatItem` | Message edited | [L814](../Shared/Model/AppAPITypes.swift#L814) | -| `chatItemReaction` | `user, added, reaction` | Reaction change | [L816](../Shared/Model/AppAPITypes.swift#L816) | -| `chatItemsDeleted` | `user, chatItemDeletions, byUser` | Messages deleted | [L818](../Shared/Model/AppAPITypes.swift#L818) | -| `contactsList` | `user, contacts: [Contact]` | All contacts list | [L819](../Shared/Model/AppAPITypes.swift#L819) | -| `userProfileUpdated` | `user, fromProfile, toProfile` | Profile changed | [L799](../Shared/Model/AppAPITypes.swift#L799) | -| `userContactLinkCreated` | `user, connLinkContact` | Address created | [L807](../Shared/Model/AppAPITypes.swift#L807) | -| `forwardPlan` | `user, chatItemIds, forwardConfirmation` | Forward plan result | [L813](../Shared/Model/AppAPITypes.swift#L813) | -| `groupChatItemsDeleted` | `user, groupInfo, chatItemIDs, byUser, member_` | Group items deleted | [L812](../Shared/Model/AppAPITypes.swift#L812) | +| `newPreparedChat` | `user, chat: ChatData` | Prepared contact/group | [L863](../Shared/Model/AppAPITypes.swift#L863) | +| `startedConnectionToGroup` | `user, groupInfo, relayResults: [RelayConnectionResult]` | Group/channel join initiated; relay results indicate per-relay connection success/failure | [L869](../Shared/Model/AppAPITypes.swift#L869) | +| `contactDeleted` | `user, contact` | Contact deleted | [L872](../Shared/Model/AppAPITypes.swift#L872) | +| `newChatItems` | `user, chatItems: [AChatItem]` | New messages sent/received | [L890](../Shared/Model/AppAPITypes.swift#L890) | +| `chatItemUpdated` | `user, chatItem: AChatItem` | Message edited | [L894](../Shared/Model/AppAPITypes.swift#L894) | +| `chatItemReaction` | `user, added, reaction` | Reaction change | [L896](../Shared/Model/AppAPITypes.swift#L896) | +| `chatItemsDeleted` | `user, chatItemDeletions, byUser` | Messages deleted | [L898](../Shared/Model/AppAPITypes.swift#L898) | +| `contactsList` | `user, contacts: [Contact]` | All contacts list | [L899](../Shared/Model/AppAPITypes.swift#L899) | +| `userProfileUpdated` | `user, fromProfile, toProfile` | Profile changed | [L878](../Shared/Model/AppAPITypes.swift#L878) | +| `userContactLinkCreated` | `user, connLinkContact` | Address created | [L886](../Shared/Model/AppAPITypes.swift#L886) | +| `forwardPlan` | `user, chatItemIds, forwardConfirmation` | Forward plan result | [L893](../Shared/Model/AppAPITypes.swift#L893) | +| `groupChatItemsDeleted` | `user, groupInfo, chatItemIDs, byUser, member_` | Group items deleted | [L892](../Shared/Model/AppAPITypes.swift#L892) | ### ChatResponse2 -Group, file, call, notification, and misc responses ([`AppAPITypes.swift` L919](../Shared/Model/AppAPITypes.swift#L919)): +Group, file, call, notification, and misc responses ([`AppAPITypes.swift` L1003](../Shared/Model/AppAPITypes.swift#L1003)): | Response | Key Fields | Description | Source | |----------|-----------|-------------|--------| -| `groupCreated` | `user, groupInfo` | New group created | [L921](../Shared/Model/AppAPITypes.swift#L921) | -| `publicGroupCreated` | `user, groupInfo, groupLink, groupRelays: [GroupRelay]` | New public group (channel) created with relay info | [L922](../Shared/Model/AppAPITypes.swift#L922) | -| `groupRelays` | `user, groupInfo, groupRelays: [GroupRelay]` | Group relay list (owner API response) | [L923](../Shared/Model/AppAPITypes.swift#L923) | -| `sentGroupInvitation` | `user, groupInfo, contact, member` | Group invitation sent | [L924](../Shared/Model/AppAPITypes.swift#L924) | -| `groupMembers` | `user, group: Group` | Group member list | [L928](../Shared/Model/AppAPITypes.swift#L928) | -| `membersRoleUser` | `user, groupInfo, members, toRole` | Role changed | [L932](../Shared/Model/AppAPITypes.swift#L932) | -| `groupUpdated` | `user, toGroup: GroupInfo` | Group profile updated | [L934](../Shared/Model/AppAPITypes.swift#L934) | -| `groupLinkCreated` | `user, groupInfo, groupLink` | Group link created | [L935](../Shared/Model/AppAPITypes.swift#L935) | -| `rcvFileAccepted` | `user, chatItem` | File download started | [L942](../Shared/Model/AppAPITypes.swift#L942) | -| `callInvitations` | `callInvitations: [RcvCallInvitation]` | Pending calls | [L951](../Shared/Model/AppAPITypes.swift#L951) | -| `ntfToken` | `token, status, ntfMode, ntfServer` | Notification token info | [L954](../Shared/Model/AppAPITypes.swift#L954) | -| `versionInfo` | `versionInfo, chatMigrations, agentMigrations` | Core version | [L962](../Shared/Model/AppAPITypes.swift#L962) | -| `cmdOk` | `user_` | Generic success | [L963](../Shared/Model/AppAPITypes.swift#L963) | -| `archiveExported` | `archiveErrors: [ArchiveError]` | Export result | [L967](../Shared/Model/AppAPITypes.swift#L967) | -| `archiveImported` | `archiveErrors: [ArchiveError]` | Import result | [L968](../Shared/Model/AppAPITypes.swift#L968) | -| `appSettings` | `appSettings: AppSettings` | Retrieved settings | [L969](../Shared/Model/AppAPITypes.swift#L969) | +| `groupCreated` | `user, groupInfo` | New group created | [L1005](../Shared/Model/AppAPITypes.swift#L1005) | +| `publicGroupCreated` | `user, groupInfo, groupLink, groupRelays: [GroupRelay]` | New public group (channel) created with relay info | [L1006](../Shared/Model/AppAPITypes.swift#L1006) | +| `groupRelays` | `user, groupInfo, groupRelays: [GroupRelay]` | Group relay list (owner API response) | [L1008](../Shared/Model/AppAPITypes.swift#L1008) | +| `sentGroupInvitation` | `user, groupInfo, contact, member` | Group invitation sent | [L1011](../Shared/Model/AppAPITypes.swift#L1011) | +| `groupMembers` | `user, group: Group` | Group member list | [L1015](../Shared/Model/AppAPITypes.swift#L1015) | +| `membersRoleUser` | `user, groupInfo, members, toRole` | Role changed | [L1019](../Shared/Model/AppAPITypes.swift#L1019) | +| `groupUpdated` | `user, toGroup: GroupInfo` | Group profile updated | [L1021](../Shared/Model/AppAPITypes.swift#L1021) | +| `groupLinkCreated` | `user, groupInfo, groupLink` | Group link created | [L1024](../Shared/Model/AppAPITypes.swift#L1024) | +| `rcvFileAccepted` | `user, chatItem` | File download started | [L1031](../Shared/Model/AppAPITypes.swift#L1031) | +| `callInvitations` | `callInvitations: [RcvCallInvitation]` | Pending calls | [L1040](../Shared/Model/AppAPITypes.swift#L1040) | +| `ntfToken` | `token, status, ntfMode, ntfServer` | Notification token info | [L1043](../Shared/Model/AppAPITypes.swift#L1043) | +| `versionInfo` | `versionInfo, chatMigrations, agentMigrations` | Core version | [L1051](../Shared/Model/AppAPITypes.swift#L1051) | +| `cmdOk` | `user_` | Generic success | [L1052](../Shared/Model/AppAPITypes.swift#L1052) | +| `archiveExported` | `archiveErrors: [ArchiveError]` | Export result | [L1056](../Shared/Model/AppAPITypes.swift#L1056) | +| `archiveImported` | `archiveErrors: [ArchiveError]` | Import result | [L1057](../Shared/Model/AppAPITypes.swift#L1057) | +| `appSettings` | `appSettings: AppSettings` | Retrieved settings | [L1058](../Shared/Model/AppAPITypes.swift#L1058) | --- ## 4. Event Types -The `ChatEvent` enum ([`AppAPITypes.swift` L1069](../Shared/Model/AppAPITypes.swift#L1069)) represents async events from the Haskell core. These arrive via `chat_recv_msg_wait` polling, not as responses to commands. +The `ChatEvent` enum ([`AppAPITypes.swift` L1179](../Shared/Model/AppAPITypes.swift#L1179)) represents async events from the Haskell core. These arrive via `chat_recv_msg_wait` polling, not as responses to commands. -Event processing entry point: [`processReceivedMsg`](../Shared/Model/SimpleXAPI.swift#L2282) in `SimpleXAPI.swift`. +Event processing entry point: [`processReceivedMsg`](../Shared/Model/SimpleXAPI.swift#L2555) in `SimpleXAPI.swift`. ### Connection Events | Event | Key Fields | Description | Source | |-------|-----------|-------------|--------| -| `contactConnected` | `user, contact, userCustomProfile` | Contact connection established | [L1076](../Shared/Model/AppAPITypes.swift#L1076) | -| `contactConnecting` | `user, contact` | Contact connecting in progress | [L1077](../Shared/Model/AppAPITypes.swift#L1077) | -| `contactSndReady` | `user, contact` | Ready to send to contact | [L1078](../Shared/Model/AppAPITypes.swift#L1078) | -| `contactDeletedByContact` | `user, contact` | Contact deleted by other party | [L1075](../Shared/Model/AppAPITypes.swift#L1075) | -| `contactUpdated` | `user, toContact` | Contact profile updated | [L1080](../Shared/Model/AppAPITypes.swift#L1080) | -| `receivedContactRequest` | `user, contactRequest, chat_` | Incoming contact request | [L1079](../Shared/Model/AppAPITypes.swift#L1079) | -| `subscriptionStatus` | `subscriptionStatus, connections` | Connection subscription change | [L1082](../Shared/Model/AppAPITypes.swift#L1082) | +| `contactConnected` | `user, contact, userCustomProfile` | Contact connection established | [L1186](../Shared/Model/AppAPITypes.swift#L1186) | +| `contactConnecting` | `user, contact` | Contact connecting in progress | [L1187](../Shared/Model/AppAPITypes.swift#L1187) | +| `contactSndReady` | `user, contact` | Ready to send to contact | [L1188](../Shared/Model/AppAPITypes.swift#L1188) | +| `contactDeletedByContact` | `user, contact` | Contact deleted by other party | [L1185](../Shared/Model/AppAPITypes.swift#L1185) | +| `contactUpdated` | `user, toContact` | Contact profile updated | [L1190](../Shared/Model/AppAPITypes.swift#L1190) | +| `receivedContactRequest` | `user, contactRequest, chat_` | Incoming contact request | [L1189](../Shared/Model/AppAPITypes.swift#L1189) | +| `subscriptionStatus` | `subscriptionStatus, connections` | Connection subscription change | [L1192](../Shared/Model/AppAPITypes.swift#L1192) | ### Message Events | Event | Key Fields | Description | Source | |-------|-----------|-------------|--------| -| `newChatItems` | `user, chatItems: [AChatItem]` | New messages received | [L1084](../Shared/Model/AppAPITypes.swift#L1084) | -| `chatItemUpdated` | `user, chatItem: AChatItem` | Message edited remotely | [L1086](../Shared/Model/AppAPITypes.swift#L1086) | -| `chatItemReaction` | `user, added, reaction: ACIReaction` | Reaction added/removed | [L1087](../Shared/Model/AppAPITypes.swift#L1087) | -| `chatItemsDeleted` | `user, chatItemDeletions, byUser` | Messages deleted | [L1088](../Shared/Model/AppAPITypes.swift#L1088) | -| `chatItemsStatusesUpdated` | `user, chatItems: [AChatItem]` | Delivery status changed | [L1085](../Shared/Model/AppAPITypes.swift#L1085) | -| `groupChatItemsDeleted` | `user, groupInfo, chatItemIDs, byUser, member_` | Group items deleted | [L1090](../Shared/Model/AppAPITypes.swift#L1090) | -| `chatInfoUpdated` | `user, chatInfo` | Chat metadata changed | [L1083](../Shared/Model/AppAPITypes.swift#L1083) | +| `newChatItems` | `user, chatItems: [AChatItem]` | New messages received | [L1194](../Shared/Model/AppAPITypes.swift#L1194) | +| `chatItemUpdated` | `user, chatItem: AChatItem` | Message edited remotely | [L1196](../Shared/Model/AppAPITypes.swift#L1196) | +| `chatItemReaction` | `user, added, reaction: ACIReaction` | Reaction added/removed | [L1197](../Shared/Model/AppAPITypes.swift#L1197) | +| `chatItemsDeleted` | `user, chatItemDeletions, byUser` | Messages deleted | [L1198](../Shared/Model/AppAPITypes.swift#L1198) | +| `chatItemsStatusesUpdated` | `user, chatItems: [AChatItem]` | Delivery status changed | [L1195](../Shared/Model/AppAPITypes.swift#L1195) | +| `groupChatItemsDeleted` | `user, groupInfo, chatItemIDs, byUser, member_` | Group items deleted | [L1200](../Shared/Model/AppAPITypes.swift#L1200) | +| `chatInfoUpdated` | `user, chatInfo` | Chat metadata changed | [L1193](../Shared/Model/AppAPITypes.swift#L1193) | ### Group Events | Event | Key Fields | Description | Source | |-------|-----------|-------------|--------| -| `receivedGroupInvitation` | `user, groupInfo, contact, memberRole` | Group invitation received | [L1091](../Shared/Model/AppAPITypes.swift#L1091) | -| `userAcceptedGroupSent` | `user, groupInfo, hostContact` | Joined group | [L1092](../Shared/Model/AppAPITypes.swift#L1092) | -| `groupLinkConnecting` | `user, groupInfo, hostMember` | Connecting via group link | [L1093](../Shared/Model/AppAPITypes.swift#L1093) | -| `joinedGroupMemberConnecting` | `user, groupInfo, hostMember, member` | Member joining | [L1095](../Shared/Model/AppAPITypes.swift#L1095) | -| `memberRole` | `user, groupInfo, byMember, member, fromRole, toRole` | Role changed | [L1097](../Shared/Model/AppAPITypes.swift#L1097) | -| `memberBlockedForAll` | `user, groupInfo, byMember, member, blocked` | Member blocked | [L1098](../Shared/Model/AppAPITypes.swift#L1098) | -| `deletedMemberUser` | `user, groupInfo, member, withMessages` | Current user removed | [L1099](../Shared/Model/AppAPITypes.swift#L1099) | -| `deletedMember` | `user, groupInfo, byMember, deletedMember` | Member removed | [L1100](../Shared/Model/AppAPITypes.swift#L1100) | -| `leftMember` | `user, groupInfo, member` | Member left | [L1101](../Shared/Model/AppAPITypes.swift#L1101) | -| `groupDeleted` | `user, groupInfo, member` | Group deleted | [L1102](../Shared/Model/AppAPITypes.swift#L1102) | -| `userJoinedGroup` | `user, groupInfo, hostMember` | Successfully joined; `hostMember` is upserted into group members | [L1103](../Shared/Model/AppAPITypes.swift#L1103) | -| `joinedGroupMember` | `user, groupInfo, member` | New member joined | [L1104](../Shared/Model/AppAPITypes.swift#L1104) | -| `connectedToGroupMember` | `user, groupInfo, member, memberContact` | E2E session established with member | [L1105](../Shared/Model/AppAPITypes.swift#L1105) | -| `groupUpdated` | `user, toGroup: GroupInfo` | Group profile changed | [L1106](../Shared/Model/AppAPITypes.swift#L1106) | +| `receivedGroupInvitation` | `user, groupInfo, contact, memberRole` | Group invitation received | [L1201](../Shared/Model/AppAPITypes.swift#L1201) | +| `userAcceptedGroupSent` | `user, groupInfo, hostContact` | Joined group | [L1203](../Shared/Model/AppAPITypes.swift#L1203) | +| `groupLinkConnecting` | `user, groupInfo, hostMember` | Connecting via group link | [L1204](../Shared/Model/AppAPITypes.swift#L1204) | +| `joinedGroupMemberConnecting` | `user, groupInfo, hostMember, member` | Member joining | [L1206](../Shared/Model/AppAPITypes.swift#L1206) | +| `memberRole` | `user, groupInfo, byMember, member, fromRole, toRole` | Role changed | [L1208](../Shared/Model/AppAPITypes.swift#L1208) | +| `memberBlockedForAll` | `user, groupInfo, byMember, member, blocked` | Member blocked | [L1209](../Shared/Model/AppAPITypes.swift#L1209) | +| `deletedMemberUser` | `user, groupInfo, member, withMessages` | Current user removed | [L1210](../Shared/Model/AppAPITypes.swift#L1210) | +| `deletedMember` | `user, groupInfo, byMember, deletedMember` | Member removed | [L1211](../Shared/Model/AppAPITypes.swift#L1211) | +| `leftMember` | `user, groupInfo, member` | Member left | [L1212](../Shared/Model/AppAPITypes.swift#L1212) | +| `groupDeleted` | `user, groupInfo, member` | Group deleted | [L1213](../Shared/Model/AppAPITypes.swift#L1213) | +| `userJoinedGroup` | `user, groupInfo, hostMember` | Successfully joined; `hostMember` is upserted into group members | [L1214](../Shared/Model/AppAPITypes.swift#L1214) | +| `joinedGroupMember` | `user, groupInfo, member` | New member joined | [L1215](../Shared/Model/AppAPITypes.swift#L1215) | +| `connectedToGroupMember` | `user, groupInfo, member, memberContact` | E2E session established with member | [L1216](../Shared/Model/AppAPITypes.swift#L1216) | +| `groupUpdated` | `user, toGroup: GroupInfo` | Group profile changed | [L1217](../Shared/Model/AppAPITypes.swift#L1217) | | `groupLinkRelaysUpdated` | `user, groupInfo, groupLink, groupRelays: [GroupRelay]` | Channel relay configuration changed | [L1107](../Shared/Model/AppAPITypes.swift#L1107) | -| `groupMemberUpdated` | `user, groupInfo, fromMember, toMember` | Member info updated | [L1081](../Shared/Model/AppAPITypes.swift#L1081) | +| `groupMemberUpdated` | `user, groupInfo, fromMember, toMember` | Member info updated | [L1191](../Shared/Model/AppAPITypes.swift#L1191) | | `groupRelayUpdated` | `user, groupInfo, member, groupRelay` | Owner-side: a relay's `relayStatus` and/or the member's status changed. Fires on `XGrpRelayReject` with `relayStatus = .rsRejected` and `member.memberStatus = .memRejected` — final until cleared by the relay operator's `/group allow ` (no event emitted to the owner for that clear). | Controller.hs (`CEvtGroupRelayUpdated`) | ### File Transfer Events | Event | Key Fields | Description | Source | |-------|-----------|-------------|--------| -| `rcvFileStart` | `user, chatItem` | Download started | [L1112](../Shared/Model/AppAPITypes.swift#L1112) | -| `rcvFileProgressXFTP` | `user, chatItem_, receivedSize, totalSize` | Download progress | [L1113](../Shared/Model/AppAPITypes.swift#L1113) | -| `rcvFileComplete` | `user, chatItem` | Download complete | [L1114](../Shared/Model/AppAPITypes.swift#L1114) | -| `rcvFileSndCancelled` | `user, chatItem, rcvFileTransfer` | Sender cancelled | [L1116](../Shared/Model/AppAPITypes.swift#L1116) | -| `rcvFileError` | `user, chatItem_, agentError, rcvFileTransfer` | Download error | [L1117](../Shared/Model/AppAPITypes.swift#L1117) | -| `sndFileStart` | `user, chatItem, sndFileTransfer` | Upload started | [L1120](../Shared/Model/AppAPITypes.swift#L1120) | -| `sndFileComplete` | `user, chatItem, sndFileTransfer` | Upload complete (inline) | [L1121](../Shared/Model/AppAPITypes.swift#L1121) | -| `sndFileProgressXFTP` | `user, chatItem_, fileTransferMeta, sentSize, totalSize` | Upload progress | [L1123](../Shared/Model/AppAPITypes.swift#L1123) | -| `sndFileCompleteXFTP` | `user, chatItem, fileTransferMeta` | XFTP upload complete | [L1125](../Shared/Model/AppAPITypes.swift#L1125) | -| `sndFileError` | `user, chatItem_, fileTransferMeta, errorMessage` | Upload error | [L1127](../Shared/Model/AppAPITypes.swift#L1127) | +| `rcvFileStart` | `user, chatItem` | Download started | [L1224](../Shared/Model/AppAPITypes.swift#L1224) | +| `rcvFileProgressXFTP` | `user, chatItem_, receivedSize, totalSize` | Download progress | [L1225](../Shared/Model/AppAPITypes.swift#L1225) | +| `rcvFileComplete` | `user, chatItem` | Download complete | [L1226](../Shared/Model/AppAPITypes.swift#L1226) | +| `rcvFileSndCancelled` | `user, chatItem, rcvFileTransfer` | Sender cancelled | [L1228](../Shared/Model/AppAPITypes.swift#L1228) | +| `rcvFileError` | `user, chatItem_, agentError, rcvFileTransfer` | Download error | [L1229](../Shared/Model/AppAPITypes.swift#L1229) | +| `sndFileStart` | `user, chatItem, sndFileTransfer` | Upload started | [L1232](../Shared/Model/AppAPITypes.swift#L1232) | +| `sndFileComplete` | `user, chatItem, sndFileTransfer` | Upload complete (inline) | [L1233](../Shared/Model/AppAPITypes.swift#L1233) | +| `sndFileProgressXFTP` | `user, chatItem_, fileTransferMeta, sentSize, totalSize` | Upload progress | [L1235](../Shared/Model/AppAPITypes.swift#L1235) | +| `sndFileCompleteXFTP` | `user, chatItem, fileTransferMeta` | XFTP upload complete | [L1237](../Shared/Model/AppAPITypes.swift#L1237) | +| `sndFileError` | `user, chatItem_, fileTransferMeta, errorMessage` | Upload error | [L1239](../Shared/Model/AppAPITypes.swift#L1239) | ### Call Events | Event | Key Fields | Description | Source | |-------|-----------|-------------|--------| -| `callInvitation` | `callInvitation: RcvCallInvitation` | Incoming call | [L1130](../Shared/Model/AppAPITypes.swift#L1130) | -| `callOffer` | `user, contact, callType, offer, sharedKey, askConfirmation` | SDP offer received | [L1131](../Shared/Model/AppAPITypes.swift#L1131) | -| `callAnswer` | `user, contact, answer` | SDP answer received | [L1132](../Shared/Model/AppAPITypes.swift#L1132) | -| `callExtraInfo` | `user, contact, extraInfo` | ICE candidates received | [L1133](../Shared/Model/AppAPITypes.swift#L1133) | -| `callEnded` | `user, contact` | Call ended by remote | [L1134](../Shared/Model/AppAPITypes.swift#L1134) | +| `callInvitation` | `callInvitation: RcvCallInvitation` | Incoming call | [L1242](../Shared/Model/AppAPITypes.swift#L1242) | +| `callOffer` | `user, contact, callType, offer, sharedKey, askConfirmation` | SDP offer received | [L1243](../Shared/Model/AppAPITypes.swift#L1243) | +| `callAnswer` | `user, contact, answer` | SDP answer received | [L1244](../Shared/Model/AppAPITypes.swift#L1244) | +| `callExtraInfo` | `user, contact, extraInfo` | ICE candidates received | [L1245](../Shared/Model/AppAPITypes.swift#L1245) | +| `callEnded` | `user, contact` | Call ended by remote | [L1246](../Shared/Model/AppAPITypes.swift#L1246) | ### Connection Security Events | Event | Key Fields | Description | Source | |-------|-----------|-------------|--------| -| `contactSwitch` | `user, contact, switchProgress` | Key rotation progress | [L1071](../Shared/Model/AppAPITypes.swift#L1071) | -| `groupMemberSwitch` | `user, groupInfo, member, switchProgress` | Member key rotation | [L1072](../Shared/Model/AppAPITypes.swift#L1072) | -| `contactRatchetSync` | `user, contact, ratchetSyncProgress` | Ratchet sync progress | [L1073](../Shared/Model/AppAPITypes.swift#L1073) | -| `groupMemberRatchetSync` | `user, groupInfo, member, ratchetSyncProgress` | Member ratchet sync | [L1074](../Shared/Model/AppAPITypes.swift#L1074) | +| `contactSwitch` | `user, contact, switchProgress` | Key rotation progress | [L1181](../Shared/Model/AppAPITypes.swift#L1181) | +| `groupMemberSwitch` | `user, groupInfo, member, switchProgress` | Member key rotation | [L1182](../Shared/Model/AppAPITypes.swift#L1182) | +| `contactRatchetSync` | `user, contact, ratchetSyncProgress` | Ratchet sync progress | [L1183](../Shared/Model/AppAPITypes.swift#L1183) | +| `groupMemberRatchetSync` | `user, groupInfo, member, ratchetSyncProgress` | Member ratchet sync | [L1184](../Shared/Model/AppAPITypes.swift#L1184) | ### System Events | Event | Key Fields | Description | Source | |-------|-----------|-------------|--------| -| `chatSuspended` | -- | Core suspended | [L1070](../Shared/Model/AppAPITypes.swift#L1070) | +| `chatSuspended` | -- | Core suspended | [L1180](../Shared/Model/AppAPITypes.swift#L1180) | --- ## 5. Error Types -Defined in [`SimpleXChat/APITypes.swift` L699](../SimpleXChat/APITypes.swift#L699): +Defined in [`SimpleXChat/APITypes.swift` L765](../SimpleXChat/APITypes.swift#L765): ```swift public enum ChatError: Decodable, Hashable, Error { @@ -488,12 +488,12 @@ public enum ChatError: Decodable, Hashable, Error { | Category | Enum | Description | Source | |----------|------|-------------|--------| | Chat logic | `ChatErrorType` | Business logic errors (e.g., invalid state, permission denied, `chatRelayExists`) | [`APITypes.swift` L788](../SimpleXChat/APITypes.swift#L788) | -| SMP Agent | `AgentErrorType` | Protocol/network errors from the SMP agent layer | [`APITypes.swift` L884](../SimpleXChat/APITypes.swift#L884) | -| Database store | `StoreError` | SQLite query/constraint errors (includes relay-related: `relayUserNotFound`, `duplicateMemberId`, `userChatRelayNotFound`, `groupRelayNotFound`, `groupRelayNotFoundByMemberId`) | [`APITypes.swift` L802](../SimpleXChat/APITypes.swift#L802) | -| Database engine | `DatabaseError` | DB open/migration/encryption errors | [`APITypes.swift` L871](../SimpleXChat/APITypes.swift#L871) | -| Remote control | `RemoteCtrlError` | Remote desktop session errors | [`APITypes.swift` L1054](../SimpleXChat/APITypes.swift#L1054) | -| Parse failure | `invalidJSON` | Failed to decode response JSON | [`APITypes.swift` L699](../SimpleXChat/APITypes.swift#L699) | -| Unexpected | `unexpectedResult` | Response type does not match expected | [`APITypes.swift` L699](../SimpleXChat/APITypes.swift#L699) | +| SMP Agent | `AgentErrorType` | Protocol/network errors from the SMP agent layer | [`APITypes.swift` L1046](../SimpleXChat/APITypes.swift#L1046) | +| Database store | `StoreError` | SQLite query/constraint errors (includes relay-related: `relayUserNotFound`, `duplicateMemberId`, `userChatRelayNotFound`, `groupRelayNotFound`, `groupRelayNotFoundByMemberId`) | [`APITypes.swift` L964](../SimpleXChat/APITypes.swift#L964) | +| Database engine | `DatabaseError` | DB open/migration/encryption errors | [`APITypes.swift` L1033](../SimpleXChat/APITypes.swift#L1033) | +| Remote control | `RemoteCtrlError` | Remote desktop session errors | [`APITypes.swift` L1234](../SimpleXChat/APITypes.swift#L1234) | +| Parse failure | `invalidJSON` | Failed to decode response JSON | [`APITypes.swift` L765](../SimpleXChat/APITypes.swift#L765) | +| Unexpected | `unexpectedResult` | Response type does not match expected | [`APITypes.swift` L765](../SimpleXChat/APITypes.swift#L765) | --- @@ -562,12 +562,12 @@ public func sendSimpleXCmd( // API.swift L115 ```swift // Polls for async events from the Haskell core -func chatRecvMsg( // SimpleXAPI.swift L237 +func chatRecvMsg( // SimpleXAPI.swift L241 _ ctrl: chat_ctrl? = nil ) async -> APIResult? // Processes a received event and updates app state -func processReceivedMsg( // SimpleXAPI.swift L2282 +func processReceivedMsg( // SimpleXAPI.swift L2555 _ res: ChatEvent ) async ``` @@ -623,9 +623,9 @@ A service response is carried as [`JSONValue`](../SimpleXChat/APITypes.swift#L72 | File | Path | |------|------| | ChatCommand enum | [`Shared/Model/AppAPITypes.swift` L15](../Shared/Model/AppAPITypes.swift#L15) | -| ChatResponse0/1/2 enums | [`Shared/Model/AppAPITypes.swift` L657, L779, L919](../Shared/Model/AppAPITypes.swift#L657) | -| ChatEvent enum | [`Shared/Model/AppAPITypes.swift` L1069](../Shared/Model/AppAPITypes.swift#L1069) | -| APIResult, ChatError | [`SimpleXChat/APITypes.swift` L27, L699](../SimpleXChat/APITypes.swift#L27) | +| ChatResponse0/1/2 enums | [`Shared/Model/AppAPITypes.swift` L729, L857, L1003](../Shared/Model/AppAPITypes.swift#L729) | +| ChatEvent enum | [`Shared/Model/AppAPITypes.swift` L1179](../Shared/Model/AppAPITypes.swift#L1179) | +| APIResult, ChatError | [`SimpleXChat/APITypes.swift` L27, L765](../SimpleXChat/APITypes.swift#L27) | | FFI bridge functions | [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift) | | Service response JSON | [`SimpleXChat/APITypes.swift` L722](../SimpleXChat/APITypes.swift#L722) | | Low-level FFI | [`SimpleXChat/API.swift`](../SimpleXChat/API.swift) | diff --git a/apps/ios/spec/architecture.md b/apps/ios/spec/architecture.md index 9ab3eb1fd2..d41d368ee6 100644 --- a/apps/ios/spec/architecture.md +++ b/apps/ios/spec/architecture.md @@ -5,7 +5,7 @@ > Related specs: [README](README.md) | [API Reference](api.md) | [State Management](state.md) | [Database](database.md) > Related product: [Product Overview](../product/README.md) -**Source:** [`SimpleXApp.swift`](../Shared/SimpleXApp.swift#L1-L183) | [`AppDelegate.swift`](../Shared/AppDelegate.swift#L1-L209) | [`ContentView.swift`](../Shared/ContentView.swift#L1-L513) | [`ChatModel.swift`](../Shared/Model/ChatModel.swift#L1-L1373) | [`SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift#L1-L2915) | [`AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift#L1-L2357) | [`APITypes.swift`](../SimpleXChat/APITypes.swift#L1-L1071) | [`API.swift`](../SimpleXChat/API.swift#L1-L388) +**Source:** [`SimpleXApp.swift`](../Shared/SimpleXApp.swift#L1-L183) | [`AppDelegate.swift`](../Shared/AppDelegate.swift#L1-L209) | [`ContentView.swift`](../Shared/ContentView.swift#L1-L513) | [`ChatModel.swift`](../Shared/Model/ChatModel.swift#L1-L1521) | [`SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift#L1-L3271) | [`AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift#L1-L2600) | [`APITypes.swift`](../SimpleXChat/APITypes.swift#L1-L1258) | [`API.swift`](../SimpleXChat/API.swift#L1-L388) --- @@ -55,11 +55,11 @@ The app follows a strict layered model where each layer communicates only with i |-------|------|------|------| | Views | [`Shared/Views/ChatList/ChatListView.swift`](../Shared/Views/ChatList/ChatListView.swift) | Chat list rendering | | | Views | [`Shared/Views/Chat/ChatView.swift`](../Shared/Views/Chat/ChatView.swift) | Conversation rendering | | -| State | [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L337) | `ChatModel`, `ItemsModel`, `Chat` classes | L337, L74, L1271 | +| State | [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L412) | `ChatModel`, `ItemsModel`, `Chat` classes | L412, L74, L1418 | | API | [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift#L93) | FFI bridge functions | L93 | -| API | [`Shared/Model/AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift#L15) | `ChatCommand`, `ChatResponse`, `ChatEvent` enums | L15, L649, L1055 | +| API | [`Shared/Model/AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift#L15) | `ChatCommand`, `ChatResponse`, `ChatEvent` enums | L15, L729, L1179 | | FFI | [`SimpleXChat/SimpleX.h`](../SimpleXChat/SimpleX.h#L1-L49) | C header declaring Haskell exports | | -| FFI | [`SimpleXChat/APITypes.swift`](../SimpleXChat/APITypes.swift#L27) | `APIResult`, `ChatError`, `ChatCmdProtocol` | L27, L699, L17 | +| FFI | [`SimpleXChat/APITypes.swift`](../SimpleXChat/APITypes.swift#L27) | `APIResult`, `ChatError`, `ChatCmdProtocol` | L27, L765, L17 | | Core | `../../src/Simplex/Chat/Controller.hs` | Haskell command processor — see `processCommand` in `Controller.hs` | | --- @@ -99,7 +99,7 @@ char *chat_encrypt_file(chat_ctrl ctl, char *fromPath, char *toPath); char *chat_decrypt_file(char *fromPath, char *key, char *nonce, char *toPath); ``` -### [Swift Bridge Functions (SimpleXAPI.swift)](../Shared/Model/SimpleXAPI.swift#L93-L221) +### [Swift Bridge Functions (SimpleXAPI.swift)](../Shared/Model/SimpleXAPI.swift#L93-L229) ```swift // Synchronous send -- blocks calling thread @@ -124,13 +124,13 @@ func sendSimpleXCmd(_ cmd: ChatCmdProtocol, _ ctrl: chat_ctrl? 5. Swift decodes JSON into [`APIResult`](../SimpleXChat/APITypes.swift#L27) where `R: ChatAPIResult` 6. Result is either `.result(R)`, `.error(ChatError)`, or `.invalid(type, json)` -### [Background Task Protection](../Shared/Model/SimpleXAPI.swift#L54-L79) +### [Background Task Protection](../Shared/Model/SimpleXAPI.swift#L54-L74) All FFI calls are wrapped in [`beginBGTask()`](../Shared/Model/SimpleXAPI.swift#L54) / `endBackgroundTask()` to prevent iOS from killing the app mid-operation. The `maxTaskDuration` is 15 seconds. --- -## [3. Event Streaming](../Shared/Model/SimpleXAPI.swift#L2220-L2916) +## [3. Event Streaming](../Shared/Model/SimpleXAPI.swift#L2509-L3271) The Haskell core emits async events (new messages, connection status changes, file progress, etc.) that are not direct responses to commands. These are received via polling: @@ -138,28 +138,28 @@ The Haskell core emits async events (new messages, connection status changes, fi Haskell Core --[chat_recv_msg_wait]--> Swift event loop --> ChatModel update --> SwiftUI re-render ``` -The event loop is implemented in [`ChatReceiver`](../Shared/Model/SimpleXAPI.swift#L2220-L2263), and events are dispatched by [`processReceivedMsg`](../Shared/Model/SimpleXAPI.swift#L2266). +The event loop is implemented in [`ChatReceiver`](../Shared/Model/SimpleXAPI.swift#L2509-L2552), and events are dispatched by [`processReceivedMsg`](../Shared/Model/SimpleXAPI.swift#L2555). -### [Event Types (ChatEvent enum)](../Shared/Model/AppAPITypes.swift#L1055-L1129) +### [Event Types (ChatEvent enum)](../Shared/Model/AppAPITypes.swift#L1179-L1422) Key async events delivered from core to UI: | Event | Description | Line | |-------|-------------|------| -| `newChatItems` | New messages received | [L1070](../Shared/Model/AppAPITypes.swift#L1070) | -| `chatItemUpdated` | Message edited by sender | [L1072](../Shared/Model/AppAPITypes.swift#L1072) | -| `chatItemsDeleted` | Messages deleted | [L1074](../Shared/Model/AppAPITypes.swift#L1074) | -| `chatItemReaction` | Reaction added/removed | [L1073](../Shared/Model/AppAPITypes.swift#L1073) | -| `contactConnected` | New contact connected | [L1062](../Shared/Model/AppAPITypes.swift#L1062) | -| `contactUpdated` | Contact profile changed | [L1066](../Shared/Model/AppAPITypes.swift#L1066) | -| `receivedGroupInvitation` | Group invitation received | [L1077](../Shared/Model/AppAPITypes.swift#L1077) | -| `groupMemberUpdated` | Group member info changed | [L1067](../Shared/Model/AppAPITypes.swift#L1067) | -| `callInvitation` | Incoming call | [L1115](../Shared/Model/AppAPITypes.swift#L1115) | -| `chatSuspended` | Core suspended (background) | [L1056](../Shared/Model/AppAPITypes.swift#L1056) | -| `rcvFileComplete` | File download finished | [L1099](../Shared/Model/AppAPITypes.swift#L1099) | -| `sndFileCompleteXFTP` | File upload finished | [L1110](../Shared/Model/AppAPITypes.swift#L1110) | +| `newChatItems` | New messages received | [L1194](../Shared/Model/AppAPITypes.swift#L1194) | +| `chatItemUpdated` | Message edited by sender | [L1196](../Shared/Model/AppAPITypes.swift#L1196) | +| `chatItemsDeleted` | Messages deleted | [L1198](../Shared/Model/AppAPITypes.swift#L1198) | +| `chatItemReaction` | Reaction added/removed | [L1197](../Shared/Model/AppAPITypes.swift#L1197) | +| `contactConnected` | New contact connected | [L1186](../Shared/Model/AppAPITypes.swift#L1186) | +| `contactUpdated` | Contact profile changed | [L1190](../Shared/Model/AppAPITypes.swift#L1190) | +| `receivedGroupInvitation` | Group invitation received | [L1201](../Shared/Model/AppAPITypes.swift#L1201) | +| `groupMemberUpdated` | Group member info changed | [L1191](../Shared/Model/AppAPITypes.swift#L1191) | +| `callInvitation` | Incoming call | [L1242](../Shared/Model/AppAPITypes.swift#L1242) | +| `chatSuspended` | Core suspended (background) | [L1180](../Shared/Model/AppAPITypes.swift#L1180) | +| `rcvFileComplete` | File download finished | [L1226](../Shared/Model/AppAPITypes.swift#L1226) | +| `sndFileCompleteXFTP` | File upload finished | [L1237](../Shared/Model/AppAPITypes.swift#L1237) | -Events are decoded as [`ChatEvent`](../Shared/Model/AppAPITypes.swift#L1055) enum in `Shared/Model/AppAPITypes.swift` and dispatched to update `ChatModel` / `ItemsModel` properties, triggering SwiftUI view re-renders via `@Published` property observation. +Events are decoded as [`ChatEvent`](../Shared/Model/AppAPITypes.swift#L1179) enum in `Shared/Model/AppAPITypes.swift` and dispatched to update `ChatModel` / `ItemsModel` properties, triggering SwiftUI view re-renders via `@Published` property observation. --- @@ -203,15 +203,15 @@ See [Database & Storage specification](database.md) for full details. ┌──────────┐ │ DB Setup │ chat_migrate_init_key() └─────┬─────┘ - │ startChat() SimpleXAPI.swift L2098 + │ startChat() SimpleXAPI.swift L2360 v ┌──────────┐ - │ Active │ apiActivateChat() SimpleXAPI.swift L358 + │ Active │ apiActivateChat() SimpleXAPI.swift L366 └─────┬─────┘ │ scenePhase == .background v ┌──────────┐ - │Background │ apiSuspendChat(timeoutMicroseconds:) SimpleXAPI.swift L368 + │Background │ apiSuspendChat(timeoutMicroseconds:) SimpleXAPI.swift L376 └─────┬─────┘ │ scenePhase == .active v @@ -259,8 +259,8 @@ The Share Extension (`SimpleX SE/`) allows sharing content (text, images, files) Optional desktop pairing allows controlling the mobile app from a desktop client: - **Pairing**: Encrypted QR code scanned by desktop client establishes a session -- **Commands**: [`connectRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1613), [`findKnownRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1620), [`confirmRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1624), [`verifyRemoteCtrlSession`](../Shared/Model/SimpleXAPI.swift#L1630), [`listRemoteCtrls`](../Shared/Model/SimpleXAPI.swift#L1636), [`stopRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1642), [`deleteRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1646) -- **State**: [`ChatModel.remoteCtrlSession`](../Shared/Model/ChatModel.swift#L395)`: RemoteCtrlSession?` tracks the active session +- **Commands**: [`connectRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1766), [`findKnownRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1773), [`confirmRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1777), [`verifyRemoteCtrlSession`](../Shared/Model/SimpleXAPI.swift#L1783), [`listRemoteCtrls`](../Shared/Model/SimpleXAPI.swift#L1789), [`stopRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1795), [`deleteRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1799) +- **State**: [`ChatModel.remoteCtrlSession`](../Shared/Model/ChatModel.swift#L474)`: RemoteCtrlSession?` tracks the active session - **Transport**: Encrypted reverse HTTP transport between mobile and desktop - **Source**: [`Shared/Views/RemoteAccess/ConnectDesktopView.swift`](../Shared/Views/RemoteAccess/ConnectDesktopView.swift#L1-L545), see `Remote.hs` in `../../src/Simplex/Chat/` @@ -324,7 +324,7 @@ Chat relays are SMP servers that forward messages to channel subscribers. They a | Root view | [`Shared/ContentView.swift`](../Shared/ContentView.swift#L24) | L24 | | FFI bridge | [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift#L93) | L93 | | Low-level FFI | [`SimpleXChat/API.swift`](../SimpleXChat/API.swift#L115) | L115 | -| App state | [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L337) | L337 | +| App state | [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L412) | L412 | | API types | [`Shared/Model/AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift#L15) | L15 | | Shared types | [`SimpleXChat/APITypes.swift`](../SimpleXChat/APITypes.swift#L27) | L27 | | C header | [`SimpleXChat/SimpleX.h`](../SimpleXChat/SimpleX.h#L1-L49) | | diff --git a/apps/ios/spec/client/chat-list.md b/apps/ios/spec/client/chat-list.md index a90a71fb13..0a51a69c55 100644 --- a/apps/ios/spec/client/chat-list.md +++ b/apps/ios/spec/client/chat-list.md @@ -64,7 +64,7 @@ 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()`](../../Shared/Views/ChatList/ChatListView.swift#L555) +- Applies search query filtering via [`filteredChats()`](../../Shared/Views/ChatList/ChatListView.swift#L736) - Sorts by last activity (most recent first), with pinned chats at top ### Layout @@ -81,11 +81,11 @@ The root list view. Key responsibilities: | Function | Line | Description | |----------|------|-------------| -| [`body`](../../Shared/Views/ChatList/ChatListView.swift#L183) | 183 | Main view body | -| [`filteredChats()`](../../Shared/Views/ChatList/ChatListView.swift#L555) | 555 | Applies active filter and search to chat list | -| [`searchString()`](../../Shared/Views/ChatList/ChatListView.swift#L598) | 598 | Normalizes search text for comparison | -| [`unreadBadge()`](../../Shared/Views/ChatList/ChatListView.swift#L529) | 529 | Renders unread count circle badge | -| [`stopAudioPlayer()`](../../Shared/Views/ChatList/ChatListView.swift#L549) | 549 | Stops any playing voice message | +| [`body`](../../Shared/Views/ChatList/ChatListView.swift#L188) | 188 | Main view body | +| [`filteredChats()`](../../Shared/Views/ChatList/ChatListView.swift#L736) | 736 | Applies active filter and search to chat list | +| [`searchString()`](../../Shared/Views/ChatList/ChatListView.swift#L779) | 779 | Normalizes search text for comparison | +| [`unreadBadge()`](../../Shared/Views/ChatList/ChatListView.swift#L710) | 710 | Renders unread count circle badge | +| [`stopAudioPlayer()`](../../Shared/Views/ChatList/ChatListView.swift#L730) | 730 | Stops any playing voice message | --- @@ -173,11 +173,11 @@ Horizontal scrolling tab bar below the navigation bar. Tabs: | Group Reports | `.presetTag(.groupReports)` | Groups with pending reports | | User tags | `.userTag(ChatTag)` | User-defined custom tags | -Filter matching is handled by [`presetTagMatchesChat()`](../../Shared/Views/ChatList/ChatListView.swift#L1134) and the in-view [`TagsView`](../../Shared/Views/ChatList/ChatListView.swift#L928) struct. +Filter matching is handled by [`presetTagMatchesChat()`](../../Shared/Views/ChatList/ChatListView.swift#L1353) and the in-view [`TagsView`](../../Shared/Views/ChatList/ChatListView.swift#L1147) struct. ### ChatTagsModel State -Filtering state is managed by [`ChatTagsModel`](../../Shared/Model/ChatModel.swift#L189) (`ChatModel.swift` L183): +Filtering state is managed by [`ChatTagsModel`](../../Shared/Model/ChatModel.swift#L189) (`ChatModel.swift` L189): ```swift class ChatTagsModel: ObservableObject { @@ -198,7 +198,7 @@ class ChatTagsModel: ObservableObject { |------|------|------|-------------| | [`PresetTag`](../../Shared/Views/ChatList/ChatListView.swift#L36) | ChatListView.swift | 36 | Enum of built-in filter categories | | [`ActiveFilter`](../../Shared/Views/ChatList/ChatListView.swift#L53) | ChatListView.swift | 53 | Enum wrapping preset, user-tag, or unread filter | -| [`setActiveFilter()`](../../Shared/Views/ChatList/ChatListView.swift#L1113) | ChatListView.swift | 1113 | Applies a filter and persists selection | +| [`setActiveFilter()`](../../Shared/Views/ChatList/ChatListView.swift#L1332) | ChatListView.swift | 1332 | Applies a filter and persists selection | ### Tag Management Commands - `apiCreateChatTag(tag: ChatTagData)` -- create tag @@ -213,7 +213,7 @@ class ChatTagsModel: ObservableObject { Search is available via pull-down gesture or search button in the navigation bar. -**Search bar UI:** [`ChatListSearchBar`](../../Shared/Views/ChatList/ChatListView.swift#L662) +**Search bar UI:** [`ChatListSearchBar`](../../Shared/Views/ChatList/ChatListView.swift#L843) ### Filtering Logic - Filters `ChatModel.chats` by matching search text against: @@ -221,7 +221,7 @@ Search is available via pull-down gesture or search button in the navigation bar - `chatInfo.localAlias` (local alias) - `chatInfo.fullName` (full name) - For deeper message content search, uses `apiGetChat(chatId:, search:)` parameter -- Core logic in [`filteredChats()`](../../Shared/Views/ChatList/ChatListView.swift#L555) and [`searchString()`](../../Shared/Views/ChatList/ChatListView.swift#L598) +- Core logic in [`filteredChats()`](../../Shared/Views/ChatList/ChatListView.swift#L736) and [`searchString()`](../../Shared/Views/ChatList/ChatListView.swift#L779) ### Search Results - Matching chats are displayed in the same list format @@ -292,7 +292,7 @@ Gradient card inviting the user to invest on Wefunder. Shown only when [`isInUS` | Where | Condition | Layout | |-------|-----------|--------| -| Chat list | rendered whenever [`chatListContent`](../../Shared/Views/ChatList/ChatListView.swift#L413) is, in the `List` after `OneHandUICard` and before the chats | `.padding(.vertical, 3)`, flipped for one-hand UI, `.zIndex(1)` | +| Chat list | rendered whenever [`chatListContent`](../../Shared/Views/ChatList/ChatListView.swift#L533) is, in the `List` after `OneHandUICard` and before the chats | `.padding(.vertical, 3)`, flipped for one-hand UI, `.zIndex(1)` | | Onboarding | below [`ConnectOnboardingView`](../../Shared/Views/NewChat/OnboardingCards.swift#L135) when `shouldShowOnboarding` | `.padding(.horizontal, 20)` (the onboarding cards' margin), `.padding(.bottom, 8)` | The list has a single banner slot, filled by an `if`/`else if` chain in priority order: the support-ended alert (`supportEnded`), the renewal-failure alert (`badgeIssueFailed`), the pitch, then the Wefunder banner. Each banner records itself in `ChatModel.chatListBanner` (`.badgeExpired`, `.badgeIssueFailed`, `.badgePitch`, `.getStake`) in its `onAppear`, and the pitch and Wefunder conditions start with `chatModel.bannerSlotFree(for:)` — true only while nothing else was shown this app session — so dismissing a banner never puts another in its place until restart. The alerts have no such check: an alert takes the slot whenever present, and once shown it holds it. The pitch also requires `noShownBadge`, false until `BadgeModel` holds the current user's state, so it cannot take the slot from a supporter whose badge loads a moment later. The onboarding placement applies the same `bannerSlotFree` check and records `.getStake`. @@ -303,7 +303,7 @@ In the onboarding branch the `.scaleEffect` and `ThemedBackground` are applied t | Default | Set by | Effect | |---------|--------|--------| -| `DEFAULT_GET_STAKE_BANNER_TAPPED` | [`openGetStake()`](../../Shared/Views/ChatList/ChatListView.swift#L408) | the dismiss X appears from then on, while there are chats | +| `DEFAULT_GET_STAKE_BANNER_TAPPED` | [`openGetStake()`](../../Shared/Views/ChatList/ChatListView.swift#L528) | the dismiss X appears from then on, while there are chats | | `DEFAULT_GET_STAKE_BANNER_DISMISSED` | the dismiss X | hides the banner in both placements | | `DEFAULT_SUPPORTER_BANNER_TAPPED` | tapping the supporter pitch | the pitch's dismiss X appears from then on | | `DEFAULT_SUPPORTER_BANNER_SHOWN` | the pitch's dismiss X, through its "You can support SimpleX later in Settings." alert, and a successful code redemption | hides the pitch | diff --git a/apps/ios/spec/client/chat-view.md b/apps/ios/spec/client/chat-view.md index c509662c9e..7f4d98e787 100644 --- a/apps/ios/spec/client/chat-view.md +++ b/apps/ios/spec/client/chat-view.md @@ -351,7 +351,7 @@ Groups use separate [`groupLinkButton()`](../../Shared/Views/Chat/Group/GroupCha ### [`channelRelaysButton()`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift#L639) → [`ChannelRelaysView`](../../Shared/Views/Chat/Group/ChannelRelaysView.swift) Navigates to relay list view with role-based branches: -- **Owner**: loads `[GroupRelay]` via [`apiGetGroupRelays`](../../Shared/Model/SimpleXAPI.swift#L1839) (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`). When `relayStatus == .rsRejected` the indicator dot is red and the text reads "rejected", matching the `connFailed`/`removed` rendering. +- **Owner**: loads `[GroupRelay]` via [`apiGetGroupRelays`](../../Shared/Model/SimpleXAPI.swift#L1999) (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`). When `relayStatus == .rsRejected` the indicator dot is red and the text reads "rejected", matching the `connFailed`/`removed` rendering. - **Member**: filters `chatModel.groupMembers` by `.memberRole == .relay`. Shows relay member display names only (no status data). - **Add relay sheet**: `existingRelayIds` excludes every `chatRelayId` present in `groupRelays` regardless of `relayStatus`, so an already-listed relay (including `.rsInactive` and `.rsRejected`) cannot be re-added from the sheet. This mirrors the backend gate at `APIAddGroupRelays` (`existingRelayIds`), which rejects duplicate `chatRelayId`s; operator must remove the relay first via the swipe action. diff --git a/apps/ios/spec/services/files.md b/apps/ios/spec/services/files.md index 7e1f8a2ad1..c1a9783654 100644 --- a/apps/ios/spec/services/files.md +++ b/apps/ios/spec/services/files.md @@ -94,9 +94,9 @@ Files below certain size thresholds are automatically accepted and downloaded wi - When a message with a file attachment arrives: 1. Check if file size is below the auto-receive threshold for its type - 2. If below: automatically call [`setFileToReceive(fileId:, userApprovedRelays:, encrypted:)`](../../Shared/Model/AppAPITypes.swift#L168) followed by download + 2. If below: automatically call [`setFileToReceive(fileId:, userApprovedRelays:, encrypted:)`](../../Shared/Model/AppAPITypes.swift#L181) followed by download 3. If above: show download button in chat item, wait for user action - 4. User manually triggers download via [`receiveFile(fileId:, userApprovedRelays:, encrypted:, inline:)`](../../Shared/Model/AppAPITypes.swift#L167) + 4. User manually triggers download via [`receiveFile(fileId:, userApprovedRelays:, encrypted:, inline:)`](../../Shared/Model/AppAPITypes.swift#L180) ### Relay Approval @@ -190,7 +190,7 @@ public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(300) // 5 minutes (300 second ## [7. CryptoFile -- At-Rest Encryption](../../SimpleXChat/ChatTypes.swift#L4241) -When [`apiSetEncryptLocalFiles(enable: true)`](../../Shared/Model/SimpleXAPI.swift#L384) is configured, files stored on the device are AES-encrypted. +When [`apiSetEncryptLocalFiles(enable: true)`](../../Shared/Model/SimpleXAPI.swift#L392) is configured, files stored on the device are AES-encrypted. ### [`CryptoFile`](../../SimpleXChat/ChatTypes.swift#L4241) Type @@ -223,7 +223,7 @@ Implemented in [`CryptoFile.swift`](../../SimpleXChat/CryptoFile.swift): - Encrypted files stored alongside unencrypted files in `Documents/files/` - The `CryptoFileArgs` (key + nonce) are stored in the Haskell database, not on the filesystem -- Toggle via privacy settings: [`apiSetEncryptLocalFiles(enable:)`](../../Shared/Model/SimpleXAPI.swift#L384) +- Toggle via privacy settings: [`apiSetEncryptLocalFiles(enable:)`](../../Shared/Model/SimpleXAPI.swift#L392) --- @@ -250,7 +250,7 @@ func getWallpaperDirectory() -> URL // Documents/wallpapers/ - Downloaded files: `Documents/files/{filename}` - Temporary files during transfer: `Documents/temp_files/` - Wallpaper images: `Documents/wallpapers/` -- File paths are set via [`apiSetAppFilePaths(filesFolder:, tempFolder:, assetsFolder:)`](../../Shared/Model/SimpleXAPI.swift#L377) at startup +- File paths are set via [`apiSetAppFilePaths(filesFolder:, tempFolder:, assetsFolder:)`](../../Shared/Model/SimpleXAPI.swift#L385) at startup --- @@ -315,35 +315,35 @@ Cancels an in-progress upload or download. For XFTP transfers, also requests chu --- -## [10. API Commands](../../Shared/Model/AppAPITypes.swift#L167) +## [10. API Commands](../../Shared/Model/AppAPITypes.swift#L180) | Command | Parameters | Description | Line | |---------|-----------|-------------|------| -| [`receiveFile`](../../Shared/Model/AppAPITypes.swift#L167) | `fileId, userApprovedRelays, encrypted, inline` | Accept and start downloading a file | [L167](../../Shared/Model/AppAPITypes.swift#L167) | -| [`setFileToReceive`](../../Shared/Model/AppAPITypes.swift#L168) | `fileId, userApprovedRelays, encrypted` | Mark file for auto-receive (no immediate download) | [L168](../../Shared/Model/AppAPITypes.swift#L168) | -| [`cancelFile`](../../Shared/Model/AppAPITypes.swift#L169) | `fileId` | Cancel in-progress transfer | [L169](../../Shared/Model/AppAPITypes.swift#L169) | -| [`apiUploadStandaloneFile`](../../Shared/Model/AppAPITypes.swift#L179) | `userId, file: CryptoFile` | Upload file to XFTP without a chat context | [L179](../../Shared/Model/AppAPITypes.swift#L179) | -| [`apiDownloadStandaloneFile`](../../Shared/Model/AppAPITypes.swift#L180) | `userId, url, file: CryptoFile` | Download from XFTP URL | [L180](../../Shared/Model/AppAPITypes.swift#L180) | -| [`apiStandaloneFileInfo`](../../Shared/Model/AppAPITypes.swift#L181) | `url` | Get metadata for an XFTP URL | [L181](../../Shared/Model/AppAPITypes.swift#L181) | +| [`receiveFile`](../../Shared/Model/AppAPITypes.swift#L180) | `fileId, userApprovedRelays, encrypted, inline` | Accept and start downloading a file | [L180](../../Shared/Model/AppAPITypes.swift#L180) | +| [`setFileToReceive`](../../Shared/Model/AppAPITypes.swift#L181) | `fileId, userApprovedRelays, encrypted` | Mark file for auto-receive (no immediate download) | [L181](../../Shared/Model/AppAPITypes.swift#L181) | +| [`cancelFile`](../../Shared/Model/AppAPITypes.swift#L182) | `fileId` | Cancel in-progress transfer | [L182](../../Shared/Model/AppAPITypes.swift#L182) | +| [`apiUploadStandaloneFile`](../../Shared/Model/AppAPITypes.swift#L192) | `userId, file: CryptoFile` | Upload file to XFTP without a chat context | [L192](../../Shared/Model/AppAPITypes.swift#L192) | +| [`apiDownloadStandaloneFile`](../../Shared/Model/AppAPITypes.swift#L193) | `userId, url, file: CryptoFile` | Download from XFTP URL | [L193](../../Shared/Model/AppAPITypes.swift#L193) | +| [`apiStandaloneFileInfo`](../../Shared/Model/AppAPITypes.swift#L194) | `url` | Get metadata for an XFTP URL | [L194](../../Shared/Model/AppAPITypes.swift#L194) | ### File Transfer Events | Event | Description | Line | |-------|-------------|------| -| [`rcvFileAccepted`](../../Shared/Model/AppAPITypes.swift#L1095) | Download request accepted | [L1095](../../Shared/Model/AppAPITypes.swift#L1095) | -| [`rcvFileStart`](../../Shared/Model/AppAPITypes.swift#L1097) | Download started | [L1097](../../Shared/Model/AppAPITypes.swift#L1097) | -| [`rcvFileProgressXFTP`](../../Shared/Model/AppAPITypes.swift#L1098) | Download progress (receivedSize, totalSize) | [L1098](../../Shared/Model/AppAPITypes.swift#L1098) | -| [`rcvFileComplete`](../../Shared/Model/AppAPITypes.swift#L1099) | Download complete | [L1099](../../Shared/Model/AppAPITypes.swift#L1099) | -| [`rcvFileSndCancelled`](../../Shared/Model/AppAPITypes.swift#L1101) | Sender cancelled the transfer | [L1101](../../Shared/Model/AppAPITypes.swift#L1101) | -| [`rcvFileError`](../../Shared/Model/AppAPITypes.swift#L1102) | Download failed | [L1102](../../Shared/Model/AppAPITypes.swift#L1102) | -| [`rcvFileWarning`](../../Shared/Model/AppAPITypes.swift#L1103) | Download warning (non-fatal) | [L1103](../../Shared/Model/AppAPITypes.swift#L1103) | -| [`sndFileStart`](../../Shared/Model/AppAPITypes.swift#L1105) | Upload started | [L1105](../../Shared/Model/AppAPITypes.swift#L1105) | -| [`sndFileComplete`](../../Shared/Model/AppAPITypes.swift#L1106) | Inline upload complete | [L1106](../../Shared/Model/AppAPITypes.swift#L1106) | -| [`sndFileProgressXFTP`](../../Shared/Model/AppAPITypes.swift#L1108) | XFTP upload progress (sentSize, totalSize) | [L1108](../../Shared/Model/AppAPITypes.swift#L1108) | -| [`sndFileCompleteXFTP`](../../Shared/Model/AppAPITypes.swift#L1110) | XFTP upload complete | [L1110](../../Shared/Model/AppAPITypes.swift#L1110) | -| [`sndFileRcvCancelled`](../../Shared/Model/AppAPITypes.swift#L1107) | Receiver cancelled | [L1107](../../Shared/Model/AppAPITypes.swift#L1107) | -| [`sndFileError`](../../Shared/Model/AppAPITypes.swift#L1112) | Upload failed | [L1112](../../Shared/Model/AppAPITypes.swift#L1112) | -| [`sndFileWarning`](../../Shared/Model/AppAPITypes.swift#L1113) | Upload warning (non-fatal) | [L1113](../../Shared/Model/AppAPITypes.swift#L1113) | +| [`rcvFileAccepted`](../../Shared/Model/AppAPITypes.swift#L1222) | Download request accepted | [L1222](../../Shared/Model/AppAPITypes.swift#L1222) | +| [`rcvFileStart`](../../Shared/Model/AppAPITypes.swift#L1224) | Download started | [L1224](../../Shared/Model/AppAPITypes.swift#L1224) | +| [`rcvFileProgressXFTP`](../../Shared/Model/AppAPITypes.swift#L1225) | Download progress (receivedSize, totalSize) | [L1225](../../Shared/Model/AppAPITypes.swift#L1225) | +| [`rcvFileComplete`](../../Shared/Model/AppAPITypes.swift#L1226) | Download complete | [L1226](../../Shared/Model/AppAPITypes.swift#L1226) | +| [`rcvFileSndCancelled`](../../Shared/Model/AppAPITypes.swift#L1228) | Sender cancelled the transfer | [L1228](../../Shared/Model/AppAPITypes.swift#L1228) | +| [`rcvFileError`](../../Shared/Model/AppAPITypes.swift#L1229) | Download failed | [L1229](../../Shared/Model/AppAPITypes.swift#L1229) | +| [`rcvFileWarning`](../../Shared/Model/AppAPITypes.swift#L1230) | Download warning (non-fatal) | [L1230](../../Shared/Model/AppAPITypes.swift#L1230) | +| [`sndFileStart`](../../Shared/Model/AppAPITypes.swift#L1232) | Upload started | [L1232](../../Shared/Model/AppAPITypes.swift#L1232) | +| [`sndFileComplete`](../../Shared/Model/AppAPITypes.swift#L1233) | Inline upload complete | [L1233](../../Shared/Model/AppAPITypes.swift#L1233) | +| [`sndFileProgressXFTP`](../../Shared/Model/AppAPITypes.swift#L1235) | XFTP upload progress (sentSize, totalSize) | [L1235](../../Shared/Model/AppAPITypes.swift#L1235) | +| [`sndFileCompleteXFTP`](../../Shared/Model/AppAPITypes.swift#L1237) | XFTP upload complete | [L1237](../../Shared/Model/AppAPITypes.swift#L1237) | +| [`sndFileRcvCancelled`](../../Shared/Model/AppAPITypes.swift#L1234) | Receiver cancelled | [L1234](../../Shared/Model/AppAPITypes.swift#L1234) | +| [`sndFileError`](../../Shared/Model/AppAPITypes.swift#L1239) | Upload failed | [L1239](../../Shared/Model/AppAPITypes.swift#L1239) | +| [`sndFileWarning`](../../Shared/Model/AppAPITypes.swift#L1240) | Upload warning (non-fatal) | [L1240](../../Shared/Model/AppAPITypes.swift#L1240) | --- @@ -355,7 +355,7 @@ Cancels an in-progress upload or download. For XFTP transfers, also requests chu | CryptoFile FFI operations | [`SimpleXChat/CryptoFile.swift`](../../SimpleXChat/CryptoFile.swift) | `writeCryptoFile`, `readCryptoFile`, `encryptCryptoFile`, `decryptCryptoFile` | | CryptoFile / CryptoFileArgs types | [`SimpleXChat/ChatTypes.swift`](../../SimpleXChat/ChatTypes.swift) | `CryptoFile` (L4241), `CryptoFileArgs` (L4289) | | API command definitions | [`Shared/Model/AppAPITypes.swift`](../../Shared/Model/AppAPITypes.swift) | `receiveFile`, `cancelFile`, `ChatEvent` file events | -| API implementations | [`Shared/Model/SimpleXAPI.swift`](../../Shared/Model/SimpleXAPI.swift) | `receiveFile` (L1471), `cancelFile` (L1590) | +| API implementations | [`Shared/Model/SimpleXAPI.swift`](../../Shared/Model/SimpleXAPI.swift) | `receiveFile` (L1624), `cancelFile` (L1743) | | File view (chat item) | [`Shared/Views/Chat/ChatItem/CIFileView.swift`](../../Shared/Views/Chat/ChatItem/CIFileView.swift) | | | Image view (chat item) | [`Shared/Views/Chat/ChatItem/CIImageView.swift`](../../Shared/Views/Chat/ChatItem/CIImageView.swift) | | | Video view (chat item) | [`Shared/Views/Chat/ChatItem/CIVideoView.swift`](../../Shared/Views/Chat/ChatItem/CIVideoView.swift) | | diff --git a/apps/ios/spec/services/notifications.md b/apps/ios/spec/services/notifications.md index 1062833f9c..801360233e 100644 --- a/apps/ios/spec/services/notifications.md +++ b/apps/ios/spec/services/notifications.md @@ -152,7 +152,7 @@ The NSE uses a subset of the chat API: - If main app is `.active`, NSE may skip processing (main app handles notifications directly) - NSE uses `chat_close_store` / `chat_reopen_store` for safe concurrent access -### [Preview Modes](../../SimpleXChat/APITypes.swift#L664) +### [Preview Modes](../../SimpleXChat/APITypes.swift#L686) `NotificationPreviewMode` controls what the NSE shows: diff --git a/apps/ios/spec/state.md b/apps/ios/spec/state.md index 91111f29f1..a6c51c4571 100644 --- a/apps/ios/spec/state.md +++ b/apps/ios/spec/state.md @@ -1,6 +1,6 @@ # SimpleX Chat iOS -- State Management -**Source:** [`ChatModel.swift`](../Shared/Model/ChatModel.swift#L1-L1404) | [`ChatTypes.swift`](../SimpleXChat/ChatTypes.swift#L1-L5377) +**Source:** [`ChatModel.swift`](../Shared/Model/ChatModel.swift#L1-L1521) | [`ChatTypes.swift`](../SimpleXChat/ChatTypes.swift#L1-L5377) > Technical specification for the app's state architecture: ChatModel, ItemsModel, Chat, ChatInfo, and preference storage. > @@ -63,123 +63,123 @@ ChatTagsModel (singleton -- filter state) --- -## 2. [ChatModel](../Shared/Model/ChatModel.swift#L353-L1289) +## 2. [ChatModel](../Shared/Model/ChatModel.swift#L412-L1406) **Class**: `final class ChatModel: ObservableObject` **Singleton**: `ChatModel.shared` -**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L353) +**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L412) ### Key Published Properties #### App Lifecycle | Property | Type | Description | Line | |----------|------|-------------|------| -| `onboardingStage` | `OnboardingStage?` | Current onboarding step | [L354](../Shared/Model/ChatModel.swift#L354) | -| `chatInitialized` | `Bool` | Whether chat has been initialized | [L363](../Shared/Model/ChatModel.swift#L363) | -| `chatRunning` | `Bool?` | Whether chat engine is running | [L364](../Shared/Model/ChatModel.swift#L364) | -| `chatDbChanged` | `Bool` | Whether DB was changed externally | [L365](../Shared/Model/ChatModel.swift#L365) | -| `chatDbEncrypted` | `Bool?` | Whether DB is encrypted | [L366](../Shared/Model/ChatModel.swift#L366) | -| `chatDbStatus` | `DBMigrationResult?` | DB migration status | [L367](../Shared/Model/ChatModel.swift#L367) | -| `ctrlInitInProgress` | `Bool` | Whether controller is initializing | [L368](../Shared/Model/ChatModel.swift#L368) | -| `migrationState` | `MigrationToState?` | Device migration state | [L417](../Shared/Model/ChatModel.swift#L417) | +| `onboardingStage` | `OnboardingStage?` | Current onboarding step | [L413](../Shared/Model/ChatModel.swift#L413) | +| `chatInitialized` | `Bool` | Whether chat has been initialized | [L422](../Shared/Model/ChatModel.swift#L422) | +| `chatRunning` | `Bool?` | Whether chat engine is running | [L423](../Shared/Model/ChatModel.swift#L423) | +| `chatDbChanged` | `Bool` | Whether DB was changed externally | [L424](../Shared/Model/ChatModel.swift#L424) | +| `chatDbEncrypted` | `Bool?` | Whether DB is encrypted | [L425](../Shared/Model/ChatModel.swift#L425) | +| `chatDbStatus` | `DBMigrationResult?` | DB migration status | [L426](../Shared/Model/ChatModel.swift#L426) | +| `ctrlInitInProgress` | `Bool` | Whether controller is initializing | [L427](../Shared/Model/ChatModel.swift#L427) | +| `migrationState` | `MigrationToState?` | Device migration state | [L477](../Shared/Model/ChatModel.swift#L477) | #### User State | Property | Type | Description | Line | |----------|------|-------------|------| -| `currentUser` | `User?` | Active user profile (triggers theme reapply on change) | [L357](../Shared/Model/ChatModel.swift#L357) | -| `users` | `[UserInfo]` | All user profiles | [L362](../Shared/Model/ChatModel.swift#L362) | -| `v3DBMigration` | `V3DBMigrationState` | Legacy DB migration state | [L356](../Shared/Model/ChatModel.swift#L356) | +| `currentUser` | `User?` | Active user profile (triggers theme reapply on change) | [L416](../Shared/Model/ChatModel.swift#L416) | +| `users` | `[UserInfo]` | All user profiles | [L421](../Shared/Model/ChatModel.swift#L421) | +| `v3DBMigration` | `V3DBMigrationState` | Legacy DB migration state | [L415](../Shared/Model/ChatModel.swift#L415) | #### Chat List | Property | Type | Description | Line | |----------|------|-------------|------| -| `chats` | `[Chat]` (private set) | All conversations for current user | [L374](../Shared/Model/ChatModel.swift#L374) | -| `deletedChats` | `Set` | Chat IDs pending deletion animation | [L375](../Shared/Model/ChatModel.swift#L375) | +| `chats` | `[Chat]` (private set) | All conversations for current user | [L433](../Shared/Model/ChatModel.swift#L433) | +| `deletedChats` | `Set` | Chat IDs pending deletion animation | [L434](../Shared/Model/ChatModel.swift#L434) | #### Active Chat | Property | Type | Description | Line | |----------|------|-------------|------| -| `chatId` | `String?` | Currently open chat ID | [L377](../Shared/Model/ChatModel.swift#L377) | -| `chatAgentConnId` | `String?` | Agent connection ID for active chat | [L378](../Shared/Model/ChatModel.swift#L378) | -| `chatSubStatus` | `SubscriptionStatus?` | Active chat subscription status | [L379](../Shared/Model/ChatModel.swift#L379) | -| `openAroundItemId` | `ChatItem.ID?` | Item to scroll to when opening | [L380](../Shared/Model/ChatModel.swift#L380) | -| `chatToTop` | `String?` | Chat to scroll to top | [L381](../Shared/Model/ChatModel.swift#L381) | -| `groupMembers` | `[GMember]` | Members of active group | [L382](../Shared/Model/ChatModel.swift#L382) | -| `groupMembersIndexes` | `[Int64: Int]` | Member ID to index mapping | [L383](../Shared/Model/ChatModel.swift#L383) | -| `membersLoaded` | `Bool` | Whether members have been loaded | [L384](../Shared/Model/ChatModel.swift#L384) | -| `secondaryIM` | `ItemsModel?` | Secondary items model (e.g. support chat scope) | [L435](../Shared/Model/ChatModel.swift#L435) | +| `chatId` | `String?` | Currently open chat ID | [L436](../Shared/Model/ChatModel.swift#L436) | +| `chatAgentConnId` | `String?` | Agent connection ID for active chat | [L437](../Shared/Model/ChatModel.swift#L437) | +| `chatSubStatus` | `SubscriptionStatus?` | Active chat subscription status | [L438](../Shared/Model/ChatModel.swift#L438) | +| `openAroundItemId` | `ChatItem.ID?` | Item to scroll to when opening | [L439](../Shared/Model/ChatModel.swift#L439) | +| `chatToTop` | `String?` | Chat to scroll to top | [L440](../Shared/Model/ChatModel.swift#L440) | +| `groupMembers` | `[GMember]` | Members of active group | [L442](../Shared/Model/ChatModel.swift#L442) | +| `groupMembersIndexes` | `[Int64: Int]` | Member ID to index mapping | [L443](../Shared/Model/ChatModel.swift#L443) | +| `membersLoaded` | `Bool` | Whether members have been loaded | [L444](../Shared/Model/ChatModel.swift#L444) | +| `secondaryIM` | `ItemsModel?` | Secondary items model (e.g. support chat scope) | [L504](../Shared/Model/ChatModel.swift#L504) | #### Authentication | Property | Type | Description | Line | |----------|------|-------------|------| -| `contentViewAccessAuthenticated` | `Bool` | Whether user has passed authentication | [L371](../Shared/Model/ChatModel.swift#L371) | -| `laRequest` | `LocalAuthRequest?` | Pending authentication request | [L372](../Shared/Model/ChatModel.swift#L372) | +| `contentViewAccessAuthenticated` | `Bool` | Whether user has passed authentication | [L430](../Shared/Model/ChatModel.swift#L430) | +| `laRequest` | `LocalAuthRequest?` | Pending authentication request | [L431](../Shared/Model/ChatModel.swift#L431) | #### Notifications | Property | Type | Description | Line | |----------|------|-------------|------| -| `deviceToken` | `DeviceToken?` | Current APNs device token | [L395](../Shared/Model/ChatModel.swift#L395) | -| `savedToken` | `DeviceToken?` | Previously saved token | [L396](../Shared/Model/ChatModel.swift#L396) | -| `tokenRegistered` | `Bool` | Whether token is registered with server | [L397](../Shared/Model/ChatModel.swift#L397) | -| `tokenStatus` | `NtfTknStatus?` | Token registration status | [L399](../Shared/Model/ChatModel.swift#L399) | -| `notificationMode` | `NotificationsMode` | Current notification mode (.off/.periodic/.instant) | [L400](../Shared/Model/ChatModel.swift#L400) | -| `notificationServer` | `String?` | Notification server URL | [L401](../Shared/Model/ChatModel.swift#L401) | -| `notificationPreview` | `NotificationPreviewMode` | What to show in notifications | [L402](../Shared/Model/ChatModel.swift#L402) | -| `notificationResponse` | `UNNotificationResponse?` | Pending notification action | [L369](../Shared/Model/ChatModel.swift#L369) | -| `ntfContactRequest` | `NTFContactRequest?` | Pending contact request from notification | [L404](../Shared/Model/ChatModel.swift#L404) | -| `ntfCallInvitationAction` | `(ChatId, NtfCallAction)?` | Pending call action from notification | [L405](../Shared/Model/ChatModel.swift#L405) | +| `deviceToken` | `DeviceToken?` | Current APNs device token | [L455](../Shared/Model/ChatModel.swift#L455) | +| `savedToken` | `DeviceToken?` | Previously saved token | [L456](../Shared/Model/ChatModel.swift#L456) | +| `tokenRegistered` | `Bool` | Whether token is registered with server | [L457](../Shared/Model/ChatModel.swift#L457) | +| `tokenStatus` | `NtfTknStatus?` | Token registration status | [L459](../Shared/Model/ChatModel.swift#L459) | +| `notificationMode` | `NotificationsMode` | Current notification mode (.off/.periodic/.instant) | [L460](../Shared/Model/ChatModel.swift#L460) | +| `notificationServer` | `String?` | Notification server URL | [L461](../Shared/Model/ChatModel.swift#L461) | +| `notificationPreview` | `NotificationPreviewMode` | What to show in notifications | [L462](../Shared/Model/ChatModel.swift#L462) | +| `notificationResponse` | `UNNotificationResponse?` | Pending notification action | [L428](../Shared/Model/ChatModel.swift#L428) | +| `ntfContactRequest` | `NTFContactRequest?` | Pending contact request from notification | [L464](../Shared/Model/ChatModel.swift#L464) | +| `ntfCallInvitationAction` | `(ChatId, NtfCallAction)?` | Pending call action from notification | [L465](../Shared/Model/ChatModel.swift#L465) | #### Calls | Property | Type | Description | Line | |----------|------|-------------|------| -| `callInvitations` | `[ChatId: RcvCallInvitation]` | Pending incoming call invitations | [L407](../Shared/Model/ChatModel.swift#L407) | -| `activeCall` | `Call?` | Currently active call | [L408](../Shared/Model/ChatModel.swift#L408) | -| `callCommand` | `WebRTCCommandProcessor` | WebRTC command queue | [L409](../Shared/Model/ChatModel.swift#L409) | -| `showCallView` | `Bool` | Whether to show full-screen call UI | [L410](../Shared/Model/ChatModel.swift#L410) | -| `activeCallViewIsCollapsed` | `Bool` | Whether call view is in PiP mode | [L411](../Shared/Model/ChatModel.swift#L411) | +| `callInvitations` | `[ChatId: RcvCallInvitation]` | Pending incoming call invitations | [L467](../Shared/Model/ChatModel.swift#L467) | +| `activeCall` | `Call?` | Currently active call | [L468](../Shared/Model/ChatModel.swift#L468) | +| `callCommand` | `WebRTCCommandProcessor` | WebRTC command queue | [L469](../Shared/Model/ChatModel.swift#L469) | +| `showCallView` | `Bool` | Whether to show full-screen call UI | [L470](../Shared/Model/ChatModel.swift#L470) | +| `activeCallViewIsCollapsed` | `Bool` | Whether call view is in PiP mode | [L471](../Shared/Model/ChatModel.swift#L471) | #### Remote Desktop | Property | Type | Description | Line | |----------|------|-------------|------| -| `remoteCtrlSession` | `RemoteCtrlSession?` | Active remote desktop session | [L414](../Shared/Model/ChatModel.swift#L414) | +| `remoteCtrlSession` | `RemoteCtrlSession?` | Active remote desktop session | [L474](../Shared/Model/ChatModel.swift#L474) | #### Misc | Property | Type | Description | Line | |----------|------|-------------|------| -| `userAddress` | `UserContactLink?` | User's SimpleX address | [L391](../Shared/Model/ChatModel.swift#L391) | -| `chatItemTTL` | `ChatItemTTL` | Global message TTL | [L392](../Shared/Model/ChatModel.swift#L392) | -| `appOpenUrl` | `URL?` | URL opened while app active | [L393](../Shared/Model/ChatModel.swift#L393) | -| `appOpenUrlLater` | `URL?` | URL opened while app inactive | [L394](../Shared/Model/ChatModel.swift#L394) | -| `showingInvitation` | `ShowingInvitation?` | Currently displayed invitation | [L416](../Shared/Model/ChatModel.swift#L416) | -| `draft` | `ComposeState?` | Saved compose draft | [L420](../Shared/Model/ChatModel.swift#L420) | -| `draftChatId` | `String?` | Chat ID for saved draft | [L421](../Shared/Model/ChatModel.swift#L421) | -| `networkInfo` | `UserNetworkInfo` | Current network type and status | [L422](../Shared/Model/ChatModel.swift#L422) | -| `conditions` | `ServerOperatorConditions` | Server usage conditions | [L424](../Shared/Model/ChatModel.swift#L424) | -| `stopPreviousRecPlay` | `URL?` | Currently playing audio source | [L419](../Shared/Model/ChatModel.swift#L419) | +| `userAddress` | `UserContactLink?` | User's SimpleX address | [L451](../Shared/Model/ChatModel.swift#L451) | +| `chatItemTTL` | `ChatItemTTL` | Global message TTL | [L452](../Shared/Model/ChatModel.swift#L452) | +| `appOpenUrl` | `URL?` | URL opened while app active | [L453](../Shared/Model/ChatModel.swift#L453) | +| `appOpenUrlLater` | `URL?` | URL opened while app inactive | [L454](../Shared/Model/ChatModel.swift#L454) | +| `showingInvitation` | `ShowingInvitation?` | Currently displayed invitation | [L476](../Shared/Model/ChatModel.swift#L476) | +| `draft` | `ComposeState?` | Saved compose draft | [L480](../Shared/Model/ChatModel.swift#L480) | +| `draftChatId` | `String?` | Chat ID for saved draft | [L482](../Shared/Model/ChatModel.swift#L482) | +| `networkInfo` | `UserNetworkInfo` | Current network type and status | [L483](../Shared/Model/ChatModel.swift#L483) | +| `conditions` | `ServerOperatorConditions` | Server usage conditions | [L485](../Shared/Model/ChatModel.swift#L485) | +| `stopPreviousRecPlay` | `URL?` | Currently playing audio source | [L479](../Shared/Model/ChatModel.swift#L479) | ### Non-Published Properties | Property | Type | Description | Line | |----------|------|-------------|------| -| `messageDelivery` | `[Int64: () -> Void]` | Pending delivery confirmation callbacks | [L426](../Shared/Model/ChatModel.swift#L426) | -| `filesToDelete` | `Set` | Files queued for deletion | [L428](../Shared/Model/ChatModel.swift#L428) | -| `chatListBanner` | `ChatListBanner?` | The banner kind the chat list showed this app session; `bannerSlotFree(for:)` tells whether a kind may take the slot (see [chat-list.md](client/chat-list.md)) | [L474](../Shared/Model/ChatModel.swift#L474) | -| `im` | `ItemsModel` | Reference to `ItemsModel.shared` | [L432](../Shared/Model/ChatModel.swift#L432) | +| `messageDelivery` | `[Int64: () -> Void]` | Pending delivery confirmation callbacks | [L487](../Shared/Model/ChatModel.swift#L487) | +| `filesToDelete` | `Set` | Files queued for deletion | [L489](../Shared/Model/ChatModel.swift#L489) | +| `chatListBanner` | `ChatListBanner?` | The banner kind the chat list showed this app session; `bannerSlotFree(for:)` tells whether a kind may take the slot (see [chat-list.md](client/chat-list.md)) | [L493](../Shared/Model/ChatModel.swift#L493) | +| `im` | `ItemsModel` | Reference to `ItemsModel.shared` | [L501](../Shared/Model/ChatModel.swift#L501) | ### Key Methods | Method | Description | Line | |--------|-------------|------| -| `getUser(_ userId:)` | Find user by ID | [L455](../Shared/Model/ChatModel.swift#L455) | -| `updateUser(_ user:)` | Update user in list and current | [L466](../Shared/Model/ChatModel.swift#L466) | -| `removeUser(_ user:)` | Remove user from list | [L476](../Shared/Model/ChatModel.swift#L476) | -| `getChat(_ id:)` | Find chat by ID | [L487](../Shared/Model/ChatModel.swift#L487) | -| `addChat(_ chat:)` | Add chat to list | [L542](../Shared/Model/ChatModel.swift#L542) | -| `updateChatInfo(_ cInfo:)` | Update chat metadata | [L556](../Shared/Model/ChatModel.swift#L556) | -| `replaceChat(_ id:, _ chat:)` | Replace chat in list | [L608](../Shared/Model/ChatModel.swift#L608) | -| `removeChat(_ id:)` | Remove chat from list | [L1217](../Shared/Model/ChatModel.swift#L1217) | -| `popChat(_ id:)` | Move chat to top of list | [L1193](../Shared/Model/ChatModel.swift#L1193) | -| `totalUnreadCountForAllUsers()` | Sum unread across all users | [L1093](../Shared/Model/ChatModel.swift#L1093) | +| `getUser(_ userId:)` | Find user by ID | [L524](../Shared/Model/ChatModel.swift#L524) | +| `updateUser(_ user:)` | Update user in list and current | [L535](../Shared/Model/ChatModel.swift#L535) | +| `removeUser(_ user:)` | Remove user from list | [L545](../Shared/Model/ChatModel.swift#L545) | +| `getChat(_ id:)` | Find chat by ID | [L556](../Shared/Model/ChatModel.swift#L556) | +| `addChat(_ chat:)` | Add chat to list | [L611](../Shared/Model/ChatModel.swift#L611) | +| `updateChatInfo(_ cInfo:)` | Update chat metadata | [L625](../Shared/Model/ChatModel.swift#L625) | +| `replaceChat(_ id:, _ chat:)` | Replace chat in list | [L677](../Shared/Model/ChatModel.swift#L677) | +| `removeChat(_ id:)` | Remove chat from list | [L1328](../Shared/Model/ChatModel.swift#L1328) | +| `popChat(_ id:)` | Move chat to top of list | [L1295](../Shared/Model/ChatModel.swift#L1295) | +| `totalUnreadCountForAllUsers()` | Sum unread across all users | [L1195](../Shared/Model/ChatModel.swift#L1195) | --- @@ -259,7 +259,7 @@ enum SecondaryItemsModelFilter { | `presetTags` | `[PresetTag: Int]` | Preset tag counts (groups, contacts, favorites, etc.) | [L194](../Shared/Model/ChatModel.swift#L194) | | `unreadTags` | `[Int64: Int]` | Unread count per user tag | [L195](../Shared/Model/ChatModel.swift#L195) | -### [ActiveFilter](../Shared/Views/ChatList/ChatListView.swift#L52) +### [ActiveFilter](../Shared/Views/ChatList/ChatListView.swift#L53) ```swift enum ActiveFilter { @@ -271,7 +271,7 @@ enum ActiveFilter { --- -## 5. [ChannelRelaysModel](../Shared/Model/ChatModel.swift#L354-L368) +## 5. [ChannelRelaysModel](../Shared/Model/ChatModel.swift#L354-L378) **Class**: `class ChannelRelaysModel: ObservableObject` **Singleton**: `ChannelRelaysModel.shared` @@ -291,14 +291,14 @@ Holds runtime relay state for the currently viewed channel. Used by `ChannelRela | Method | Description | Line | |--------|-------------|------| | `set(groupId:groupRelays:)` | Populate all properties at once | [L359](../Shared/Model/ChatModel.swift#L359) | -| `reset()` | Clear all properties to nil/empty | [L346](../Shared/Model/ChatModel.swift#L346) | +| `reset()` | Clear all properties to nil/empty | [L374](../Shared/Model/ChatModel.swift#L374) | --- -## 6. [Chat](../Shared/Model/ChatModel.swift#L1301-L1353) +## 6. [Chat](../Shared/Model/ChatModel.swift#L1418-L1470) **Class**: `final class Chat: ObservableObject, Identifiable, ChatLike` -**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L1301) +**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L1418) Represents a single conversation in the chat list. Each `Chat` is an independent observable object. @@ -306,10 +306,10 @@ Represents a single conversation in the chat list. Each `Chat` is an independent | Property | Type | Description | Line | |----------|------|-------------|------| -| `chatInfo` | `ChatInfo` | Conversation type and metadata | [L1302](../Shared/Model/ChatModel.swift#L1302) | -| `chatItems` | `[ChatItem]` | Preview items (typically last message) | [L1303](../Shared/Model/ChatModel.swift#L1303) | -| `chatStats` | `ChatStats` | Unread counts and min unread item ID | [L1304](../Shared/Model/ChatModel.swift#L1304) | -| `created` | `Date` | Creation timestamp | [L1305](../Shared/Model/ChatModel.swift#L1305) | +| `chatInfo` | `ChatInfo` | Conversation type and metadata | [L1419](../Shared/Model/ChatModel.swift#L1419) | +| `chatItems` | `[ChatItem]` | Preview items (typically last message) | [L1420](../Shared/Model/ChatModel.swift#L1420) | +| `chatStats` | `ChatStats` | Unread counts and min unread item ID | [L1421](../Shared/Model/ChatModel.swift#L1421) | +| `created` | `Date` | Creation timestamp | [L1422](../Shared/Model/ChatModel.swift#L1422) | ### [ChatStats](../SimpleXChat/ChatTypes.swift#L1881-L1903) @@ -327,10 +327,10 @@ struct ChatStats: Decodable, Hashable { | Property | Description | Line | |----------|-------------|------| -| `id` | Chat ID from `chatInfo.id` | [L1336](../Shared/Model/ChatModel.swift#L1336) | -| `viewId` | Unique view identity including creation time | [L1338](../Shared/Model/ChatModel.swift#L1338) | -| `unreadTag` | Whether chat counts as "unread" based on notification settings | [L1328](../Shared/Model/ChatModel.swift#L1328) | -| `supportUnreadCount` | Unread count for group support scope | [L1340](../Shared/Model/ChatModel.swift#L1340) | +| `id` | Chat ID from `chatInfo.id` | [L1453](../Shared/Model/ChatModel.swift#L1453) | +| `viewId` | Unique view identity including creation time | [L1455](../Shared/Model/ChatModel.swift#L1455) | +| `unreadTag` | Whether chat counts as "unread" based on notification settings | [L1445](../Shared/Model/ChatModel.swift#L1445) | +| `supportUnreadCount` | Unread count for group support scope | [L1457](../Shared/Model/ChatModel.swift#L1457) | --- @@ -515,5 +515,5 @@ Chat-level preferences stored in the SQLite database (managed by Haskell core): |------|------| | ChatModel, ItemsModel, Chat, ChatTagsModel, ChannelRelaysModel | [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift) | | ChatInfo, User, Contact, GroupInfo, ChatItem | [`SimpleXChat/ChatTypes.swift`](../SimpleXChat/ChatTypes.swift) | -| ActiveFilter | [`Shared/Views/ChatList/ChatListView.swift`](../Shared/Views/ChatList/ChatListView.swift#L52) | +| ActiveFilter | [`Shared/Views/ChatList/ChatListView.swift`](../Shared/Views/ChatList/ChatListView.swift#L53) | | Preference defaults | [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift), [`SimpleXChat/FileUtils.swift`](../SimpleXChat/FileUtils.swift) |