Both profile pickers get an "Add profile" row. It opens the existing CreateProfile form, which gains an optional submit callback, and hands the created profile to the picker's existing selection path - so the create flow and a row tap run the same code. ActiveProfilePicker's row handler is extracted to selectProfile() for that; the body is unchanged apart from indentation. The form opens in ModalManager.fullscreen: on desktop the other placements are panes, where center closes the chat the invitation is in and start and end leave the picker beside it usable while apiChangeConnectionUser invalidates the connection id. The list is refreshed after creating, because keepActiveUser skips the switch that would otherwise do it and nothing else re-lists, and the reassignment is skipped if the form was closed while the profile was being created, when close() would pop the picker instead.
SimpleX Chat -- Kotlin Multiplatform Specification
Table of Contents
Executive Summary
SimpleX Chat is a Kotlin Multiplatform application targeting Android and Desktop (JVM) platforms. The UI layer is built entirely with Jetpack Compose. The application communicates with a Haskell-based cryptographic core (simplex-chat) through a JNI bridge -- native functions declared in Kotlin and linked at runtime to a shared library (libapp-lib). Platform-specific behavior (notifications, file system paths, services, audio/video) is abstracted using the expect/actual pattern and a runtime-assignable PlatformInterface callback object.
The Gradle project is structured as three modules:
| Module | Purpose |
|---|---|
:common |
Shared Compose UI, models, platform abstractions (commonMain, androidMain, desktopMain) |
:android |
Android application entry point (SimplexApp, MainActivity) |
:desktop |
Desktop application entry point (Main.kt, showApp()) |
All meaningful application logic resides in :common/commonMain. Platform source sets (androidMain, desktopMain) provide actual implementations for expect declarations and host platform-specific integration code.
Dependency Graph
App Entry Points
+-- Android: SimplexApp.onCreate -> initHaskell -> initMultiplatform -> initChatControllerOnStart
| MainActivity.onCreate -> setContent { AppScreen() }
+-- Desktop: main() -> initHaskell -> runMigrations -> initApp -> showApp -> AppWindow -> AppScreen()
|
v
Common Module (commonMain)
+-- ChatModel (Compose state singleton) <-> ChatController/SimpleXAPI (JNI bridge) <-> Haskell Core (chat_ctrl)
+-- Views (Compose)
| +-- App.kt: AppScreen -> MainScreen
| +-- ChatListView -> ChatView -> ComposeView -> SendMsgView
| +-- ChatItemView (message rendering: text, image, video, voice, file, call, events)
| +-- Settings: SettingsView, UserProfileView, UserProfilesView
| +-- Onboarding: OnboardingView, WhatsNewView, CreateFirstProfile
| +-- Call: CallView, IncomingCallAlertView
| +-- Database: DatabaseView, DatabaseEncryptionView, DatabaseErrorView
| +-- Groups: GroupChatInfoView, AddGroupMembersView, GroupMemberInfoView
| +-- Contacts: ContactListNavView
| +-- Remote: ConnectDesktopView, ConnectMobileView
| +-- Terminal: TerminalView
+-- Models
| +-- ChatModel -- global app state (Compose MutableState singleton)
| +-- ChatsContext -- per-context chat list state (primary + optional secondary)
| +-- Chat -- per-conversation state (chatInfo, chatItems, chatStats)
| +-- ChatController -- API command dispatch, event receiver, preferences
| +-- AppPreferences -- 150+ SharedPreferences keys
+-- Services
| +-- NtfManager -- abstract notification coordinator (Android/Desktop implementations)
| +-- SimplexService -- Android foreground service for background messaging
| +-- ThemeManager -- theme resolution (system/light/dark/simplex/black + per-user overrides)
| +-- CallManager -- WebRTC call lifecycle
+-- Platform (expect/actual)
+-- Core.kt -- JNI declarations (external fun), initChatController, chatInitTemporaryDatabase
+-- AppCommon.kt -- runMigrations, AppPlatform enum
+-- Files.kt -- dataDir, tmpDir, filesDir, dbAbsolutePrefixPath (expect)
+-- Share.kt -- shareText, shareFile, openFile (expect)
+-- VideoPlayer.kt -- VideoPlayerInterface, VideoPlayer (expect class)
+-- RecAndPlay.kt -- RecorderInterface, AudioPlayerInterface (expect)
+-- UI.kt -- showToast, hideKeyboard, getKeyboardState (expect)
+-- Notifications.kt -- allowedToShowNotification (expect)
+-- NtfManager.kt -- abstract NtfManager class
+-- Platform.kt -- PlatformInterface (runtime callback object)
+-- Cryptor.kt -- CryptorInterface (expect)
+-- Images.kt -- bitmap utilities (expect)
+-- SimplexService.kt-- getWakeLock (expect)
+-- Log.kt, Modifier.kt, Back.kt, ScrollableColumn.kt, PlatformTextField.kt, Resources.kt
Specification Documents
| Document | Path | Description |
|---|---|---|
| Architecture | spec/architecture.md | System layers, module structure, JNI bridge, app lifecycle, event streaming, platform abstraction |
| State Management | spec/state.md | ChatModel singleton, ChatsContext, Chat data class, AppPreferences, ActiveChatState |
| API | spec/api.md | ChatController command dispatch, ~150 API functions in 11 categories, CC/CR/API types |
| Database | spec/database.md | SQLite database files, migrations, encryption, backup/restore |
| Impact | spec/impact.md | Source file → product concept mapping for change impact analysis |
| Chat View | spec/client/chat-view.md | ChatView, ChatItemView, message rendering, item interactions |
| Chat List | spec/client/chat-list.md | ChatListView, ChatPreviewView, filtering, search, tags |
| Compose | spec/client/compose.md | ComposeView, SendMsgView, ComposeState, attachments, mentions |
| Navigation | spec/client/navigation.md | App screen routing, onboarding, settings, new chat flows |
| Calls | spec/services/calls.md | WebRTC call lifecycle, signaling, platform-specific call views |
| Files | spec/services/files.md | File transfer (SMP inline / XFTP), CryptoFile encryption, platform file paths |
| Notifications | spec/services/notifications.md | NtfManager, SimplexService, notification channels, background delivery |
| Theme | spec/services/theme.md | ThemeManager, color system, wallpapers, per-user overrides |
Product Documents
| Category | Path | Topic |
|---|---|---|
| Overview | product/README.md | Product overview, capability map, navigation map |
| Concepts | product/concepts.md | 30 product concepts (PC1-PC30) mapped to docs + source |
| Glossary | product/glossary.md | Domain term definitions (9 sections) |
| Rules | product/rules.md | 18 business rules in 6 categories |
| Gaps | product/gaps.md | 7 known gaps with recommendations |
| Flows | product/flows/ | onboarding, messaging, connection, calling, file-transfer, group-lifecycle |
| Views | product/views/ | chat-list, chat, settings, onboarding, call, new-chat, contact-info, group-info, user-profiles |
Source Entry Points
| Component | File | Key Symbol | Line |
|---|---|---|---|
| Android Application | SimplexApp.kt |
class SimplexApp |
41 |
| Android Activity | MainActivity.kt |
class MainActivity |
27 |
| Desktop Entry | Main.kt |
fun main() |
21 |
| Desktop App Window | DesktopApp.kt |
fun showApp() |
33 |
| Desktop Init | AppCommon.desktop.kt |
fun initApp() |
21 |
| Common App Screen | App.kt |
fun AppScreen() |
47 |
| JNI Bridge | Core.kt |
external fun initHS() |
18 |
| Chat Controller | SimpleXAPI.kt |
object ChatController |
493 |
| Chat Model | ChatModel.kt |
object ChatModel |
86 |
| App Preferences | SimpleXAPI.kt |
class AppPreferences |
94 |
| Platform Interface | Platform.kt |
interface PlatformInterface |
15 |
| Notification Manager | NtfManager.kt |
abstract class NtfManager |
19 |
| Theme Manager | ThemeManager.kt |
object ThemeManager |
18 |
| Android Haskell Init | AppCommon.android.kt |
fun initHaskell(packageName: String) |
33 |
| Common Migrations | AppCommon.kt |
fun runMigrations() |
41 |
| Android Service | SimplexService.kt |
class SimplexService |
41 |
| Gradle Root | settings.gradle.kts |
include(":android", ":desktop", ":common") |
22 |
| Common Build | build.gradle.kts |
kotlin { androidTarget(); jvm("desktop") } |
14 |