From 03f3f51988e5245524d5a2d320e75410f8dd80e8 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 15 May 2026 18:25:19 +0400 Subject: [PATCH] kotlin --- .../chat/simplex/common/model/ChatModel.kt | 4 +- .../views/chat/group/ChannelRelaysView.kt | 4 +- .../views/chat/group/GroupMemberInfoView.kt | 3 + .../common/views/newchat/AddChannelView.kt | 10 +++- .../commonMain/resources/MR/base/strings.xml | 3 + apps/multiplatform/product/concepts.md | 1 + .../multiplatform/product/views/group-info.md | 27 +++++++++ apps/multiplatform/spec/api.md | 1 + apps/multiplatform/spec/client/chat-view.md | 8 +++ apps/multiplatform/spec/impact.md | 58 ++++++++++--------- apps/multiplatform/spec/state.md | 15 +++++ 11 files changed, 103 insertions(+), 31 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 80b68f37a5..025b3069db 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -2290,7 +2290,8 @@ enum class RelayStatus { @SerialName("invited") RsInvited, @SerialName("accepted") RsAccepted, @SerialName("active") RsActive, - @SerialName("inactive") RsInactive; + @SerialName("inactive") RsInactive, + @SerialName("rejected") RsRejected; val text: String get() = when (this) { RsNew -> generalGetString(MR.strings.relay_status_new) @@ -2298,6 +2299,7 @@ enum class RelayStatus { RsAccepted -> generalGetString(MR.strings.relay_status_accepted) RsActive -> generalGetString(MR.strings.relay_status_active) RsInactive -> generalGetString(MR.strings.relay_status_inactive) + RsRejected -> generalGetString(MR.strings.relay_status_rejected) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/ChannelRelaysView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/ChannelRelaysView.kt index 2091f66e1d..d6150a1d08 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/ChannelRelaysView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/ChannelRelaysView.kt @@ -109,7 +109,9 @@ private fun ChannelRelaysLayout( if (groupInfo.isOwner) { SectionView { SectionItemView(click = { - val existingRelayIds = groupRelays.filter { it.relayStatus != RelayStatus.RsInactive }.mapNotNull { it.userChatRelay.chatRelayId }.toSet() + // Backend gate (APIAddGroupRelays) rejects any chatRelayId already in group_relays + // regardless of relayStatus, so all current rows must be excluded from the add list. + val existingRelayIds = groupRelays.mapNotNull { it.userChatRelay.chatRelayId }.toSet() ModalManager.end.showModalCloseable(true) { close -> AddGroupRelayView( groupInfo = groupInfo, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index 0a7eba63a0..fc5f73de75 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -615,6 +615,9 @@ fun GroupMemberInfoLayout( val clipboard = LocalClipboardManager.current ShareRelayAddressButton { clipboard.shareText(simplexChatLink(relayAddress)) } } + if (groupRelay?.relayStatus == RelayStatus.RsRejected) { + InfoRow(stringResource(MR.strings.member_info_status), stringResource(MR.strings.member_info_relay_status_rejected_by_operator)) + } } if (groupInfo.useRelays && member.memberRole == GroupMemberRole.Relay) { SectionTextFooter( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt index 09372636ab..74f9b2d3ac 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt @@ -596,8 +596,14 @@ fun chatRelayDisplayName(relay: UserChatRelay): String { @Composable fun RelayStatusIndicator(status: RelayStatus, connFailed: Boolean = false, memberStatus: GroupMemberStatus? = null) { val removed = memberStatus in listOf(GroupMemberStatus.MemLeft, GroupMemberStatus.MemRemoved, GroupMemberStatus.MemGroupDeleted) - val color = if (connFailed || removed) Color.Red else if (status == RelayStatus.RsActive) Color.Green else WarningYellow - val text = if (connFailed) generalGetString(MR.strings.relay_status_failed) else if (memberStatus == GroupMemberStatus.MemLeft) generalGetString(MR.strings.relay_conn_status_removed_by_operator) else if (removed) generalGetString(MR.strings.relay_conn_status_removed) else status.text + val isRejected = status == RelayStatus.RsRejected + val color = if (connFailed || removed || isRejected) Color.Red else if (status == RelayStatus.RsActive) Color.Green else WarningYellow + val text = + if (connFailed) generalGetString(MR.strings.relay_status_failed) + else if (memberStatus == GroupMemberStatus.MemLeft) generalGetString(MR.strings.relay_conn_status_removed_by_operator) + else if (isRejected) generalGetString(MR.strings.relay_status_rejected) + else if (removed) generalGetString(MR.strings.relay_conn_status_removed) + else status.text Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 75d917575d..95566c04b8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -2992,6 +2992,9 @@ accepted active inactive + rejected + Status + rejected by relay operator All relays removed diff --git a/apps/multiplatform/product/concepts.md b/apps/multiplatform/product/concepts.md index da33bf11d7..5d707cf832 100644 --- a/apps/multiplatform/product/concepts.md +++ b/apps/multiplatform/product/concepts.md @@ -49,6 +49,7 @@ This document provides a structured mapping between product-level concepts, thei | PC28 | Chat Tags | [README.md](README.md) (Navigation Map) | [spec/state.md](../spec/state.md) | `common/.../views/chatlist/TagListView.kt`, `ChatListView.kt` | `Types.hs` (`ChatTag`), `Controller.hs` | | PC29 | User Address | [README.md](README.md) (Contacts, User Management) | [spec/api.md](../spec/api.md) | `common/.../views/usersettings/UserAddressView.kt`, `UserAddressLearnMore.kt` | `Controller.hs` (`APICreateMyAddress`) | | PC30 | Member Support Chat | [README.md](README.md) (Groups) | [spec/api.md](../spec/api.md) | `common/.../views/chat/group/MemberSupportView.kt`, `MemberSupportChatView.kt`, `MemberAdmission.kt` | `Messages.hs` (`GroupChatScope`), `Controller.hs` | +| PC31 | Channels (Relays) | [views/group-info.md](views/group-info.md) | [spec/client/chat-view.md](../spec/client/chat-view.md), [spec/state.md](../spec/state.md) | `common/.../model/ChatModel.kt` (`RelayStatus` incl. `RsRejected`, `GroupRelay`, `GroupMemberRole.Relay`, `GroupMemberStatus.MemRejected`), `common/.../views/chat/group/ChannelRelaysView.kt`, `GroupMemberInfoView.kt` (rejected-status row), `common/.../views/newchat/AddChannelView.kt` (`RelayStatusIndicator` rejected branch), `common/.../views/chat/group/AddGroupRelayView.kt` | `Controller.hs` (`APIAddGroupRelays`, `APIAllowRelayGroup`, `XGrpRelayReject` CONF handler) | **Legend for abbreviated paths:** - `common/.../` expands to `common/src/commonMain/kotlin/chat/simplex/common/` diff --git a/apps/multiplatform/product/views/group-info.md b/apps/multiplatform/product/views/group-info.md index 65b068adc8..d7400f5592 100644 --- a/apps/multiplatform/product/views/group-info.md +++ b/apps/multiplatform/product/views/group-info.md @@ -130,6 +130,30 @@ Shown when `developerTools` preference is enabled: Business chats use alternative labels: "Delete chat" instead of "Delete group". +### Channel Relays View (`ChannelRelaysView`) + +Accessible from channel info; shows relay members (role == `Relay`): + +| Element | Description | +|---|---| +| Relay list | Filtered from `chatModel.groupMembers` by `Relay` role; excludes `MemRemoved` and `MemGroupDeleted` | +| Relay row | Profile image, relay display name, status text (`RelayStatus.text` or connection status via `relayConnStatus`) | +| Relay tap | Navigates to `GroupMemberInfoView` with `groupRelay:` parameter | +| Add relay entry | Owner-only "Add relay" action opens `AddGroupRelayView`; the available-to-add list excludes any `chatRelayId` already present in `groupRelays` (regardless of `relayStatus`), so inactive or rejected relays cannot be re-added without first removing them via the row's long-press menu | +| Long-press menu | Owner-only "Remove relay" action for relays that can be removed | +| Empty state | "No chat relays" | +| Footer | "Chat relays forward messages to channel subscribers." | + +Owner sees relay status from `apiGetGroupRelays`; non-owner sees connection status only. + +#### Channel Member Info — relay surface (in `GroupMemberInfoView`) + +| Element | Description | +|---|---| +| Relay link info row | Shown when `member.relayLink` exists, displays `hostFromRelayLink(link)` | +| Relay address info row | Shown when `groupRelay?.userChatRelay.address` exists, with "Share relay address" button | +| Status row (rejected) | Shown when `groupRelay?.relayStatus == RelayStatus.RsRejected`: "Status: rejected by relay operator". The relay rejected the invitation to rejoin this channel after a prior `/leave`; the owner-side `GroupMember.memberStatus` is also set to `MemLeft` so the relay renders identically to one that explicitly left. Clearable only by the relay operator running `/relay allow #`. | + ## Source Files | File | Path | @@ -143,3 +167,6 @@ Business chats use alternative labels: "Delete chat" instead of "Delete group". | `WelcomeMessageView.kt` | `views/chat/group/WelcomeMessageView.kt` | | `MemberAdmission.kt` | `views/chat/group/MemberAdmission.kt` | | `MemberSupportView.kt` | `views/chat/group/MemberSupportView.kt` | +| `ChannelRelaysView.kt` | `views/chat/group/ChannelRelaysView.kt` | +| `AddGroupRelayView.kt` | `views/chat/group/AddGroupRelayView.kt` | +| `AddChannelView.kt` (`RelayStatusIndicator`) | `views/newchat/AddChannelView.kt` | diff --git a/apps/multiplatform/spec/api.md b/apps/multiplatform/spec/api.md index 15d5e141a0..fa3d437988 100644 --- a/apps/multiplatform/spec/api.md +++ b/apps/multiplatform/spec/api.md @@ -352,6 +352,7 @@ Events handled in `processReceivedMsg` include: | `DeletedMember` / `DeletedMemberUser` | A member was removed | | `LeftMember` | A member left voluntarily | | `GroupUpdated` | Group profile changed | +| `GroupRelayUpdated` | Owner-side: a relay's `relayStatus` and/or the member's status changed. Fires on `XGrpRelayReject` with `relayStatus = RsRejected` and `GroupMember.memberStatus = MemLeft` — final on owner side until cleared by the relay operator's `/relay allow #` (no event emitted to the owner for that clear). | | `MemberRole` | A member's role changed | | `MemberBlockedForAll` | A member was blocked for all | | `RcvFileStart` / `RcvFileComplete` / `RcvFileError` | File receive progress | diff --git a/apps/multiplatform/spec/client/chat-view.md b/apps/multiplatform/spec/client/chat-view.md index 2819b1e751..0031ae51a5 100644 --- a/apps/multiplatform/spec/client/chat-view.md +++ b/apps/multiplatform/spec/client/chat-view.md @@ -322,3 +322,11 @@ Key sections: group profile, group link, member list with roles, group preferenc | `MemberSupportChatView.kt` | Member support chat (scoped context) | | `MemberSupportView.kt` | Support chat list for moderators | | `WelcomeMessageView.kt` | Group welcome message editor | +| `ChannelRelaysView.kt` | Channel relay list. Owner-only Add relay entry opens `AddGroupRelayView` with `existingRelayIds = groupRelays.mapNotNull { it.userChatRelay.chatRelayId }.toSet()` — every relay currently in `groupRelays` is excluded regardless of `relayStatus`, mirroring the backend `APIAddGroupRelays` gate. Long-press menu offers Remove relay for relays that can be removed. | +| `AddGroupRelayView.kt` | Sheet to pick relays to add to a channel | + +### Relay Rejection Surface + +When a relay operator runs `/leave #channel`, the relay sends `x.grp.relay.reject` over the owner-relay direct contact channel. Owner-side handling: the corresponding `GroupRelay.relayStatus` transitions `RSInvited → RSRejected`; the relay's `GroupMember.memberStatus` is set to `MemLeft` so the owner UI renders the rejected relay identically to one that explicitly ran `/leave` (`MemRejected` is reserved for the knocking-admission flow). In `GroupMemberInfoView`, an additional "Status: rejected by relay operator" `InfoRow` appears when `groupRelay?.relayStatus == RelayStatus.RsRejected`. The status is final on the owner side — clearable only by the relay operator running `/relay allow #`, which has no owner-facing event. + +The `RelayStatusIndicator` composable in `AddChannelView.kt` renders `RsRejected` with a red dot and "rejected" text, matching the `connFailed`/`removed` rendering. diff --git a/apps/multiplatform/spec/impact.md b/apps/multiplatform/spec/impact.md index cd0f836585..f808cf31ba 100644 --- a/apps/multiplatform/spec/impact.md +++ b/apps/multiplatform/spec/impact.md @@ -40,6 +40,7 @@ | PC28 | Chat Tags | | PC29 | User Address | | PC30 | Member Support Chat | +| PC31 | Channels (Relays) | --- @@ -51,13 +52,13 @@ Path prefix: `common/src/commonMain/kotlin/chat/simplex/common/` | Source File | Product Concepts Affected | Risk Level | Notes | |-------------|--------------------------|------------|-------| -| `App.kt` | PC1 through PC30 | High | Root composable — navigation scaffold for all features | +| `App.kt` | PC1 through PC31 | High | Root composable — navigation scaffold for all features | | `AppLock.kt` | PC22 | Medium | App lock state and authorization lifecycle | -| `model/ChatModel.kt` | PC1 through PC30 | High | Central state object — every feature reads or writes here | -| `model/SimpleXAPI.kt` | PC1 through PC30 | High | FFI bridge to Haskell core — all commands and responses | +| `model/ChatModel.kt` | PC1 through PC31 | High | Central state object — every feature reads or writes here | +| `model/SimpleXAPI.kt` | PC1 through PC31 | High | FFI bridge to Haskell core — all commands and responses | | `model/CryptoFile.kt` | PC10, PC23 | Medium | Encrypted file read/write helpers | -| `platform/Core.kt` | PC1 through PC30 | High | Native FFI declarations (`chatMigrateInit`, `chatSendCmd`, etc.) — all API traffic | -| `platform/AppCommon.kt` | PC1 through PC30 | Medium | Shared app initialization logic | +| `platform/Core.kt` | PC1 through PC31 | High | Native FFI declarations (`chatMigrateInit`, `chatSendCmd`, etc.) — all API traffic | +| `platform/AppCommon.kt` | PC1 through PC31 | Medium | Shared app initialization logic | | `platform/Files.kt` | PC10, PC23, PC26 | Medium | File path resolution, temp dirs, encryption utilities | | `platform/NtfManager.kt` | PC18 | High | Notification manager expect declarations | | `platform/Notifications.kt` | PC18 | Medium | Notification channel and permission abstractions | @@ -67,7 +68,7 @@ Path prefix: `common/src/commonMain/kotlin/chat/simplex/common/` | `platform/Cryptor.kt` | PC23 | Medium | Keystore encryption expect declarations | | `platform/Share.kt` | PC10, PC12 | Low | Share sheet abstractions | | `platform/Images.kt` | PC10, PC19 | Low | Image processing utilities | -| `platform/Platform.kt` | PC1 through PC30 | Low | Platform detection and capability flags | +| `platform/Platform.kt` | PC1 through PC31 | Low | Platform detection and capability flags | | `platform/PlatformTextField.kt` | PC4 | Low | Native text input expect declarations | | `platform/Back.kt` | PC1 | Low | Back navigation handling | | `platform/UI.kt` | PC24 | Low | UI density and locale helpers | @@ -160,7 +161,9 @@ Path prefix: `common/src/commonMain/kotlin/chat/simplex/common/` |-------------|--------------------------|------------|-------| | `views/chat/group/GroupChatInfoView.kt` | PC3, PC14, PC15, PC16, PC30 | High | Group management hub | | `views/chat/group/AddGroupMembersView.kt` | PC14, PC16 | Medium | Member invitation flow | -| `views/chat/group/GroupMemberInfoView.kt` | PC3, PC14, PC16, PC30 | Medium | Member details and role management | +| `views/chat/group/GroupMemberInfoView.kt` | PC3, PC14, PC16, PC30, PC31 | Medium | Member details and role management; relay-address + rejected-status info rows | +| `views/chat/group/ChannelRelaysView.kt` | PC31 | Medium | Channel relay list, add/remove entries | +| `views/chat/group/AddGroupRelayView.kt` | PC31 | Low | Add relay sheet | | `views/chat/group/GroupProfileView.kt` | PC3, PC14 | Medium | Group profile editing | | `views/chat/group/GroupLinkView.kt` | PC15 | Low | Group link creation and sharing | | `views/chat/group/GroupPreferences.kt` | PC3, PC8, PC14 | Medium | Group feature toggles | @@ -189,6 +192,7 @@ Path prefix: `common/src/commonMain/kotlin/chat/simplex/common/` | `views/newchat/NewChatSheet.kt` | PC12 | Medium | Bottom sheet with connection options | | `views/newchat/ConnectPlan.kt` | PC12, PC15 | Medium | Link parsing and connection plan resolution | | `views/newchat/AddGroupView.kt` | PC3, PC14 | Medium | New group creation flow | +| `views/newchat/AddChannelView.kt` | PC31 | Medium | Public channel creation, channel link card, `RelayStatusIndicator` | | `views/newchat/ContactConnectionInfoView.kt` | PC12 | Low | Pending connection details | | `views/newchat/AddContactLearnMore.kt` | PC12 | Low | Educational content | | `views/newchat/QRCode.kt` | PC12 | Low | QR code display | @@ -264,9 +268,9 @@ Path prefix: `common/src/commonMain/kotlin/chat/simplex/common/` | Source File | Product Concepts Affected | Risk Level | Notes | |-------------|--------------------------|------------|-------| -| `views/helpers/AlertManager.kt` | PC1 through PC30 | Medium | Modal alert system used across all features | -| `views/helpers/ModalView.kt` | PC1 through PC30 | Medium | Modal navigation stack | -| `views/helpers/Utils.kt` | PC1 through PC30 | Low | Shared formatting, clipboard, and utility functions | +| `views/helpers/AlertManager.kt` | PC1 through PC31 | Medium | Modal alert system used across all features | +| `views/helpers/ModalView.kt` | PC1 through PC31 | Medium | Modal navigation stack | +| `views/helpers/Utils.kt` | PC1 through PC31 | Low | Shared formatting, clipboard, and utility functions | | `views/helpers/DatabaseUtils.kt` | PC23 | Medium | Keystore passphrase and database helpers | | `views/helpers/LinkPreviews.kt` | PC11 | Medium | Link preview fetching and rendering | | `views/helpers/LocalAuthentication.kt` | PC22 | Medium | Biometric/passcode authentication expect | @@ -319,8 +323,8 @@ Path prefix: `android/src/main/java/chat/simplex/app/` | Source File | Product Concepts Affected | Risk Level | Notes | |-------------|--------------------------|------------|-------| -| `SimplexApp.kt` | PC1 through PC30 | High | Application class — initializes core, preferences, and notification channels | -| `MainActivity.kt` | PC1 through PC30 | High | Single-activity host — intent handling, lifecycle, deep links | +| `SimplexApp.kt` | PC1 through PC31 | High | Application class — initializes core, preferences, and notification channels | +| `MainActivity.kt` | PC1 through PC31 | High | Single-activity host — intent handling, lifecycle, deep links | | `SimplexService.kt` | PC18 | High | Foreground service — keeps message receiver alive | | `CallService.kt` | PC17 | Medium | Foreground service for active calls | | `MessagesFetcherWorker.kt` | PC18 | Medium | WorkManager periodic message fetch | @@ -334,7 +338,7 @@ Path prefix: `common/src/androidMain/kotlin/chat/simplex/common/` | Source File | Product Concepts Affected | Risk Level | Notes | |-------------|--------------------------|------------|-------| -| `platform/AppCommon.android.kt` | PC1 through PC30 | Medium | Android app initialization actual declarations | +| `platform/AppCommon.android.kt` | PC1 through PC31 | Medium | Android app initialization actual declarations | | `platform/SimplexService.android.kt` | PC18 | Medium | Android foreground service actual implementation | | `platform/Files.android.kt` | PC10, PC23, PC26 | Medium | Android file paths and content-URI resolution | | `platform/Cryptor.android.kt` | PC23 | Medium | Android Keystore encryption actual implementation | @@ -400,7 +404,7 @@ Path prefix: `desktop/src/jvmMain/kotlin/chat/simplex/desktop/` | Source File | Product Concepts Affected | Risk Level | Notes | |-------------|--------------------------|------------|-------| -| `Main.kt` | PC1 through PC30 | High | JVM entry point — Haskell init, migrations, app launch | +| `Main.kt` | PC1 through PC31 | High | JVM entry point — Haskell init, migrations, app launch | ### 3.2 Desktop Platform Implementations (desktopMain) @@ -411,7 +415,7 @@ Path prefix: `common/src/desktopMain/kotlin/chat/simplex/common/` | `DesktopApp.kt` | PC1, PC2, PC3 | High | Desktop Compose window — window lifecycle, crash recovery | | `StoreWindowState.kt` | — | Low | Window position/size persistence | | `model/NtfManager.desktop.kt` | PC18 | Medium | Desktop system tray notification display | -| `platform/AppCommon.desktop.kt` | PC1 through PC30 | Medium | Desktop app initialization actual declarations | +| `platform/AppCommon.desktop.kt` | PC1 through PC31 | Medium | Desktop app initialization actual declarations | | `platform/SimplexService.desktop.kt` | PC18 | Low | Desktop background receiver (no foreground service) | | `platform/Files.desktop.kt` | PC10, PC23, PC26 | Medium | Desktop file path resolution | | `platform/Cryptor.desktop.kt` | PC23 | Medium | Desktop keystore encryption actual implementation | @@ -473,13 +477,13 @@ The Haskell core is compiled as a shared native library (`libsimplex.so` / `libs | Source File | Product Concepts Affected | Risk Level | Notes | |-------------|--------------------------|------------|-------| -| `src/Simplex/Chat.hs` | PC1 through PC30 | High | Main chat module — top-level orchestration | -| `src/Simplex/Chat/Controller.hs` | PC1 through PC30 | High | Command processor — all API commands dispatched here | -| `src/Simplex/Chat/Types.hs` | PC1 through PC30 | High | Core data types shared across all features | -| `src/Simplex/Chat/Core.hs` | PC1 through PC30 | High | Chat engine lifecycle (start, stop, subscribe) | -| `src/Simplex/Chat/Library/Commands.hs` | PC1 through PC30 | High | API command handler implementations | -| `src/Simplex/Chat/Library/Internal.hs` | PC1 through PC30 | High | Internal helpers for command processing | -| `src/Simplex/Chat/Library/Subscriber.hs` | PC1 through PC30 | High | Event subscriber — incoming message routing | +| `src/Simplex/Chat.hs` | PC1 through PC31 | High | Main chat module — top-level orchestration | +| `src/Simplex/Chat/Controller.hs` | PC1 through PC31 | High | Command processor — all API commands dispatched here | +| `src/Simplex/Chat/Types.hs` | PC1 through PC31 | High | Core data types shared across all features | +| `src/Simplex/Chat/Core.hs` | PC1 through PC31 | High | Chat engine lifecycle (start, stop, subscribe) | +| `src/Simplex/Chat/Library/Commands.hs` | PC1 through PC31 | High | API command handler implementations | +| `src/Simplex/Chat/Library/Internal.hs` | PC1 through PC31 | High | Internal helpers for command processing | +| `src/Simplex/Chat/Library/Subscriber.hs` | PC1 through PC31 | High | Event subscriber — incoming message routing | | `src/Simplex/Chat/Protocol.hs` | PC2, PC3, PC4, PC5, PC6, PC7 | High | Chat-level message protocol (x-events) | | `src/Simplex/Chat/Messages.hs` | PC2, PC3, PC4, PC5, PC6, PC7, PC8, PC9 | High | Message types and content | | `src/Simplex/Chat/Messages/CIContent.hs` | PC4, PC5, PC6, PC7, PC8, PC9, PC11 | Medium | Chat item content variants | @@ -489,8 +493,8 @@ The Haskell core is compiled as a shared native library (`libsimplex.so` / `libs | `src/Simplex/Chat/Files.hs` | PC10 | Medium | File transfer orchestration | | `src/Simplex/Chat/Delivery.hs` | PC2, PC3 | Medium | Message delivery engine | | `src/Simplex/Chat/Markdown.hs` | PC4 | Low | Markdown parsing for message formatting | -| `src/Simplex/Chat/Store.hs` | PC1 through PC30 | High | Database store interface | -| `src/Simplex/Chat/Store/Shared.hs` | PC1 through PC30 | Medium | Shared store utilities | +| `src/Simplex/Chat/Store.hs` | PC1 through PC31 | High | Database store interface | +| `src/Simplex/Chat/Store/Shared.hs` | PC1 through PC31 | Medium | Shared store utilities | | `src/Simplex/Chat/Store/Messages.hs` | PC4, PC5, PC6, PC7, PC8 | High | Message persistence | | `src/Simplex/Chat/Store/Groups.hs` | PC3, PC14, PC15, PC16, PC30 | High | Group persistence | | `src/Simplex/Chat/Store/Direct.hs` | PC2, PC12, PC13 | High | Contact persistence | @@ -519,11 +523,11 @@ The Haskell core is compiled as a shared native library (`libsimplex.so` / `libs | `src/Simplex/Chat/Operators/Presets.hs` | PC25 | Low | Preset server operators | | `src/Simplex/Chat/Operators/Conditions.hs` | PC25 | Low | Operator usage conditions | | `src/Simplex/Chat/AppSettings.hs` | PC25 | Low | App settings sync types | -| `src/Simplex/Chat/Mobile.hs` | PC1 through PC30 | High | C FFI exports — JNI bridge target | +| `src/Simplex/Chat/Mobile.hs` | PC1 through PC31 | High | C FFI exports — JNI bridge target | | `src/Simplex/Chat/Mobile/File.hs` | PC10 | Medium | Mobile file read/write FFI | -| `src/Simplex/Chat/Mobile/Shared.hs` | PC1 through PC30 | Medium | Shared FFI helpers | +| `src/Simplex/Chat/Mobile/Shared.hs` | PC1 through PC31 | Medium | Shared FFI helpers | | `src/Simplex/Chat/Mobile/WebRTC.hs` | PC17 | Low | WebRTC FFI helpers | -| `src/Simplex/Chat/View.hs` | PC1 through PC30 | Low | Terminal view rendering (not used by mobile/desktop UI) | +| `src/Simplex/Chat/View.hs` | PC1 through PC31 | Low | Terminal view rendering (not used by mobile/desktop UI) | | `src/Simplex/Chat/Stats.hs` | PC25 | Low | Server statistics tracking | | `src/Simplex/Chat/Util.hs` | — | Low | General Haskell utilities | | `src/Simplex/Chat/Styled.hs` | — | Low | Terminal styled text (not used by mobile/desktop UI) | diff --git a/apps/multiplatform/spec/state.md b/apps/multiplatform/spec/state.md index 900d6593ab..6eea65fd44 100644 --- a/apps/multiplatform/spec/state.md +++ b/apps/multiplatform/spec/state.md @@ -300,6 +300,21 @@ data class ChatStats( | `ChatInfo.ContactConnection` | `"contactConnection"` | `contactConnection: PendingContactConnection` | | `ChatInfo.InvalidJSON` | `"invalidJSON"` | `json: String` | +### RelayStatus (Channels) + +`RelayStatus` is an `enum class` at [`ChatModel.kt line 2288`](../common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt#L2288) modelling a relay's lifecycle for a channel on the owner's side. Serialized as a lowercase string via `@SerialName`. + +| Case | SerialName | Meaning | +|---|---|---| +| `RsNew` | `"new"` | Allocated locally; not yet sent | +| `RsInvited` | `"invited"` | `XGrpRelayInv` sent, awaiting `XGrpRelayAcpt` | +| `RsAccepted` | `"accepted"` | Accepted, link-data update pending | +| `RsActive` | `"active"` | Listed in channel link data; forwarding | +| `RsInactive` | `"inactive"` | No longer in link data or backend reports it removed | +| `RsRejected` | `"rejected"` | Relay sent `XGrpRelayReject` for the channel link; final on the owner side. Clearable only by the relay operator running `/relay allow #`. The owner-side `GroupMember.memberStatus` is also set to `MemLeft` so the relay renders identically to one that explicitly left (`MemRejected` is reserved for the knocking-admission flow). | + +The `text` extension on the enum returns the localized status string (resource key `relay_status_*`, with `relay_status_rejected` = "rejected"). + ---